| ||||||||||||||
Scanners, digital cameras, and other image-acquisition devices are part of the computing landscape. Despite their ubiquity, however, Java does not provide a standard API for interacting with these devices. And yet there certainly is a desire to have a standard API--see java.net's image acquisition API [15] forum for evidence of that desire. For now, we must either be content to use a commercial API, such as Gnome's Morena [16], or create our own API (to save money or implement our own features).
Welcome to a three-part series that explores the TWAIN and SANE specifications for image acquisition, and presents TWAIN-based and SANE-based Java APIs that I created to support image acquisition in the Java world. Because the source code is freely available, you can customize those APIs as you see fit.
In part one of this series, you begin to discover TWAIN. You then explore a very simple API that bridges the Java world with the TWAIN world: JTwain. Finally, you play with a simple Swing-based application that interacts with JTwain to select an image-acquisition device and acquire images from that device, to be displayed within a scrollable window: JTwainDemo. Part two increases your knowledge of TWAIN, and then builds upon the JTwain API to take advantage of additional TWAIN features. Finally, part three concludes this series, by exploring SANE, presenting a SANE-based API for Java, explaining the need for two standards, and studying the goal of merging the TWAIN and SANE standards into a unified image-acquisition standard.
Note: Because I'm working on a Windows platform, this series is biased in that direction. If your platform is not Windows, you should still read this series. I believe you'll find useful material that can be adapted to other platforms.
TWAIN is not an acronym, even though some believe it stands for Technology Without An Interesting Name. From the TWAIN Working Group's FAQ (see Resources [13]): TWAIN is from [Rudyard] Kipling's "The Ballad of East and West"--"... and never the twain shall meet ...", reflecting the difficulty, at the time, of connecting scanners and personal computers. It was up-cased to TWAIN to make it more distinctive.
According to the FAQ [17], TWAIN is an image-capture API for the Microsoft Windows and Apple Macintosh operating systems. That API was introduced in 1992 and version 1.9 is the most current version. Before we can build a Java API to interact with that image-capture API, we need to understand TWAIN. The best way to do that: obtain a copy of the TWAIN specification (see Resources [13]). The following sections explore that specification, in terms of the big picture and several important details.
Note: I discuss the rationale for TWAIN not directly supporting Linux and Unix in the final part of this series. As you will discover, it is still possible to use TWAIN with Linux or Unix.
TWAIN requires three software elements (and hardware) working together to enable image acquisition:
Application: The application presents a File menu with Select source... and Acquire... menu items for choosing an image-acquisition device and obtaining an image from that device, respectively. In response to the user selecting one of those menu items, the application sends messages (also known as events) to TWAIN. When acquiring an image, TWAIN sends messages to the application, which the application handles in its message-handling (also known as event-handling) loop.
Source: A source (also known as a data source) is a driver that controls a specific image-acquisition device and is written by the device's developer to conform to the TWAIN specification. Sources are stored in files that end with the .ds file extension. For example, on my Windows platform, the c:\windows\Twain_32 directory contains a file named hpprsclt.ds. That file serves as my Hewlett Packard ScanJet 3300C scanner source.
Source manager: The source manager (also known as the data
source manager) manages the interactions between applications and sources. One
of those sources is known as the default source, which is used by the
source manager in the absence of any specified source. For the Windows
platform, there are two source managers: the twain.dll 16-bit
source manager and the twain_32.dll 32-bit source manager. Both
source managers exist in my c:\windows directory. Along with
those files are twunk_16.exe and twunk_32.exe. They
make it possible for the 32-bit source manager to enumerate 16-bit sources and
for the 16-bit source manager to enumerate 32-bit sources. In this series, the
only source manager file I'm interested in is twain_32.dll.
The application communicates directly with the source manager, the source manager communicates directly with both the application and a source, and each source communicates directly with the source manager and hardware. All of that communication is illustrated in Figure 1.

Figure 1. The communicating elements of TWAIN
Version 1.9 of the TWAIN specification is more than 550 pages long. As I found out, it's rather overwhelming when first encountered. To help you avoid having to master the TWAIN specification just to read this article, I present (below) only those details necessary for understanding JTwain; you can peruse the bulk of the specification when you have the time.
States and sessions: Communication between TWAIN elements is defined by a sequence of seven states, where the first three states can be found in every session (the period of time in which an application is connected to the source manager or the period of time in which an application is connected to a source via the source manager): pre-session, source manager loaded, source manager open, source open, source enabled, transfer is ready, and transferring. A session normally transitions forward from its current state to the next state and transitions backward from its current state to the previous state without missing intermediate states.
In the pre-session state, the source manager exists on disk, but is not in memory because no application has established a session with it. To start a session with the source manager, an application must load the source manager into memory. The session begins in the source manager loaded state, and the source manager can accept operation messages from the application. Before the source manager can be used to manage sources, however, it must be opened. When that is done, the session moves forward to the source manager open state. In that state, the source manager can provide a list of sources to the application, can open sources, and can close sources. The source manager remains in that state until it is closed. However, the source manager will not close if any sources that it is managing are open.
Once the source manager has been opened, an application starts another session, by asking the source manager to open a source. That session begins in the source open state. The source is ready to receive source-specific operation messages that inquire about the source's capabilities (which I'll talk about in part two), such as the availability of an automatic document feeder. The application next moves a source into the source enabled state, which causes the source to display its own user interface (i.e., a dialog box), if requested to do so by the application. After being enabled, the source notifies the application, via the application's message loop, when it is ready for data transfer to begin. Once that message is sent, the session moves to the transfer is ready state. For an image transfer, the application must inquire about image information (such as image size) before the transfer can start. After obtaining image information, the session transitions to the transferring state. The source transfers image data to the application. When the transfer completes, the application informs the source manager, which transitions the session back to either the transfer is ready or source enabled state (depending on the message sent). Assuming the session returns to source enabled, a subsequent message to disable the source transitions the session back to source open, and a followup message to close the source terminates the session. However, the application's prior session with the source manager still exists.
DSM_Entry() and DS_Entry() C-style functions:
The application communicates with the source manager by invoking the source
manager's DSM_Entry() function. In turn, the source manager talks
to a source, by invoking the source's DS_Entry() function. Those
functions have nearly identical parameter lists and are fully described in the
TWAIN specification. An application never invokes DS_Entry().
Data structures: TWAIN specifies a variety of data structures, of which
some are used by JTwain: TW_IDENTITY describes an application and
a source to the source manager, TW_UINT16 contains
DSM_Entry()'s return value, TW_MEMREF serves to cast
DSM_Entry()'s final argument to an untyped pointer,
TW_USERINTERFACE handles user interface coordination between the
application and a source, TW_EVENT passes events (messages, in
Windows-speak) from the application to a source, TW_PENDINGXFERS
tells the application how many more complete transfers the source currently
has available, TW_IMAGEINFO describes the image data being
transferred between source and application, and TW_UINT32 records
a Windows handle to the image data.
Operation triplets: An application communicates an operation request to
the source manager by passing three arguments in a DSM_Entry()
function call: a data group value, a data argument type value, and a message
value. The source manager often forwards that operation triplet to the source,
by invoking DS_Entry().
The data group value identifies an operation category: DG_CONTROL
operations control a TWAIN session, DG_IMAGE operations work with
image data, and DG_AUDIO operations work with audio data
(supported by some digital cameras--I don't discuss audio data in this
series). The data argument type value identifies the type of data being passed
as the last argument to DSM_Entry() or DS_Entry().
For example, DAT_IDENTITY identifies the last argument being
passed as the address of a TW_IDENTITY data structure. The
message value identifies a specific operation, such as MSG_OPENDS
(open a source). Examples of operation triplets: DG_CONTROL/DAT_PARENT/MSG_OPENDSM (open the source manager),
DG_IMAGE/DAT_IMAGENATIVEXFER/MSG_GET (begin transferring an image's data from a source to the
application, via the native data transfer mechanism), and
DG_CONTROL/DAT_IDENTITY/MSG_USERSELECT (inform the source manager to display a dialog box
that allows the user to select a source).
Data transfer modes: TWAIN provides three modes for transferring image data from source to application: native, disk file, and buffered memory. Native (which is used by JTwain and is the default transfer mode) transfers an image as either a device-independent bitmap on Windows or a PICT bitmap on Macintosh. Disk file (which may or may not be supported by a source) transfers an image into a file created by the application, and using a format specified by the application (and supported by the source). Buffered memory transfers an image as an unformatted bitmap using one or more memory buffers. Applications may have to loop repeatedly until all buffered image data has been retrieved.
Return codes and condition codes: DSM_Entry() returns with
a value that identifies the status of an operation. That value is represented
at the source code level by a constant with a TWRC_ prefix.
Statuses tested by JTwain include success (TWRC_SUCCESS), failure
(TWRC_FAILURE), event belongs to an application and not a source
(TWRC_NOTDSEVENT), and data transfer complete
(TWRC_XFERDONE). If TWRC_FAILURE is returned, the
application can invoke DSM_Entry() with the following operation
triplet to obtain a condition code that clarifies the reason for failure:
DG_CONTROL/DAT_STATUS/MSG_GET. That
condition code is represented by a TWCC_-prefixed constant at the
source code level. This article's version of JTwain does not examine condition
codes.
I've implemented the JTwain API as a hybrid Java/Windows library that consists of two Java classfiles and a Windows dynamic link library, written in C++. The following sections tour the Java and C++ sides of the library, and provide the instructions needed to construct that library.
The Java side of the JTwain library consists of the files JTwain.java
and JTwainException.java. JTwain.java declares the class
JTwain, which presents three methods that use the Java Native
Interface to initialize JTwain, acquire one image from the default source, and
select the default source:
public static boolean init() loads jtwain.dll (which
contains the compiled C++ code for the Windows side of the JTwain library). If
that file is found, loads, and successfully initializes, init()
returns Boolean true. Otherwise, init() returns Boolean false.
Although you can call init() multiple times, it's good programming
practice to call that method only once. Be sure to call init()
before calling any other method in the JTwain class. Otherwise,
an UnsatisifiedLinkError is all you will get for your efforts.
Note: Although I could have chosen to use a static initializer to initialize JTwain,
I prefer the init() method, as its use conveniently allows me to
dynamically reconfigure the application to either gray out or not show the
Acquire... and Select Source... menu items in the event the
library's DLL file cannot be found, or something goes wrong during the DLL's
initialization.
public static native Image acquire() displays the dialog box that
is associated with the default source, so that you can configure the source.
If you click the Cancel button, this method returns the null reference.
But if you click the Scan (or similar) button, this method attempts to
scan one image from the source (no matter how many images you may have chosen,
via the dialog box). If successful, it returns an Image.
public static native void selectSourceAsDefault() lets you choose
a new default source. If the Select button is clicked, the highlighted
source becomes the new default source (unless it already was the default). But
if you click the Cancel button, the current default source remains.
JTwainException.java declares THE class JTwainException,
which describes failures originating from jtwain.dll and TWAIN.
The methods acquire() and selectSourceAsDefault() throw
this checked exception.
Three files comprise the C++ side of the JTwain library: twain.h,
jtwain.h, and jtwain.cpp. twain.h is
the standard C-style TWAIN header file that describes TWAIN's public interface
to applications. Consult the Resources [13] section to
obtain a link to that header file. jtwain.h is derived from the
Java JTwain class; I show you how to generate that header file in
the next section. Finally, jtwain.cpp contains the source code to
jtwain.dll. That source code consists of global variables, the
DLL's entry-point function (DllMain()), two C++ functions that
correspond to JTwain's acquire() and
selectSourceAsDefault() native methods, and three helper
functions that are private to the DLL.
Because jtwain.cpp is fully documented, I won't bother to discuss
that source code. Rather, I want to focus on a peculiarity that appears in the
source code, and explain my rationale for that peculiarity: instead of opening
and closing the source manager exactly once in DllMain(), the
source manager is repeatedly opened and closed each time either of the DLL
functions corresponding to JTwain's acquire() and
selectSourceAsDefault() methods is called.
When I first started writing jtwain.cpp, I opened and closed the
source manager exactly once in DllMain(). It didn't take long to
discover a problem with that approach. If the thread that invokes
DllMain() (which is usually the thread that executes a Java
application's main() method) differs from the thread that invokes
the DLL acquire and selectSourceAsDefault functions (which is usually the AWT
event-handling thread), the Win32 GetMessage() function (in the
DLL's acquire function) may appear to lock up.
Before the source manager can be opened, a window must be created. When
opening the source manager, that window's handle is passed to
DSM_Entry() and identifies the parent of the source manager's and
source's dialog box windows. Because GetMessage() does not
retrieve messages for windows belonging to other threads or applications, if
one thread creates the window in DllMain() and a different thread
calls GetMessage() in the DLL's acquire function,
GetMessage() will not return any messages for that window. The
solution I decided upon: open the source manager each time the DLL's acquire
or selectSourceAsDefault functions are called, and close the source manager upon
exit from each function.
Now that you have some insight into the workings of the JTwain library, you'll want to create that library's executable code. Unzip this article's code.zip [18] file, and you should end up with the directory structure below (assuming the c: drive):
c:\unzipped
code
net
javajeff
jtwain
The net, javajeff, and the final jtwain directories correspond to the package name I've assigned to this library.
Assuming c:\unzipped\code is the current directory, construct the Java portion of the library by executing the following command line to compile the library's JTwain.java and JTwainException.java source files:
javac net/javajeff/jtwain/JTwain.java
If all goes well, you should observe the classfiles JTwain.class and JTwainException.class in the jtwain subdirectory of javajeff.
Before you can build the Windows portion of the library, you need to choose an appropriate C++ compiler. I used version 5.5.1 of Borland's free C++ compiler to compile the C++ source code. (Check Resources [13] for a link to that compiler.) You'll have to register with Borland (if you're not already registered), which costs nothing.
If you prefer Microsoft's Visual C++ product, you will probably need to remove
or comment out all #pragma argsused directives from the
jtwain.cpp source file. Borland compilers use that directive to
suppress warning messages arising from passing arguments to functions, but not
referring to those arguments inside the functions.
Complete the following steps to construct the Windows portion of the library:
Create jtwain.h. Assuming
c:\unzipped\code is the current directory,
accomplish that task by executing this command line:
javah net.javajeff.jtwain.JTwain. You should observe a
net_javajeff_jtwain_JTwain.h header file. You will need to rename
that
file to jtwain.h and move it into the
c:\unzipped\code\net\javajeff\jtwain directory.
Create jtwain.dll. Assuming c:\unzipped\code\net\javajeff\jtwain is the current directory, and that you installed Borland C+ 5.5.1 and kept the install defaults, place the following commands in a batch file, and execute the batch file to compile jtwain.cpp and link the resulting object file into jtwain.dll (each command should appear in its entirety on a single line):
set path=c:\borland\bcc55\bin;%path% bcc32 -tWD -I"c:\borland\bcc55\include; c:\jdk1.5.0\include;c:\jdk1.5.0\include\win32" -Lc:\borland\bcc55\lib jtwain.cpp
The first command extends the path to include Borland C++ 5.5.1's binary
tools
directory. The second command invokes bcc32 to perform the
compilation and
linking tasks. The -tWD option tells bcc32 that a
DLL is being
created. The -I option specifies the directory path to include
files. On my platform, c:\jdk1.5.0\include and
c:\jdk1.5.0\include\win32 are the locations of the JNI header files
that are needed by jtwain.cpp. Finally, the -L option
specifies the
location of library files. Assuming all goes well, you should discover a
jtwain.dll file in the c:\unzipped\code\net\javajeff\jtwain
directory.
Note: Once the DLL has been built, make it accessible to Windows by copying that file into an appropriate location, such as c:\windows (under Windows 9x/ME).
We're nearly ready to obtain images from image-acquisition devices via JTwain. There's only one thing left to do: create a Java application that employs the JTwain API to get those images. To save you the bother, I've created a simple JTwainDemo application. That application consists of two source files: ImageArea.java and JTwainDemo.java. After we compile those source files, we'll play with JTwain.
Complete the following steps to compile JTwainDemo's source files:
Make sure that c:\unzipped\code is the current directory.
Issue the following command line: javac JTwainDemo.java. Success
is indicated by the appearance of five classfiles in c:\unzipped\code.
Execute java JTwainDemo to launch the JTwainDemo application. The
first item of business accomplished by the application is the initialization
of JTwain in the main() method, as the code fragment below
demonstrates:
if (!JTwain.init ())
{
System.out.println ("JTwainDemo: TWAIN not supported");
return;
}
If initialization fails, the application exits after outputting an appropriate error message to the standard output device.
After a moment, a window appears with a File menu. Open File and you'll see three menu items: Acquire..., Select Source..., and Exit. Click Select Source.... That menu item's action listener executes the following code fragment:
try
{
JTwain.selectSourceAsDefault ();
}
catch (JTwainException e2)
{
JOptionPane.showMessageDialog (JTwainDemo.this,
e2.getMessage ());
}
When selectSourceAsDefault() executes, it displays the dialog box
shown in Figure 2 (unless a JTwainException object is thrown
because of some kind of failure). Because source names depend on the types of
connected TWAIN-supported devices, you might observe a different list of
source names.

Figure 2. Select Source dialog box
Select an appropriate source name (such as TWAIN_32 Sample Source) and click the Select button. The highlighted source name identifies the new default source.
Return to File... and select Acquire.... That menu item's action listener executes the following code fragment:
try
{
Image im = JTwain.acquire ();
if (im == null)
return;
ia.setImage (im);
jsp.getHorizontalScrollBar ().setValue (0);
jsp.getVerticalScrollBar ().setValue (0);
}
catch (JTwainException e2)
{
JOptionPane.showMessageDialog (JTwainDemo.this,
e2.getMessage ());
}
When acquire() executes, it displays the dialog box that Figure 3
reveals (assuming TWAIN_32 Sample Source is the default source
and that a JTwainException object has not been thrown). If the
user clicks the Cancel button, acquire() returns null and
no new image will be displayed. But if an image is successfully acquired, the
ia.setImage (im); method call causes the image to be displayed,
and the subsequent scrollbar method calls reorient the scrollbars so that the
upper-left corner of the image appears in the upper-left corner of the window.

Figure 3. Source-specific dialog box
From the Bit-Depth field, choose either an 8-bit or a 24-bit image. If
you choose 1-bit, a JTwainException object will thrown, because
that bit depth is not supported by JTwain. You can specify as many images as
you want, but only one image will be acquired. When you're ready to acquire an
image, click the Scan button.
Suppose you choose a 24-bit image and then click Scan. In a moment, you would notice a colorful image in JTwainDemo's window. Figure 4 illustrates some of that image.

Figure 4. JTwainDemo uses JTwain to acquire an image from TWAIN_32 Sample Source
Note: TWAIN_32 Sample Source is an emulated source that is part of the Twain developer's SDK. Consult the Resources [13] section for a link to that SDK.
Java's lack of a standard image-acquisition API is an oversight that hopefully will be rectified in a future release. Until that time, however, we can either purchase a commercial API or create our own API.
We can base our API on either of the TWAIN or SANE specifications. So far, we've only looked at TWAIN, in terms of the big picture and important details. We have also explored the very simple TWAIN-based JTwain API and played with a simple JTwainDemo application that demonstrates JTwain.
I have some homework for you to accomplish:
Modify the JTwain API library to include a
public static native String getDefaultSource() method, which
returns the name of the default source as a String object. That
method should throw a JTwainException object if some kind of
failure occurs. Think carefully about the operation triplet you need to send
to DSM_Entry().
Test your getDefaultSource() method by placing the following line
of code (within an appropriate try/catch construct) after the call to
JTwain.init() in JTwainDemo's main()
method:
System.out.println ("Default source = " + JTwain.getDefaultSource ());
Modify JTwainDemo to save an acquired image to a file. Add a Save as...
menu item to the File menu, and provide some logic to choose a filename
and save the image to a file (with that filename) in a format of your choice.
Feel free to use the imageio package, available in J2SE 1.4 and J2SE 5.0.
Next time, Java Tech digs deeper into TWAIN and incorporates new features into the JTwain API.
The previous Java Tech article [23] presented you with some challenging homework on thread communication. Let's revisit that homework and investigate solutions.
Five philosophers sit around a circular table. Each philosopher alternates between thinking and eating rice. In front of each philosopher is a bowl of rice that is constantly replenished by a dedicated waiter. Exactly five chopsticks are on the table, with one chopstick between each adjacent pair of philosophers. Each philosopher must pick up both chopsticks adjacent to his/her plate simultaneously before that philosopher can eat.
Create a Java application that simulates this behavior. Avoid deadlock and the problem of indefinite postponement, where one or more philosophers soon starve because philosophers adjacent to the starving philosophers are always eating. Make sure that mutual exclusion is enforced, so that two adjacent philosophers do not use the same chopstick at the same time.
Consult the Philos.java source code in this article's attached code file (see Resources [13]).
Links:
[1] http://www.java.net/author/jeff-friesen
[2] http://www.java.net/article/2004/11/16/java-tech-acquire-images-twain-and-sane-part-1#and_never
[3] http://www.java.net/article/2004/11/16/java-tech-acquire-images-twain-and-sane-part-1#big_picture
[4] http://www.java.net/article/2004/11/16/java-tech-acquire-images-twain-and-sane-part-1#devil_details
[5] http://www.java.net/article/2004/11/16/java-tech-acquire-images-twain-and-sane-part-1#jtwain
[6] http://www.java.net/article/2004/11/16/java-tech-acquire-images-twain-and-sane-part-1#tour_jtwain_java
[7] http://www.java.net/article/2004/11/16/java-tech-acquire-images-twain-and-sane-part-1#tour_jtwain_c
[8] http://www.java.net/article/2004/11/16/java-tech-acquire-images-twain-and-sane-part-1#library_construction
[9] http://www.java.net/article/2004/11/16/java-tech-acquire-images-twain-and-sane-part-1#jtwaindemo
[10] http://www.java.net/article/2004/11/16/java-tech-acquire-images-twain-and-sane-part-1#compile_source
[11] http://www.java.net/article/2004/11/16/java-tech-acquire-images-twain-and-sane-part-1#ltb_twain
[12] http://www.java.net/article/2004/11/16/java-tech-acquire-images-twain-and-sane-part-1#conclusion
[13] http://www.java.net/article/2004/11/16/java-tech-acquire-images-twain-and-sane-part-1#resources
[14] http://www.java.net/article/2004/11/16/java-tech-acquire-images-twain-and-sane-part-1#answers_previous
[15] http://forums.java.net/jive/thread.jspa?forumID=23&threadID=172
[16] http://www.gnome.sk/Twain/jtp_try&buy.html
[17] http://twain.org/faq.htm
[18] http://www.java.net/today/2004/11/18/code.zip
[19] http://www.borland.com/products/downloads/download_cbuilder.html
[20] http://twain.org/devfiles/twainkit.exe
[21] http://twain.org/devfiles/twain.h
[22] http://twain.org/docs/Spec1_9_197.pdf
[23] http://www.java.net/pub/a/today/2004/09/15/sync2.html
[24] http://javajeff.mb.ca