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



    What Is SANE?
       SANE Environment
       SANE API
       SANE Network Protocol
    A SANE-Based API for Java
    The Need for Two Specifications
    Merging SANE with TWAIN
    Answers to Previous Homework

    Java doesn't provide a standard API for acquiring images from digital cameras, scanners, and other image-acquisition devices. This omission has inspired this three-part Java Tech series that explores the TWAIN and SANE image-acquisition specifications, and how to make use of those specifications in a Java context. The previous two articles in this series--the first introducing TWAIN and providing a simple TWAIN library and Java application that demonstrates that library, the second on improving on the library and demo application--focused on the TWAIN specification from a Microsoft Windows perspective, because TWAIN's origin lies in the Windows world. In contrast, this article largely moves away from TWAIN (and Windows), by focusing on the Unix-based SANE image-acquisition specification.

    This article begins with an introduction to SANE, where you learn about SANE's environment, API, and network protocol. The article next explores a Java API for acquiring images with SANE. Moving forward, the article discusses the need for both SANE and TWAIN. The article (and series) closes by looking at merging SANE with TWAIN into a unified image-acquisition specification.

    What Is SANE?

    SANE, an acronym for "Scanner Access Now Easy," is an image-capture API. The API provides standardized access to any raster-image scanner, camera, or other kinds of image-acquisition devices. Version 1.03 is the current version.

    SANE was introduced a few years after TWAIN to support (and standardize) image acquisition on Unix and Linux platforms, because TWAIN could not (and still is not able to) do the job. Although SANE originated for Unix and Linux, it has been ported to Mac OS X, OS/2, and other operating systems.

    SANE is maintained by volunteers who meet at SANE's official website (see the Resources section for a link to the website). From that website, you can download SANE source code and documentation for your Unix or Linux platform.

    Make sure to download "SANE Standard Version 1.03" (see Resources for a link). That document introduces SANE, describes SANE's environment, explores the SANE API, and discusses SANE's network protocol. The three sections below are based on material found in that document.

    SANE Environment

    SANE provides a standard interface to raster-image devices. Applications using that interface are known as SANE front ends. Drivers that implement that interface and control raster-image devices are known as SANE back ends. Figure 1 shows the relationship between SANE front ends and back ends.

    Figure 1
    Figure 1. Relating front ends to back ends

    Application front ends communicate with driver back ends through an intermediary known as symbolic link (symlink, for short) to a driver back end that controls a specific image-acquisition device. When an application communicates, it's actually communicating with whatever driver is represented by

    Changing symlinks is convenient for changing drivers without needing to relink applications. However, that is not convenient whenever you want to dynamically switch image-acquisition devices. SANE overcomes this problem by providing the pseudo-driversdll and net. Those pseudo-drivers talk to other SANE drivers instead of physical devices: dlltalks to other SANE drivers on the same machine andnet talks to other SANE drivers across a network. Figure 2 presents an example of a SANE driver hierarchy involvingdll and net. This example is based on, but isn't identical to, an example found in the SANE documentation.

    Figure 2
    Figure 2. A SANE driver hierarchy

    Figure 2 reveals two machines: A and B. Machine A' is a symlink to the dllpseudo-driver. That pseudo-driver uses dynamically linked libraries to access both the mustek scanner driver (which controls the local mustek scanner) and the netpseudo-driver.

    net provides network access to Machine B, by connecting Machine A to the SANE daemon (saned) that runs on Machine B. The daemon communicates with dll, which dynamically loads the hp scanner driver. As a result, an application that runs on Machine A can access Machine B's HP scanner.

    Of course, Figure 2 is just one example. On Machine A, could be a symlink to the netpseudo-driver, or a real driver (such as the mustekscanner driver). System administrators have lots of flexibility in how they set up the SANE driver hierarchy.

    Perhaps the most important part of the SANE environment is the format used to represent acquired images. SANE regards an image as a rectangular area that is subdivided into rows and columns. SANE refers to the intersection of a row and column as a quadratic pixel. This pixel consists of one or more sample values, where a sample value represents a color channel (red, green, blue) or a shade of gray. Each sample also has a bit depth: one, 8, and 16 bits per sample are valid bit depths.

    SANE transmits an image as a sequence of frames. Each frame covers the entire rectangular area but may only contain a subset of the channels. For example, a frame may only contain the red sample values, or the green sample values. Alternatively, a frame could consist of the sample values of all three color channels. More information on SANE's image data format and image transmission can be found in the document mentioned earlier.


    The SANE API was written in the C language. Just as TWAIN supplies the twain.h header file for inclusion in TWAIN-based source code, SANE supplies the sane.h header file for inclusion in SANE-based source code.

    The sane.h header file largely consists of type and function declarations. The type declarations range from simple types, such as SANE_Bool, SANE_Int, andSANE_String, to complex types, such asSANE_Option_Descriptor. A total of 14 functions compose the API:

    • sane_init(): Initializes SANE and must be called before any other function.
    • sane_get_devices(): Returns a list of available image-acquisition devices.
    • sane_open(): Opens a named image-acquisition device and returns a handle that must be passed to other API functions used to communicate with the device.
    • sane_get_option_descriptor(): Returns an option descriptor for a specific option (a device parameter). Options describe device controls (such as scan resolution) in a user-interface-independent way and control nearly all aspects of device operation. Option number 0 returns the number of available options.
    • sane_control_option(): Sets/gets the value of a specified option, for the device indicated by a device handle. For example, this function would be used to set a brightness control to 50 percent.
    • sane_start(): Initiates image acquisition from a specific device, as indicated by a device handle.
    • sane_get_parameters(): Returns information on scan parameters for the device indicated by a device handle. Scan parameters include pixels per line and bytes per line.
    • sane_set_io_mode(): Enables either blocking or non-blocking I/O for the device indicated by a device handle. This function can be called only after making a call tosane_start().
    • sane_get_select_fd(): Obtains a platform-specific file descriptor from a device handle. This function can only be called after a call has been made tosane_start().
    • sane_read(): Reads image data from an open image-acquisition device, as indicated by a specific device handle. This function can only be called after a call has been made tosane_start().
    • sane_cancel(): Cancels the current operation for the device that is indicated by a device handle.
    • sane_close(): Closes the open image-acquisition device indicated by a specific device handle.
    • sane_exit(): Terminates an application's communication with SANE. The application must callsane_init() before it can communicate once more with SANE.
    • sane_strstatus(): Translates a SANE status code into a printable string. Examples of status codes includeSANE_STATUS_DEVICE_BUSY,SANE_STATUS_IO_ERROR, andSANE_STATUS_CANCELLED.

    An application calls sane_init() to begin interacting with SANE. The application next typically callssane_get_devices() to obtain a list of accessible devices. A device will be picked from this list and its name passed to sane_open() to open the device. Once the device is open, its controls can be set up or queried. This occurs in a loop, where each iteration invokessane_get_option_descriptor() to obtain a control's descriptor, followed by sane_control_option() to query or set up the control.

    Device setup is followed by image acquisition. This task begins with a call to sane_start(), and continues with a loop where sane_get_parameters() and thensane_read() are invoked. Image acquisition continues until sane_read() returns end-of-file, or the user chooses to terminate image acquisition (assuming the application allows image acquisition to be cancelled), whereby the application invokes sane_cancel(). Following image acquisition,sane_close() is invoked and the open device is closed.sane_exit() is then called to break the application's connection with the SANE back end.

    SANE Network Protocol

    SANE was designed to facilitate network access to image-acquisition devices. Most SANE implementations support anet pseudo-driver and a corresponding network daemon to access those devices through a network connection.

    SANE provides a network protocol designed to enable the efficient transmission of images (because of low encoding overhead), provide efficient access to option descriptors on the client side (because this is a common operation), and be simple and easy to implement on any host architecture and in any programming language.

    The protocol provides an encoding scheme for primitive data types and type constructors. For example, a SANE_Intvalue is encoded as four 8-bit bytes, ordered from most-significant to least-significant. Also, arrays are encoded by a word denoting the array's length followed by the array values.

    The protocol is based on remote procedure calls (RPCs). All activity is initiated by the client, and the server is restricted to answering client requests. For example, theSANE_NET_INIT RPC establishes a connection to a particular SANE network daemon. The RPC passes a version code and a username to the daemon, and receives a status value and another version code in reply.

    A SANE-Based API for Java

    After exploring SANE, creating a Java API that accesses SANE back ends seems to be the next logical step. Unfortunately, my lack of access to either a Unix or a Linux SANE implementation forces me to seek an alternative. That alternative is to explore an existing Java-based SANE API. For this article, I have chosen JSane.

    JSane is an open source project whose Java classes communicate directly with a SANE daemon (and adhere to the SANE daemon's network protocol). Consult the Resourcessection for a link to JSane's website.

    JSane's classes are organized in two provides exception classes that represent SANE statuses, and a foundation, whose classes emulate the pseudo-driver net.

    To communicate with SANE-based image-acquisition devices, a JSane-enabled Java program must first connect to the host on which the SANE daemon runs. This can be accomplished by creating aJSane_Net_Connection object. That object's constructor requires the SANE daemon's host name and the port on which the daemon runs (typically port 6566). If a connection cannot be established, the constructor throws either of two The code fragment below shows how to make the connection.

    JSane_Net_Connection con = new JSane_Net_Connection ("host", 6566); 

    Once a connection has been established, calls to the aforementioned class's getNumberDevices() andgetDevice() methods make it possible to enumerate all image-acquisition devices recognized by the SANE daemon.getDevice() returns an object whose class subclasses the abstract JSane_Base_Device class.

    A device is opened by calling JSane_Base_Device'sopen() method, and closed by callingclose(). Prior to closing the device, a list of option descriptors can be obtained by callinggetNumberOptions() and getOption().

    To show you how to enumerate devices and their options via JSane, I've written an enumeration program that is similar to a sample program that comes with JSane. If you have access to a SANE daemon, I urge you to compile and execute the code, and view the list. The enumeration program's Java source code appears below:

    // // Enumerate devices and device options. import; import*; import*; public class EnumDevOpt { public static void main (String [] args) { try { // Attempt to connect to the SANE daemon // running on host host and port number // 6566. JSane_Net_Connection con; con = new JSane_Net_Connection ("host", 6566); // For each device accessible through // the SANE daemon ... for (int devNum = 0; devNum < con.getNumberDevices (); devNum++) { // Obtain the device's descriptor. JSane_Base_Device dev; dev = con.getDevice (devNum); // If a descriptor was returned // (devNum is in range) ... if (dev != null) { // Output device information. System.out.println("Device = " + dev); // Attempt to open the device. (); // Obtain a count of options // supported by the device. int nopt; nopt = dev.getNumberOptions (); // For each option, return and // output the option's // descriptor. for (int i = 0; i < nopt; i++) System.out.println (dev.getOption (i)); // Close the device. dev.close (); } else System.out.println ("Unable " + "to get " + "device " + devNum); } // Drop the connection to the SANE // daemon. con.net_exit (); } catch (IOException e) { System.out.println (e); } catch (JSane_Exception e) { System.out.println (e); } } } 

    The sample program on which I based EnumDevOptcontains an oddity that needs an explanation. The sample program drops its connection to the SANE daemon by invokingJSane_Net_Connection's exit() method. But according to JSane's Java documentation, exit() does not exist. Instead, there is a net_exit() method, which I've substituted in

    So now we know how to enumerate devices and their options via JSane. But how do we acquire images? Using JSane to acquire an image is very simple: only two methods (getFrame() andgetImage()) need to be called, as the following code fragment reveals:

    // Attempt to open a device. (); // Manipulate options. // Perform a scan. JSane_Base_Frame frame = dev.getFrame (); // Retrieve the image. BufferedImage image = frame.getImage (); // Close the device. dev.close (); 

    I recommend exploring the classes and interfaces in JSane's two packages. That way, you'll get comfortable with this API and can begin experimenting with JSane on your own.

    The Need for Two Specifications

    TWAIN has been around since 1992. It's supported on the Apple Macintosh and on several versions of Microsoft Windows. Furthermore, IBM's OS/2 supports TWAIN. But there is no support (at least none that I can find) for Unix and Linux.

    Why doesn't TWAIN support Unix and Linux? The TWAIN Working Group's "Expanding TWAIN's Portability to Include Unix" white paper (check out the Resources section for a link to this white paper) provides an answer. According to that white paper, which focuses more on Unix than Linux, the fact that TWAIN drivers require an attachment to the application's message loop, and the fact that TWAIN vendors must provide custom dialog boxes that expose all of the features of their devices, cause problems for Unix.

    Unlike Macintosh and Windows, the Unix operating system can run with different GUIs. Furthermore, a GUI isn't essential to running Unix. How do we reconcile TWAIN's emphasis on GUI support at the driver level and non-standard or non-existent GUIs on Unix machines? This is the crux of the problem.

    SANE came into existence to overcome this problem. By separating device controls from their representation in a GUI, SANE applications can be created to run in a command-line or a GUI context. Another benefit (previously shown in Figure 2) brought out by SANE's separation of device controls from their GUI representations: network transparent access to image-acquisition devices. Unlike TWAIN, SANE supports remote access to image-acquisition devices over the network. Imagine manipulating a web camera located thousands of miles from your desktop. The internet and SANE make this possible.

    Merging SANE with TWAIN

    Some image-acquisition devices only support TWAIN, and other image-acquisition devices exclusively support SANE. Out of the box, you cannot use TWAIN devices in a SANE context (and vice versa). To overcome this problem, we need to first consider merging SANE with TWAIN into a unified specification.

    One approach to achieving SANE-TWAIN unification was put forward by the TWAIN Working Group in "Expanding TWAIN's Portability to Include Unix." According to that white paper, it's desirable to achieve synergy between the SANE and TWAIN specifications, by using SANE as the back end (letting TWAIN avoid reinventing solutions to fundamental device communication problems) and by using TWAIN as the front end of a unified specification.

    To achieve synergy between SANE and TWAIN, the white paper recommends changing the architecture of existing TWAIN drivers, which results in hybrid TWAIN-SANE drivers. Figure 3 presents the current architecture of a TWAIN driver (which I copied from the white paper).

    Figure 3
    Figure 3. Current TWAIN driver architecture

    Modifications include moving Capability Negotiation (Programmatic Controls) to the SANE side of a hybrid TWAIN-SANE driver, and implementing a mechanism that passes all capabilities operations from the TWAIN side to the SANE side of the hybrid driver. Figure 4 presents the proposed hybrid driver architecture (also copied from the white paper).

    Figure 4
    Figure 4. Hybrid TWAIN-SANE driver architecture

    The white paper goes on to propose writing a generic TWAIN-on-SANE driver that implements these modifications. Although the TWAIN Working Group has not released a generic TWAIN-on-SANE driver in the five years since they wrote the white paper, I've discovered an interesting Windows project that seems to accomplish their objective: SaneTwain. This project bridges the SANE and TWAIN worlds by providing a SANE-TWAIN data source and software to connect to a SANE server. Check out the Resources section for a link to the SaneTwain website.

    So what has merging SANE with TWAIN got to do with Java? A future Java version will probably support image-acquisition devices in its libraries, perhaps as part of the Image I/O API. This future support will most likely embrace both TWAIN and SANE drivers (because of their ubiquity). Furthermore, it will be necessary to develop a standard API, perhaps modelled after SANE or TWAIN. This task might be handled by introducing separate subsystems for TWAIN and SANE. As this seems rather complicated, it might be advantageous to think about merging SANE with TWAIN and providing a single subsystem for the result.


    SANE is an API and image-acquisition specification that primarily targets Unix and Linux. Unlike TWAIN, SANE is GUI-neutral, which makes SANE ideal for use in command-line-driven Unix environments. Another advantage over TWAIN: SANE enables network transparent access to image-acquisition devices located across networks.

    We've reached the end of this series. You should have a basic understanding of TWAIN and SANE, along with an understanding of the Java APIs presented in this series' articles. Hopefully, that knowledge will help you when you find yourself needing to integrate support for image acquisition into your Java programs.

    Where do we go from here? I believe that Java needs a unified framework that makes it possible for Java programs to transparently obtain images from either SANE or TWAIN drivers. Would you be interested in working with me on an open source project that just might make this framework a reality? Let me know.

    I have some homework for you to accomplish:

    • If you have access to a SANE daemon, download JSane and try out this article's EnumDevOpt program.
    • The first part of this series presented a JTwainDemo application. Convert that application's source code to equivalent applet source code. Test your JTwainDemo applet using theappletviewer tool found in the J2SE 5.0 SDK. Don't worry about making this applet work within a real web browser.

    Next time, Java Tech presents language lessons that focus on the problems of inheritance, interfaces versus abstract classes, when assertions should not be used, the usefulness of covariant return types, and more.


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

      A simple capability that you might consider adding (for a scanner device) is scan orientation (portrait or landscape). UseICAP_ORIENTATION in a pair of C++ functions that get and set the current scan orientation.