Java Tech: Acquire Images with TWAIN and SANE, Part 1 Blog

    " hrefaction="pub">
    <  /tr>                                           

    And Never the TWAIN Shall Meet
       The Big Picture
       The Devil is in the Details
    The JTwain API Library
       Tour JTwain's Java Side
       Tour JTwain's C++ Side
       Library Construction
    The JTwainDemo Application
       Compile the Source Files
       Let There Be TWAIN
    Answers to Previous Homework

    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's image acquisition API forum for evidence of that desire. For now, we must either be content to use a commercial API, such as Gnome's Morena, 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.

    And Never the TWAIN Shall Meet

    TWAIN is not an acronym, even though some believe it stands forTechnology Without An Interesting Name. From the TWAIN Working Group's FAQ (see Resources):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, 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). 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.

    The Big Picture

    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, thec:\windows\Twain_32 directory contains a file namedhpprsclt.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 thedefault 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
    Figure 1. The communicating elements of TWAIN

    The Devil is in the Details

    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 thesource 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 enabledstate, 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 thetransfer 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 transferringstate. 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 invokesDS_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 containsDSM_Entry()'s return value, TW_MEMREFserves 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_PENDINGXFERStells the application how many more complete transfers the source currently has available, TW_IMAGEINFO describes the image data being transferred between source and application, andTW_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 invokingDS_Entry().

      The data group value identifies an operation category:DG_CONTROL operations control a TWAIN session,DG_IMAGE operations work with image data, andDG_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() orDS_Entry(). For example, DAT_IDENTITYidentifies the last argument being passed as the address of aTW_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), andDG_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). IfTWRC_FAILURE is returned, the application can invokeDSM_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.

    The JTwain API Library

    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.

    Tour JTwain's Java Side

    The Java side of the JTwain library consists of the and 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() loadsjtwain.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 callinit() before calling any other method in theJTwain class. Otherwise, anUnsatisifiedLinkError 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. declares THE classJTwainException, which describes failures originating from jtwain.dll and TWAIN. The methodsacquire() and selectSourceAsDefault()throw this checked exception.

    Tour JTwain's C++ Side

    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 section to obtain a link to that header file. jtwain.h is derived from the JavaJTwain 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 toJTwain's acquire() andselectSourceAsDefault() 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() andselectSourceAsDefault() 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 DLLacquire and selectSourceAsDefaultfunctions (which is usually the AWT event-handling thread), the Win32 GetMessage() function (in the DLL'sacquire 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. BecauseGetMessage() does not retrieve messages for windows belonging to other threads or applications, if one thread creates the window in DllMain() and a different thread callsGetMessage() in the DLL's acquirefunction, GetMessage() will not return any messages for that window. The solution I decided upon: open the source manager each time the DLL's acquire orselectSourceAsDefault functions are called, and close the source manager upon exit from each function.

    Library Construction

    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 file, and you should end up with the directory structure below (assuming thec: drive):

    c:\unzipped code net javajeff jtwain

    The net, javajeff, and the final jtwaindirectories 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.javaand source files:

    javac net/javajeff/jtwain/

    If all goes well, you should observe the classfilesJTwain.class and JTwainException.class in thejtwain 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. (CheckResources 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 argsuseddirectives 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:

    1. 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 thec:\unzipped\code\net\javajeff\jtwain directory.

    2. Create jtwain.dll. Assumingc:\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 invokesbcc32 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\includeand 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.dllfile in the c:\unzipped\code\net\javajeff\jtwaindirectory.

      Note: Once the DLL has been built, make it accessible to Windows by copying that file into an appropriate location, such asc:\windows (under Windows 9x/ME).

    The JTwainDemo Application

    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 and After we compile those source files, we'll play with JTwain.

    Compile the Source Files

    Complete the following steps to compile JTwainDemo's source files:

    1. Make sure that c:\unzipped\code is the current directory.

    2. Issue the following command line: javac Success is indicated by the appearance of five classfiles in c:\unzipped\code.

    Let There Be TWAIN

    Execute java JTwainDemo to launch the JTwainDemo application. The first item of business accomplished by the application is the initialization of JTwain in themain() 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 aJTwainException 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
    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, theia.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
    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 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 appropriatetry/catch construct) after the call toJTwain.init() in JTwainDemo'smain() 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 theimageio 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.


    Answers to Previous Homework

    The previous Java Tech article presented you with some challenging homework on thread communication. Let's revisit that homework and investigate solutions.

    1. 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.

    2. 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.

    3. Consult the source code in this article's attached code file (see Resources).