Skip to main content

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

{cs.r.title}









Contents
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
Conclusion
Resources
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 libsane.so--a symbolic
link (symlink, for short) to a driver back end that controls a
specific image-acquisition device. When an application communicates
with libsane.so, it's actually communicating with
whatever driver is represented by libsane.so.

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-drivers
dll and net. Those pseudo-drivers talk to
other SANE drivers instead of physical devices: dll
talks to other SANE drivers on the same machine and
net talks to other SANE drivers across a network.
Figure 2 presents an example of a SANE driver hierarchy involving
dll 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's
libsane.so is a symlink to the dll
pseudo-driver. That pseudo-driver uses dynamically linked libraries
to access both the mustek scanner driver (which
controls the local mustek scanner) and the net
pseudo-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,
libsane.so could be a symlink to the net
pseudo-driver, or a real driver (such as the mustek
scanner 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.

SANE API

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, and
SANE_String, to complex types, such as
SANE_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 to
    sane_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 to
    sane_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 to
    sane_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 call
    sane_init() before it can communicate once more with
    SANE.
  • sane_strstatus(): Translates a SANE status code
    into a printable string. Examples of status codes include
    SANE_STATUS_DEVICE_BUSY,
    SANE_STATUS_IO_ERROR, and
    SANE_STATUS_CANCELLED.

An application calls sane_init() to begin
interacting with SANE. The application next typically calls
sane_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 invokes
sane_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 then
sane_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 a
net 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_Int
value 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, the
SANE_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 Resources
section for a link to JSane's website.

JSane's classes are organized in two packages:
uk.org.jsane.JSane_Base and
uk.org.jsane.JSane_Net.
uk.org.jsane.JSane_Base provides exception classes
that represent SANE statuses, and a foundation for
uk.org.jsane.JSane_Net, 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 a
JSane_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 exceptions:
java.io.IOException or
java.net.UnknownHostException. The code fragment below
shows how to make the connection.

[prettify]JSane_Net_Connection con = new JSane_Net_Connection ("host", 6566);
[/prettify]

Once a connection has been established, calls to the
aforementioned class's getNumberDevices() and
getDevice() 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's
open() method, and closed by calling
close(). Prior to closing the device, a list of option
descriptors can be obtained by calling
getNumberOptions() 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:

[prettify]// EnumDevOpt.java

// Enumerate devices and device options.

import java.io.IOException;

import uk.org.jsane.JSane_Base.*;
import uk.org.jsane.JSane_Net.*;

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.

                   dev.open ();

                   // 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);
      }
   }
}
[/prettify]

The sample program on which I based EnumDevOpt
contains an oddity that needs an explanation. The sample program
drops its connection to the SANE daemon by invoking
JSane_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 EnumDevOpts.java.

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() and
getImage()) need to be called, as the following code
fragment reveals:

[prettify]// Attempt to open a device.

dev.open ();

// Manipulate options.

// Perform a scan.

JSane_Base_Frame frame = dev.getFrame ();

// Retrieve the image.

BufferedImage image = frame.getImage ();

// Close the device.

dev.close ();
[/prettify]

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.

Conclusion

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

Resources

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). Use
    ICAP_ORIENTATION in a pair of C++ functions that get
    and set the current scan orientation.

width="1" height="1" border="0" alt=" " />
Related Topics >> Programming   |