Skip to main content

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

November 18, 2004








Contents
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
Conclusion
Resources
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 java.net'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 for Technology
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, 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
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 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.

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

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

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
code.zip 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 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:

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

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

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 files:
ImageArea.java and JTwainDemo.java. 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 JTwainDemo.java. 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 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
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
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.

Conclusion

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.

Resources

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 Philos.java source code in this article's attached
    code file (see Resources).

width="1" height="1" border="0" alt=" " />
Jeff Friesen is a freelance software developer and educator specializing in Java technology. Check out his site at javajeff.mb.ca.
Related Topics >> Programming   |