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

Version 2



    The New JTwain
    A Capable JTwain<  /a>
       Capabilities Quick Study
    The Revised JTwainDemo
    Answers to Previous Homework

    Last time, I introduced a three-part series on TWAIN and SANE. Part one explored TWAIN in terms of the big picture and necessary details, presented the JTwain API library that makes TWAIN available to Java, and revealed the JTwainDemo application that interacts with JTwain to obtain images from scanners and other image-acquisition devices (and present them to the user).

    Part one's JTwain API library has some problems. I begin the second part of this series by pointing out these problems and introducing a new JTwain API library that overcomes them. I next explore data source capabilities--a concept I hinted at in part one--and introduce that portion of the new JTwain API library dedicated to capabilities. I wrap up part two by introducing an improved JTwainDemo that demonstrates the new JTwain.

    Note: Unlike part one, this article does not present build instructions (for brevity). However, you'll find a make file for the library's DLL in the sample code file associated with this article (see Resources).

    The New JTwain

    Several design decisions are responsible for the simplicity of part one's JTwain API library: keep the library small (two native methods), allow only a single image to be transferred in a session, and depend on Windows dialog boxes for the user interface when acquiring an image or selecting a default data source. Ironically, these same design decisions also introduce problems, necessitating a new JTwain API library:

    • Inefficient implementation: Each acquire()method call has to open the data source manager, open the default data source, display a data-source-specific dialog box, obtain one image, close the default data source, and close the data source manager. Repeatedly opening and closing the data source manager and the same default data source (unless changed via a call toselectSourceAsDefault()) is not efficient. I feel that a better design would place these individual operations into their own API methods.

    • Single-image transfer: The acquire() method only returns a single image. This rules out acquiring the multiple images that are made available by automatic document feeders. A better approach: acquire() needs to return an array of images.

    • Windows dialog box dependence:selectSourceAsDefault() and acquire()present Windows dialog boxes to the user. This reliance on Windows detracts from whatever non-Windows look and feel we might choose for our JTwain applications. It would be better to removeacquire()'s dialog box, do away withselectSourceAsDefault(), and provide the necessary JTwain methods that help to construct Java-based "Select Source" and data-source-specific dialog boxes.

    I've created a new JTwain API library that solves the aforementioned problems. Multiple native API methods (including a streamlined acquire()) result in a more efficient implementation. The acquire() method now returns an array of images. And the Windows dialog boxes are no more: I've removed selectSourceAsDefault() (responsible for "Select Source"), and the remodelled acquire() method doesn't display a data-source-specific Windows dialog box. Except for three methods (that I introduce later), all methods in the new API are described below:

    • public static native Image [] acquire() acquires an array of images from the currently open data source. The length of the array (which is allocated by JTwain) determines the number of images that were acquired.

      This method will typically return a single image, unless certain capabilities (a concept I discuss later) in regard to an automatic document feeder are set to appropriate values. I do not discuss automatic document feeders and how to set them up, because I don't have access to those devices. To learn more about TWAIN's support for automatic document feeders, I recommend studying the TWAIN specification (see Resources).

    • public static native void closeDS() closes the currently open data source.

    • public static native void closeDSM() closes the data source manager.

    • public static native int getCC(int dest) returns the condition code from the most recent operation associated with either the data source manager or the currently open data source. If dest is 0, the condition code is returned from the data source manager. If dest is non-zero, the condition code is returned from the currently open data source.

      JTwain declares several constants starting withCC_ that represent all condition codes. If you need to specify a condition code at the source code level, use one of these constants instead of the actual value.

    • public static native String getDefaultDS() returns the name of the default data source. An application should acquire images from this data source if the user has not chosen to select a data source. Also, this name should be highlighted in a Java-based "Select Source" dialog box the first time that dialog box is displayed.

    • public static native String getFirstDS() returns the name of the first data source. When enumerating all data source names, this method must be called beforegetNextDS().

    • public static native String getNextDS() returns the name of the next data source, or an empty string if there are no more data sources. When enumerating all data source names, callgetNextDS() after callinggetFirstDS().

    • public static native int getRC() returns the return code from the most recent operation.

      JTwain declares several constants starting withRC_ that represent all return codes. If you need to specify a return code at the source code level, use one of these constants instead of the actual value.

    • public static native void openDS(String srcName)opens the data source identified by srcName. The open data source is referred to as the currently open data source.

    • public static native void openDSM() opens the data source manager.

    Except for getRC(), which throws no exceptions, each of the above methods is capable of throwingJTwainException objects.

    Note: The methods getRC() and getCC()return values that identify failures with TWAIN. They do not return values that identify non-TWAIN failures. The only places where a non-TWAIN failure might occur are located in theacquire() method: an image transfer used compression or did not produce an 8-bit (grayscale) or 24-bit (RGB) image, the transferred image could not be added to an internal vector data structure (because of limited memory), or the image could not be converted from Windows' device-independent bitmap format to a Java-based image (perhaps a Java class or method couldn't be found, memory was exhausted, or a method could not be called).

    To demonstrate some of the methods in the new JTwain API, I've written a short program that enumerates all data source names. The enumeration technique could be inserted into dialog box code to create a Java-based "Select Source" dialog box.

    // import net.javajeff.jtwain.*; public class EnumSources { public static void main (String [] args) { // Initialize JTwain. if (!JTwain.init ()) { System.out.println ("TWAIN unsupported"); return; } try { // Open data source manager. JTwain.openDSM (); try { // Get name of first data source. String name = JTwain.getFirstDS (); do { // Output data source name. System.out.println (name); // Get name of next data source. name = JTwain.getNextDS (); } while (!name.equals ("")); } catch (JTwainException e) { // Output return code and condition // code from data source manager // (reason for failure). System.out.println (e + ", RC = " + JTwain.getRC () + ", CC = " + JTwain.getCC (0)); } // Close data source manager. JTwain.closeDSM (); } catch (JTwainException e) { // Output return code (reason for // failure). System.out.println (e + ", RC = " + JTwain.getRC ()); } } }

    Because is commented, I won't bother to describe the source code. Compile that code and run it to obtain a list of data source names on your platform. You will need those names to play with the program in the next section. For example, when I execute java EnumSources on my platform, I obtain the following list of data source names:

    HP PrecisionScan LT 3.0 TWAIN_32 Sample Source

    A Capable JTwain

    Image-acquisition devices support varying features. For example, some scanners support automatic document feeders to automate the scanning of multiple documents, and many digital cameras offer a camera preview mode (not to mention information on battery life). Support for color images and information about the author of an image are other examples. Collectively, these and other features are known as capabilities.

    A data source "knows" what capabilities are supported by the image-acquisition hardware that it represents, and displays the current settings of some (if not all) of these capabilities to the user in a data-source-specific dialog box. The user then has the opportunity to modify these settings (such as choosing whether or not to support an automatic document feeder, or changing the paper size) prior to acquiring an image. Part one's Figure 3 revealed a Windows dialog box (on my platform) presenting the current settings of a few capabilities for theTWAIN_32 Sample Source data source.

    Because the responsibility for presenting data-source-specific dialog boxes is no longer with JTwain, but has migrated to JTwain applications, it's necessary for the application to determine capability settings and allow users to modify those settings. To assist the application, I've introduced three methods into the JTwain API that govern access to a single pixel typecapability. This determines the kind of pixel data that a data source can acquire, such as black-and-white images, grayscale images, and RGB (red/green/blue) images:

    • public static native int getPixelType() returns the currently open data source's current pixel type. Compare that return value with one of JTwain's PT_constants--this makes your source code more readable.

    • public static native int [] getPixelTypes() returns an array containing the currently open data source's supported pixel types. Compare the array contents with JTwain'sPT_ constants.

    • public static native void setPixelType(int type)sets the currently open data source's current pixel type. Instead of passing an integer value, pass one of JTwain'sPT_ constants.

    Like the previous methods, each method above is capable of throwing objects of the type JTwainException. But each method can also throw an object of the typeUnsupportedCapabilityException, if the data source that's currently open does not support the pixel type capability.

    Note: Each time a data source is opened viaopenDS(), the current values of all supported capabilities are reset to their defaults. As a result, you must change the current values of whatever capabilities you are supporting before calling acquire().

    I have written a short program that demonstrates the three pixel type methods. You could place the code that gets this capability's current value and list of supported values into a Java-based, data-source-specific dialog box.

    // import net.javajeff.jtwain.*; public class PixelTypeDemo { public static void main (String [] args) { // Ensure exactly one command-line argument is // specified. if (args.length != 1) { System.out.println ("usage: java " + "PixelTypeDemo " + "srcname"); return; } // Initialize JTwain. if (!JTwain.init ()) { System.out.println ("TWAIN unsupported"); return; } try { // Open data source manager. JTwain.openDSM (); try { // Open specified data source. JTwain.openDS (args [0]); try { // Execute pixel type demo. doDemo (); } catch (JTwainException e) { // Output data source CC code. reportFailure (e.getMessage (), 1); } // Close currently open data source. JTwain.closeDS (); } catch (JTwainException e) { // Output data source manager CC code. reportFailure (e.getMessage (), 0); } // Close data source manager. JTwain.closeDSM (); } catch (JTwainException e) { // Do not output a CC code. reportFailure (e.getMessage (), -1); } } // ========================================== // Execute the pixel type demo. This method // catches UnsupportedCapabilityException but // throws JTwainException. // ========================================== static void doDemo () throws JTwainException { try { // Obtain array of supported pixel types. int [] pixTypes = JTwain.getPixelTypes (); // Output array's contents. System.out.println ("Supported pixel types:"); System.out.println (); for (int i = 0; i < pixTypes.length; i++) outputPixelTypeName (pixTypes [i]); // Obtain current pixel type. int pixType = JTwain.getPixelType (); // Output current pixel type. System.out.println (); System.out.print ("Current pixel type: "); outputPixelTypeName (pixType); System.out.println (); // Change current pixel type. if (pixType == JTwain.PT_GRAY) { System.out.println ("Set to RGB"); JTwain.setPixelType (JTwain.PT_RGB); } else { System.out.println ("Set to GRAY"); JTwain.setPixelType (JTwain.PT_GRAY); } // Obtain current pixel type. pixType = JTwain.getPixelType (); // Output current pixel type. System.out.println (); System.out.print ("Current pixel type: "); outputPixelTypeName (pixType); } catch (UnsupportedCapabilityException e) { System.out.println ("No ICAP_PIXELTYPE"); } } // ============================================== // Output a pixel type integer as a string value. // ============================================== static void outputPixelTypeName (int pixType) { switch (pixType) { case JTwain.PT_BW: System.out.println ("Black & White"); break; case JTwain.PT_GRAY: System.out.println ("Grayscale"); break; case JTwain.PT_RGB: System.out.println ("Red/Green/Blue"); break; case JTwain.PT_PALETTE: System.out.println ("Palette"); break; case JTwain.PT_CMY: System.out.println ("CMY"); break; case JTwain.PT_CMYK: System.out.println ("CMYK"); break; case JTwain.PT_YUV: System.out.println ("YUV"); break; case JTwain.PT_YUVK: System.out.println ("YUVK"); break; case JTwain.PT_CIEXYZ: System.out.println ("CIEXYZ"); break; default: System.out.println ("Unknown"); } } // ============================================== // Output a failure message, a return code, and a // condition code based on dest. // // dest < 0 -- don't output condition code // dest = 0 -- output data source manager // condition code // dest > 0 -- output data source condition code // ============================================== static void reportFailure (String msg, int dest) { System.out.print (msg + ", RC = " + JTwain.getRC ()); if (dest >= 0) try { System.out.print (", CC = " + JTwain.getCC (dest)); } catch (JTwainException e) { } } }

    Once again, I won't bother to describe the source code, because of the variety of comments. Compile that code and run it on your platform. For example, when I execute java PixelTypeDemo "TWAIN_32 Sample Source" (the quotes must be present because of embedded spaces in the data source name), I observe the following output:

    Supported pixel types: Black & White Grayscale Red/Green/Blue Current pixel type: Grayscale Set to RGB Current pixel type: Red/Green/Blue
    Capabilities Quick Study

    At some point, you'll most likely want to add your own capabilities methods to JTwain. Before doing that, you will need to acquire the appropriate knowledge. Although the TWAIN specification provides extensive capabilities coverage, you might find wading through all of that information somewhat overwhelming. To help you quickly come up to speed on implementing your own capabilities in JTwain, I recommend that you read this section. It introduces you to the concepts of capability constants, operation triplets needed for getting/setting capability values, and containers for use in those operations.

    The twain.h header file (see Resources) offers an assortment of constants that describe capabilities recognized by TWAIN. Constants organize into three main categories: constants beginning with theCAP_ prefix identify capabilities common to all kinds of image-acquisition devices, constants beginning with theACAP_ prefix identify audio-oriented capabilities, and constants that begin with the ICAP_ prefix identify image-oriented capabilities. As an example, the constantICAP_PIXELTYPE identifies the pixel type capability.

    TWAIN presents six capability-oriented operation triplets that are passed to DSM_Entry() when working with capabilities. I only use three of those operation triplets in JTwain:

    • I use the operation triplet of DG_CONTROL,DAT_CAPABILITY, and MSG_GET to return a range of supported values for pixel type.

    • I use the operation triplet of DG_CONTROL,DAT_CAPABILITY, and MSG_GETCURRENT to return the pixel type's current value.

    • I use operation triplet of DG_CONTROL,DAT_CAPABILITY, and MSG_SET to change the pixel type's current value.

    When an application wants to change a capability's current value, it allocates memory for a specific TWAIN container(data structure), populates that container's fields with appropriate values, and passes that container to TWAIN (via the final operation triplet in the list above). When an application wants to obtain a capability's current value, or obtain a list of supported values, the data source allocates the container's memory. Regardless of the allocation source, the application is responsible for releasing that memory before terminating. For an example of a container and how the memory allocation works on the application side, examine the C++ code fragment below (which refers to various constants and types located in twain.h). That source code changes the current value of the pixel type capability:

    TW_CAPABILITY cap; cap.Cap = ICAP_PIXELTYPE; cap.ConType = TWON_ONEVALUE; // Allocate memory for the container. cap.hContainer = GlobalAlloc (GHND, sizeof(TW_ONEVALUE)); if (cap.hContainer == 0) { // Handle lack of memory ... } // Lock the container memory. TW_ONEVALUE *pTWOneValue = (TW_ONEVALUE *) GlobalLock (cap.hContainer); // Populate the container's fields. pTWOneValue->ItemType = TWTY_UINT16; pTWOneValue->Item = pixType; // Unlock the container memory. GlobalUnlock (cap.hContainer); // Invoke operation by calling DSM_Entry(). g_rc = (*g_pDSM_Entry) (&g_AppID, &g_SrcID, DG_CONTROL, DAT_CAPABILITY, MSG_SET, (TW_MEMREF) &cap); // Deal with success or failure, indicated by g_rc. // Free the container's memory. GlobalFree (cap.hContainer);

    The code fragment above introduces a TW_CAPABILITYdata structure that describes a capability to TWAIN. That data structure's Cap field is assigned theICAP_PIXELTYPE constant, identifying the pixel type image capability. The ConType field is assigned theTWON_ONEVALUE constant that identifies the kind of container holding the new pixel type value as a single-value container. Memory for that container must then be allocated. Under Windows, TWAIN requires that the application callGlobalAlloc() to perform the allocation. UnlikeTWON_ONEVALUE, TW_ONEVALUE provides the correct size to GlobalAlloc().

    Because GlobalAlloc() returns a memory handle, a call is made to GlobalLock() to lock the memory and return a pointer to that memory. Using that pointer, the container's fields are then populated: the ItemTypefield is assigned the TWTY_UINT16 constant, identifying the type of the container's value as a 16-bit unsigned integer, and the Item field is assigned the new value (stored in the variable pixType) for the pixel type capability. Following the assignment, the memory is unlocked by calling GlobalUnlock().

    DSM_Entry() is invoked with the appropriate operation triplet for changing the pixel type capability's value. The address of the capability data structure is passed as the final argument to this function. Whether or not the operation succeeds,GlobalFree() is invoked to release the memory previously allocated for the container.

    In addition to TWON_ONEVALUE andTW_ONEVALUE, JTwain uses theTWON_ENUMERATION/TW_ENUMERATION container to hold a list of supported pixel type values. As you add new capabilities to JTwain, you might also work withTWON_ARRAY/TW_ARRAY andTWON_RANGE/TW_RANGE. The TWAIN specification has much more to say about these containers (and other TWAIN data structures).

    The Revised JTwainDemo

    I've created a revised JTwainDemo application that exercises nearly the entire suite of methods in the new JTwain API. As before, JTwainDemo consists of the source and Compile both source files and execute java JTwainDemo to run that application.

    From the File menu, choose the Select Source... menu item. In response, you'll see an all-Java "Select Source" dialog box that lists the names of all data sources on your platform. As shown in Figure 1, only two data sources exist on my platform.

    Figure 1
    Figure 1: "TWAIN_32 Sample Source" is highlighted in the all-Java "Select Source" dialog box

    Choose Acquire... from the File menu and study the "Config Source" dialog box. That dialog box lists all values supported by the pixel type capability of the currently open data source. Furthermore, one of those values should be highlighted, indicating it's the current value of that capability. As Figure 2 reveals, "Config Source" displays a list of three pixel types (with one of the pixel types highlighted) when "TWAIN_32 Sample Source" is the currently open data source.

    Figure 2
    Figure 2: The all-Java "Config Source" dialog box highlights Grayscale

    Now that you have seen JTwainDemo's GUI enhancements, you'll probably want to study the application's source code. The following code fragment provides you with a glimpse of that source code.

    // Set any selected pixel type. JTwain.setPixelType (cs.getPixType ()); // Acquire one or more images. Only one image // should be returned because no capabilities // involving an automatic document feeder have // been set. Image [] im = JTwain.acquire (); // Update ImageArea panel with the first new // image, and adjust the scrollbars. ia.setImage (im [0]);

    The code fragment is taken from the action listener attached to the Acquire... menu item. After displaying the "Config Source" dialog box, opening the data source manager, and opening the selected data source, the listener executes the code fragment.

    The code fragment's first task is to set the open data source's pixel type. An image is then acquired from that data source. Usually, one image will be returned in the array (at position 0) unless an automatic document feeder is in use and various related capabilities have been appropriately initialized. Once the image has been acquired, it's displayed in the ImageAreapanel.

    Note: When acquiring an image, the sequence of JTwain API calls, from openDSM() through closeDSM(), must be invoked on the same thread. Doing that will prevent the "lockup" problem with the Win32 GetMessage() function that I discussed in part one. Also, although you can execute the image-acquisition sequence on the event-handling thread, your application GUIs will be more responsive by relegating that sequence to a background thread.


    JTwain has grown up. The new version of this API library solves three problems present in the previous JTwain. You've been introduced to the expanded API and have learned about the concept of capabilities, as we dug deeper into TWAIN. You've also been introduced to a revised JTwainDemo application that exercises almost the entire suite of methods in the new API.

    This is the end of our TWAIN coverage. Although I would have loved to dig even deeper into TWAIN (such as exploring more capabilities, discussing alternative image transfer modes, showing how to build a custom data source, and so on), this article is long enough. I believe you now have acquired sufficient knowledge to continue exploring TWAIN on your own.

    I have some homework for you to accomplish:

    • Examine the capabilities list in Chapter 9 of the TWAIN specification and then choose a capability that you might like to add to JTwain. Implement at least two native methods that get the current value and set the current value of that capability. You might also need to implement a third native method to get a list of supported values for the capability. Modify JTwainDemo's source code to demonstrate your capability of choice.

    Next time, Java Tech wraps up this series by exploring SANE.


    Answers to Previous Homework

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

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

    2. 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 ());

    3. Consult the 1 subdirectory of the answersdirectory in this article's attached code file (see Resources).

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

      Consult the 2 subdirectory of the answersdirectory in this article's attached code file (see Resources).