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

by Jeff Friesen
11/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:

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.

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:

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:

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

Jeff Friesen is a freelance software developer and educator specializing in Java technology. Check out his site at javajeff.mb.ca.

Read more Java Tech columns.


 Feed java.net RSS Feeds