Bridge the gap between Java and Twain

Integrate imaging devices in Java using Twain and JNI

Twain has long allowed Win32 programmers to integrate imaging devices into their applications at no cost. Twain allows us to capture images from seemingly disparate devices like flatbed scanners, fingerprint readers, Web cams, and digital cameras. The standard allows Aunt Emma to take a picture at a wedding, acquire the images onto her PC that afternoon, and send the edited pictures to the entire family that night. Thus, when interfacing with image devices, application developers can free themselves from working with arcane and many times proprietary interfaces, and instead concentrate on one interface.

Twain functionality would greatly benefit the Win32 Java community, but, alas, these open-standards benefits have been available for years in the Win32 world, but, because of Java's platform independence, not the Java world. In this article I show how you too can talk Twain by introducing an architecture that integrates a Java 1.4 Swing application with a simple JNI (Java Native Interface)-based package.

Let's start by discussing the architectural layers involved in retrieving a JPEG file from your favorite Twain-enabled device. Twain natively acquires a Win32 DIB (Device-Independent Bitmap), but we will retrieve a Java readable JPEG file. Retrieving such a file involves five architectural layers:

  1. Your Swing 1.4 GUI (graphical user interface).
  2. The org.scavino.jtwain source code shown later in this article.
  3. The generated JNI wrapper classes.
  4. The EZTwain DLL (dynamic link library), a freeware library encapsulating much of the Twain interface with an easy-to-use C API. The EZTwain library has long helped our C brethren with Twain. The same library via JNI can now assist our Java bothers and sisters.
  5. The free Intel JPEG Library (IJL) for encoding and decoding JPEG images. While it does feature more functionalites, providing a Java-readable JPEG is its main responsibility.

Thankfully all these technologies are free.

Now that we have thought out the architectural layers, we now self impose a process. As an OOAD (object-oriented analysis and design) instructor, I insist on an inception phase and mission statement when developing new APIs. My mission is to build the smallest API possible that will satisfy the specified requirements. What requirements you may ask? Rather than enumerating countless never-to-be-read shall statements, I use the Rational Unified Process (RUP) for guidance. As RUP is a use-case-focused process, we start with our "Acquire a Twain Image" use-case, which has one primary flow consisting of several steps to accomplish our result.

You will find the entire use-case here. The document is intentionally human readable and devoid of code mumbo-jumbo. If we write our requirements in plain English, our stakeholders will take the time to understand our use-case. When analysts, management, and testers understand the use-cases, we receive software on time and on budget. Let me paraphrase the primary use-case as follows:

Acquire Twain image steps (Happy Day Scenario):

  1. The application checks for Twain's existence
  2. Twain exists
  3. The application queries the Twain subsystem for all available sources
  4. The application displays the choices in the Swing 1.4 GUI
  5. The user chooses a source
  6. The user presses an "Acquire" button
  7. The application retrieves a Java image into the application
  8. The application further processes a Java image as the requirements state

Technically the use-case will conclude when you add your application-specific requirements.

Now that we have our use-case defined, we can begin to move towards design by creating a UML (Unified Modeling Language) sequence diagram. The diagram below lets us sequentially follow the steps required to fulfill the preceding use-case. The diagram allows us to follow the messages as they touch each class and thus, clearly see how the use-case is being implemented.

Acquire image primary flow. Click on thumbnail to view full-size image.

Without the use-case and flow, we could write large cumbersome APIs that no one will use or, worse yet, come up short during construction.

We can place the above steps into an iteration plan. We finish the iteration when we accomplish all the steps. We can add more features in subsequent iterations once we evaluate feedback from the first iteration. For now, we need only the bare minimum.

When I wrote the first version of the org.scavino.jtwain library, I added almost every function that Twain exposed—not a good idea. My client wanted only the minimum, and I gave the near maximum. I could have forgone the extra code to maintain, understand, and document.

Intel JPEG Library

Our architecture first needs a reliable JPEG decoder that can convert the native Twain DIBs to a format that Java can interpret. The IJL encodes and decodes your JPEG images. In its simplest form, the IJL can convert the native Twain DIBs to JPEGs rather than the default Windows BMP (Bitmap). Since Java natively handles JPEGs and not BMPs, some subsystem must complete the conversion. Choosing which layer handles the conversion is an important decision for several reasons:

  • First, we must decide if Java GUI clients should need additional libraries like Sun Microsystems' JAI (Java Advanced Imaging) or Java native BMP classes to manipulate the BMP file format. Or should the native DLL process the image?
  • Second, if the DLL converts the images to JPEG, then the IJL DLL might have to reside in the client machine's path. Adding a DLL to the DOS path is not a trivial deployment decision.
  • Third, an additional option would link the much larger ijl15l.lib library in lieu of the smaller ijl15.lib link library. This static library increases the stack size and would ignore potential ijl15l.lib DLL upgrades from Intel. If we wanted new JPEG functionality from the ijl15.dll, we would have to rebuild and test again.

To make this exercise cleaner and less daunting, the static link library option is the best. We won't need to call the cumbersome GetProcAddress(...) method, and we have one less deployment hassle to manage.

Classic EZTwain 1.x DLL

Now that we can decode the native image from Twain to a supported Java format like JPEG, wrapping the Twain C API becomes a priority. At first, I started to write my own Twain wrapper library. After several failures and a bruised ego, I searched for an alternative. I strained my brain until I found the EZTwain DLL library.

Dosadi

has given away their 1.x version to the Twain community at large. After some minor modifications, we can use the EZTwain DLL with our JNI headers. Because the DLL as-is hangs during the Twain calls, source code changes were required.

Specifically I modified the TWAIN_UnloadSourceManager() method. I had to ensure all the pointers were set to null, otherwise the DLL would not properly unload the source manager. After I changed the source code, the Java application responded without errors. The native Win32 environment didn't need these changes, as the DLL unloaded properly

Java and Win32 hardware: The ugly truth

Though the low-level plumbing is set up, communication between Java and Win32 will still present problems. Java has difficulty integrating with hardware devices because of the Win32 message pump. Java has no real way to connect to the event handlers in the Win32 world. Instead, you must create a proxy window, so it, not the Java application, can receive the Win32 SDK messages.

That approach negates the need to pass the HWND parameter to EZTwain DLL; instead we can use a null HWND as a parameter within the DLL. When the EZTwain 1.x DLL detects a null, it creates a proxy window, hides it, and uses it for pumping message handlers. The Java client is relieved of the HWND responsibility, simplifying the bridge between the two layers.

Note: That technique proves useful for synchronous hardware devices like Twain or utility libraries that lack a real GUI. With Twain, you pass it a command, the vendor-supplied dialog appears, the acquisition occurs, and then Twain eventually returns a DIB. However, be aware that, with TAPI (Telephony Application Programmers Interface) and other asynchronous technologies, passing a null HWND will not suffice. Further processing of call-back methods may be required. Asynchronous hardware devices need both the application and the DLL to participate in two-way communication via the Win32 message handlers. As Java clients cannot take part, more complex solutions are required: perhaps a TCP/IP socket server.

The JNI C interface

Now we need to map the previous flow of events to an actual interface in the JNI layer. We must write the exact number of JNI methods required to realize the use-case—no more and no less. Strictly adhering to RUP keeps us focused. The sequence diagram depicts the user actor going through the use-case and exercising our API. I detail the methods below:

isTwainAvailble()

The JNI method jboolean Java_org_scavino_twain_JTwain_isTwainAvailble() allows an application to test for Twain's existence. This method accomplishes Step 1 in the use-case's primary flow. isTwainAvailble() does not confirm whether your particular Twain device is enabled or even attached to the machine. It merely tests for the existence of the twain.dll in your path and verifies that you have at least one Twain device installed. This result is cached after the Twain library has been prodded. A more elaborate exception-handling mechanism could replace the simple boolean return type. Again, the current goal is simplicity, and a future iteration could have exception handling in the interface.

While the JNI jboolean Java_org_scavino_twain_JTwain_isTwainAvailble() method could poll each device to see if it is actually attached, doing so would sacrifice performance and usability. Calling the device not only degrades performance, but might also actually prompt the user to capture an image. A vendor dialog abruptly appearing is not a pleasant user experience.

getAvailableSources()

The JNI jobjectArray Java_org_scavino_twain_JTwain_getAvailableSources() method accomplishes Steps 2 and 3 in the use-case flow. As identified in the Twain specification, the Twain layer is interrogated for each driver's vendor name. This method's source code actually originates from the Twain specification, not EZTwain.

The names are stored in the simplest container possible: a Java String array. When originally designing the API, I used the Java Vector and later decided against it. I eventually chose the jobjectArray type already available in JNI. The choice obviated the need to directly invoke expensive Vector constructors and then call add() methods via JNI. The application ultimately uses jobjectArray to display the installed devices' names.

acquire(...)

The jstring Java_org_scavino_twain_JTwain_acquire(__Ljava_lang_String_2) method helps to realize Steps 4 and 5 in the event flow. The application passes a Java String representing the device's name to JNI. If this name exists, the device's UI displays to the user for image capture.

After user intervention, the subsystem returns the fully qualified filename of the resulting JPEG image. Java can read JPEG files natively; thus, we've surmounted the Java-to-Twain gap.

I must note a few pitfalls with device names: They are not normally internationalized. Worse yet, sometimes vendors give their devices less-than-descriptive names. For example, one particular fingerprint-reader vendor doesn't even have the word fingerprint in its Twain name. Regardless, you could consider using the ResourceBundle class and some device name-mapping to make the device names logical and international. This tactic assumes you understand the devices you are deploying; otherwise you are at the vendor's mercy for writing descriptive names.

Below you will find the entire org.scavino.jtwain API as previously outlined, with all its methods and sections, minus the Javadoc. I purposely kept the API's Java portion to a minimum:

org.scavino.jtwain: The simple Twain library

package org.scavino.twain;
public class JTwain {
    private static final JTwain mInstance = new JTwain();
    protected final String DLL_NAME = "jtwain";
    
    private JTwain() {
        initLib();
    }
    
    public static JTwain getInstance(){
        return mInstance;
    }
    native public boolean isTwainAvailble();
    native public String[] getAvailableSources();
    native public String acquire();
    native public String acquire(String sourceName);
    private void initLib(){
        try {
            
            System.loadLibrary(DLL_NAME);
        }
        
        finally {
            System.out.println("Loading : " + DLL_NAME + ".dll");
        }
    }
}

As expected, the actual Java source code is rather straightforward and not complicated. The code accomplishes its actual work and details in the lower layers. Java clients see a rather simple API of mostly native methods.

Though small, the API includes some subtle design details, namely use of the Singleton pattern. Considering only one active device is enabled at a time, invoking the org.scavino.jtwain.jtwain class methods through the getInstance() static method as prescribed in the Singleton pattern seemed appropriate.

Note: If you change the package structure, for example, renaming the org.scavino package to fit your corporate standards, you must regenerate the JNI header class using the javah compiler. Also, make sure to use the fully qualified class name as a parameter to javah so the entire package name generates in the resulting C header file. JNI is string-based, and changing the package structure name without regenerating the JNI headers will give you an UnsatisfiedLinkError exception.

JTwacker: Your helpful test client

I have included a test client in the source code for your viewing pleasure. JTwacker first checks for Twain's existence with a call to isTwainAvailble(). As the library is a Singleton, the native DLL loads during the first method call. You should see an initialization message stating such in your output window. The application fills the combo box with the String array result from the getAvailableSources() call. The first call will be the slowest because the Twain library loads during this time. The user now has a list of available devices. Note this great trick of the trade: You don't need to install an actual Twain device to see things work. The folks at http://www.twain.org have provided the Twain community with a sample Twain source. Download the Twain Developer Toolkit from that site and you can test right away. This allows the entire development team to unit-test the code and acquire images without sharing the sometimes expensive hardware device from machine to machine.

Finally, we call the acquire (String sourceName), and the name of the resulting JPEG file returns. I merely display the BufferedImage in a JPanel (see the source code). You can do more.

Java and Win32 imaging devices: The twain do meet

I hope you'll appreciate this architecture's value. Knowing how all the image pieces fit together and mitigating the greatest risks first leads to quality software. While the desire to quickly cobble together code after a night of Red Bull is seductive, the long-term costs of poorly architected software far out weighs the short-run gains of making something compile. This architecture provides a set of blueprints that ensures you build the best API for the right people, with the best setup requirements for the appropriate budget, and on time. Specifically, I have outsourced all the tricky bits to existing libraries that do one task but do that task well. This way, we have a best-of-breed solution and the ability to change out subsystems if better solutions surface along the way. In effect, I am describing a component-driven architecture, which proves the whole can be greater than the parts. On the surface, Twain, EZTwain, JNI, and the Intel JPEG Library might not have much in common, but putting these parts together builds a rather useful subsystem.

Additionally, to integrate the org.scavino.jtwain and meet the requirements, we needed a plan. We applied the Rational Unified Process to subsystem development. Writing our use-case and flows before coding helped us imagine our goal first. Once we created the flows, we designed the API. We wrote the interface to support the steps in our primary flow—no more and no less.

Further enhancements should follow the same process. Given additional requirements, I would write additional flows and see what part of the org.scavino.jtwain API I could reuse and what parts I need to update. This iterative process will allow for managed growth, with each iteration meeting the intended goals.

Hugo Scavino, a certified Java developer and Rational OOAD-certified instructor, has spent more than 10 years programming and working as a consultant. With an MBA from Florida State University, he assists clients in making the OO hurdle. Currently, he leads teams as a design and OOAD expert in Delphi, C++, and now Java. Hugo stays up late improving his computer skills by reading technical books and journals, watching Leo Laporte on TechTV, and playing video games.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies