Last time,
I introduced a three-part series on TWAIN and SANE.
Part one explored TWAIN in terms of the big picture and necessary details,
presented the JTwain API library that makes TWAIN available to Java,
and revealed the JTwainDemo application that interacts with JTwain to
obtain images from scanners and other image-acquisition devices (and present
them to the user).
Part one's JTwain API library has some problems. I begin the second part of this
series by pointing out these problems and introducing a new JTwain API library
that overcomes them. I next explore data source capabilities--a concept I
hinted at in part one--and introduce that portion of the new JTwain API
library dedicated to capabilities. I wrap up part two by introducing an improved
JTwainDemo that demonstrates the new JTwain.
Note: Unlike part one, this article does not present build instructions (for brevity).
However, you'll find a make file for the library's DLL in the sample code file
associated with this article (see Resources).
The New JTwain
Several design decisions are responsible for the simplicity of part one's JTwain
API library: keep the library small (two native methods), allow only a single
image to be transferred in a session, and depend on Windows dialog boxes for
the user interface when acquiring an image or selecting a default data source.
Ironically, these same design decisions also introduce problems, necessitating
a new JTwain API library:
Inefficient implementation: Each acquire() method call has
to open the data source manager, open the default data source, display a data-source-specific dialog box, obtain one image, close the default data source,
and close the data source manager. Repeatedly opening and closing the data
source manager and the same default data source (unless changed via a call to
selectSourceAsDefault()) is not efficient. I feel that a better
design would place these individual operations into their own API methods.
Single-image transfer: The acquire() method only returns a
single image. This rules out acquiring the multiple images that are made available
by automatic document feeders. A better approach: acquire() needs
to return an array of images.
Windows dialog box dependence:selectSourceAsDefault() and
acquire() present Windows dialog boxes to the user. This reliance
on Windows detracts from whatever non-Windows look and feel we might choose for
our JTwain applications. It would be better to remove acquire()'s
dialog box, do away with selectSourceAsDefault(), and provide the
necessary JTwain methods that help to construct Java-based "Select Source" and
data-source-specific dialog boxes.
I've created a new JTwain API library that solves the aforementioned problems.
Multiple native API methods (including a streamlined acquire())
result in a more efficient implementation. The acquire() method
now returns an array of images. And the Windows dialog boxes are no more: I've
removed selectSourceAsDefault() (responsible for "Select Source"),
and the remodelled acquire() method doesn't display a data-source-specific Windows dialog box. Except for three methods (that I introduce
later), all methods in the new API are described below:
public static native Image [] acquire() acquires an array of
images from the currently open data source. The length of the array (which is
allocated by JTwain) determines the number of images that were acquired.
This method will typically return a single image, unless certain capabilities
(a concept I discuss later) in regard to an automatic document feeder are set
to appropriate values. I do not discuss automatic document feeders and how to
set them up, because I don't have access to those devices. To learn more about
TWAIN's support for automatic document feeders, I recommend studying the TWAIN
specification (see Resources).
public static native void closeDS() closes the currently open
data source.
public static native void closeDSM() closes the data source
manager.
public static native int getCC(int dest) returns the condition
code from the most recent operation associated with either the data source
manager or the currently open data source. If dest is 0, the
condition code is returned from the data source manager. If dest
is non-zero, the condition code is returned from the currently open data
source.
JTwain declares several constants starting with CC_
that represent all condition codes. If you need to specify a condition code at
the source code level, use one of these constants instead of the actual value.
public static native String getDefaultDS() returns the name of
the default data source. An application should acquire images from this data
source if the user has not chosen to select a data source. Also, this name
should be highlighted in a Java-based "Select Source" dialog box the first
time that dialog box is displayed.
public static native String getFirstDS() returns the name of the
first data source. When enumerating all data source names, this method must be
called before getNextDS().
public static native String getNextDS() returns the name of the
next data source, or an empty string if there are no more data sources. When
enumerating all data source names, call getNextDS() after calling
getFirstDS().
public static native int getRC() returns the return code from the
most recent operation.
JTwain declares several constants starting with RC_
that represent all return codes. If you need to specify a return code at the
source code level, use one of these constants instead of the actual value.
public static native void openDS(String srcName) opens the data
source identified by srcName. The open data source is referred to
as the currently open data source.
public static native void openDSM() opens the data source manager.
Except for getRC(), which throws no exceptions, each of the above
methods is capable of throwing JTwainException objects.
Note: The methods getRC() and getCC() return values that
identify failures with TWAIN. They do not return values that identify non-TWAIN
failures. The only places where a non-TWAIN failure might occur are located in the
acquire() method: an image transfer used compression or did not
produce an 8-bit (grayscale) or 24-bit (RGB) image, the transferred image could
not be added to an internal vector data structure (because of limited memory),
or the image could not be converted from Windows' device-independent bitmap
format to a Java-based image (perhaps a Java class or method couldn't be found,
memory was exhausted, or a method could not be called).
To demonstrate some of the methods in the new JTwain API, I've written a short
program that enumerates all data source names. The enumeration technique could
be inserted into dialog box code to create a Java-based "Select Source" dialog
box.
// EnumSources.java
import net.javajeff.jtwain.*;
public class EnumSources
{
public static void main (String [] args)
{
// Initialize JTwain.
if (!JTwain.init ())
{
System.out.println ("TWAIN unsupported");
return;
}
try
{
// Open data source manager.
JTwain.openDSM ();
try
{
// Get name of first data source.
String name = JTwain.getFirstDS ();
do
{
// Output data source name.
System.out.println (name);
// Get name of next data source.
name = JTwain.getNextDS ();
}
while (!name.equals (""));
}
catch (JTwainException e)
{
// Output return code and condition
// code from data source manager
// (reason for failure).
System.out.println (e + ", RC = " +
JTwain.getRC () +
", CC = " +
JTwain.getCC (0));
}
// Close data source manager.
JTwain.closeDSM ();
}
catch (JTwainException e)
{
// Output return code (reason for
// failure).
System.out.println (e + ", RC = " +
JTwain.getRC ());
}
}
}
Because EnumSources.java is commented, I won't bother to describe
the source code. Compile that code and run it to obtain a list of data source
names on your platform. You will need those names to play with the program in
the next section. For example, when I execute java EnumSources on
my platform, I obtain the following list of data source names:
HP PrecisionScan LT 3.0
TWAIN_32 Sample Source
A Capable JTwain
Image-acquisition devices support varying features. For example, some scanners
support automatic document feeders to automate the scanning of multiple
documents, and many digital cameras offer a camera preview mode (not to
mention information on battery life). Support for color images and information
about the author of an image are other examples. Collectively, these and other
features are known as capabilities.
A data source "knows" what capabilities are supported by the image-acquisition
hardware that it represents, and displays the current settings of some (if not
all) of these capabilities to the user in a data-source-specific dialog box.
The user then has the opportunity to modify these settings (such as choosing
whether or not to support an automatic document feeder, or changing the paper
size) prior to acquiring an image. Part one's Figure 3 revealed a
Windows dialog box (on my platform) presenting the current settings of a few
capabilities for the TWAIN_32 Sample Source data source.
Because the responsibility for presenting data-source-specific dialog boxes is
no longer with JTwain, but has migrated to JTwain applications, it's necessary
for the application to determine capability settings and allow users to modify
those settings. To assist the application, I've introduced three methods into
the JTwain API that govern access to a single pixel type capability.
This determines the kind of pixel data that a data source can acquire, such
as black-and-white images, grayscale images, and RGB (red/green/blue) images:
public static native int getPixelType() returns the currently
open data source's current pixel type. Compare that return value with one of
JTwain's PT_ constants--this makes your source
code more readable.
public static native int [] getPixelTypes() returns an array
containing the currently open data source's supported pixel types. Compare the
array contents with JTwain's PT_ constants.
public static native void setPixelType(int type) sets the
currently open data source's current pixel type. Instead of passing an integer
value, pass one of JTwain's PT_ constants.
Like the previous methods, each method above is capable of throwing objects of
the type JTwainException. But each method can also throw an object of
the type UnsupportedCapabilityException, if the data source that's
currently open does not support the pixel type capability.
Note: Each time a data source is opened via openDS(), the current
values of all supported capabilities are reset to their defaults. As a result,
you must change the current values of whatever capabilities you are supporting
before calling acquire().
I have written a short program that demonstrates the three pixel type methods.
You could place the code that gets this capability's current value and list of
supported values into a Java-based, data-source-specific dialog box.
// PixelTypeDemo.java
import net.javajeff.jtwain.*;
public class PixelTypeDemo
{
public static void main (String [] args)
{
// Ensure exactly one command-line argument is
// specified.
if (args.length != 1)
{
System.out.println ("usage: java " +
"PixelTypeDemo " +
"srcname");
return;
}
// Initialize JTwain.
if (!JTwain.init ())
{
System.out.println ("TWAIN unsupported");
return;
}
try
{
// Open data source manager.
JTwain.openDSM ();
try
{
// Open specified data source.
JTwain.openDS (args [0]);
try
{
// Execute pixel type demo.
doDemo ();
}
catch (JTwainException e)
{
// Output data source CC code.
reportFailure (e.getMessage (),
1);
}
// Close currently open data source.
JTwain.closeDS ();
}
catch (JTwainException e)
{
// Output data source manager CC code.
reportFailure (e.getMessage (), 0);
}
// Close data source manager.
JTwain.closeDSM ();
}
catch (JTwainException e)
{
// Do not output a CC code.
reportFailure (e.getMessage (), -1);
}
}
// ==========================================
// Execute the pixel type demo. This method
// catches UnsupportedCapabilityException but
// throws JTwainException.
// ==========================================
static void doDemo () throws JTwainException
{
try
{
// Obtain array of supported pixel types.
int [] pixTypes = JTwain.getPixelTypes ();
// Output array's contents.
System.out.println ("Supported pixel types:");
System.out.println ();
for (int i = 0; i < pixTypes.length; i++)
outputPixelTypeName (pixTypes [i]);
// Obtain current pixel type.
int pixType = JTwain.getPixelType ();
// Output current pixel type.
System.out.println ();
System.out.print ("Current pixel type: ");
outputPixelTypeName (pixType);
System.out.println ();
// Change current pixel type.
if (pixType == JTwain.PT_GRAY)
{
System.out.println ("Set to RGB");
JTwain.setPixelType (JTwain.PT_RGB);
}
else
{
System.out.println ("Set to GRAY");
JTwain.setPixelType (JTwain.PT_GRAY);
}
// Obtain current pixel type.
pixType = JTwain.getPixelType ();
// Output current pixel type.
System.out.println ();
System.out.print ("Current pixel type: ");
outputPixelTypeName (pixType);
}
catch (UnsupportedCapabilityException e)
{
System.out.println ("No ICAP_PIXELTYPE");
}
}
// ==============================================
// Output a pixel type integer as a string value.
// ==============================================
static void outputPixelTypeName (int pixType)
{
switch (pixType)
{
case JTwain.PT_BW:
System.out.println ("Black & White");
break;
case JTwain.PT_GRAY:
System.out.println ("Grayscale");
break;
case JTwain.PT_RGB:
System.out.println ("Red/Green/Blue");
break;
case JTwain.PT_PALETTE:
System.out.println ("Palette");
break;
case JTwain.PT_CMY:
System.out.println ("CMY");
break;
case JTwain.PT_CMYK:
System.out.println ("CMYK");
break;
case JTwain.PT_YUV:
System.out.println ("YUV");
break;
case JTwain.PT_YUVK:
System.out.println ("YUVK");
break;
case JTwain.PT_CIEXYZ:
System.out.println ("CIEXYZ");
break;
default:
System.out.println ("Unknown");
}
}
// ==============================================
// Output a failure message, a return code, and a
// condition code based on dest.
//
// dest < 0 -- don't output condition code
// dest = 0 -- output data source manager
// condition code
// dest > 0 -- output data source condition code
// ==============================================
static void reportFailure (String msg, int dest)
{
System.out.print (msg + ", RC = " +
JTwain.getRC ());
if (dest >= 0)
try
{
System.out.print (", CC = " +
JTwain.getCC (dest));
}
catch (JTwainException e)
{
}
}
}
Once again, I won't bother to describe the source code, because of the variety
of comments. Compile that code and run it on your platform. For example, when
I execute java PixelTypeDemo "TWAIN_32 Sample Source" (the quotes
must be present because of embedded spaces in the data source name), I observe
the following output:
Supported pixel types:
Black & White
Grayscale
Red/Green/Blue
Current pixel type: Grayscale
Set to RGB
Current pixel type: Red/Green/Blue
Capabilities Quick Study
At some point, you'll most likely want to add your own capabilities methods to
JTwain. Before doing that, you will need to acquire the appropriate knowledge.
Although the TWAIN specification provides extensive capabilities coverage, you
might find wading through all of that information somewhat overwhelming. To help
you quickly come up to speed on implementing your own capabilities in JTwain,
I recommend that you read this section. It introduces you to the concepts of
capability constants, operation triplets needed for getting/setting capability
values, and containers for use in those operations.
The twain.h header file (see Resources) offers
an assortment of constants that describe capabilities recognized by TWAIN.
Constants organize into three main categories: constants beginning with the
CAP_ prefix identify capabilities common to all kinds of
image-acquisition devices, constants beginning with the ACAP_
prefix identify audio-oriented capabilities, and constants that begin with the
ICAP_ prefix identify image-oriented capabilities. As an example,
the constant ICAP_PIXELTYPE identifies the pixel type capability.
TWAIN presents six capability-oriented operation triplets that are passed to
DSM_Entry() when working with capabilities. I only use three of
those operation triplets in JTwain:
I use the operation triplet of DG_CONTROL, DAT_CAPABILITY, and MSG_GET to return a range of supported values for pixel type.
I use the operation triplet of DG_CONTROL, DAT_CAPABILITY, and MSG_GETCURRENT to return the pixel type's current value.
I use operation triplet of DG_CONTROL, DAT_CAPABILITY, and MSG_SET to change the pixel type's current value.
When an application wants to change a capability's current value, it allocates
memory for a specific TWAIN container (data structure), populates that
container's fields with appropriate values, and passes that container to TWAIN
(via the final operation triplet in the list above). When an application wants
to obtain a capability's current value, or obtain a list of supported values,
the data source allocates the container's memory. Regardless of the allocation
source, the application is responsible for releasing that memory before
terminating. For an example of a container and how the memory allocation works
on the application side, examine the C++ code fragment below (which refers to
various constants and types located in twain.h). That source code
changes the current value of the pixel type capability:
TW_CAPABILITY cap;
cap.Cap = ICAP_PIXELTYPE;
cap.ConType = TWON_ONEVALUE;
// Allocate memory for the container.
cap.hContainer =
GlobalAlloc (GHND, sizeof(TW_ONEVALUE));
if (cap.hContainer == 0)
{
// Handle lack of memory ...
}
// Lock the container memory.
TW_ONEVALUE *pTWOneValue =
(TW_ONEVALUE *) GlobalLock (cap.hContainer);
// Populate the container's fields.
pTWOneValue->ItemType = TWTY_UINT16;
pTWOneValue->Item = pixType;
// Unlock the container memory.
GlobalUnlock (cap.hContainer);
// Invoke operation by calling DSM_Entry().
g_rc = (*g_pDSM_Entry) (&g_AppID,
&g_SrcID,
DG_CONTROL,
DAT_CAPABILITY,
MSG_SET,
(TW_MEMREF) &cap);
// Deal with success or failure, indicated by g_rc.
// Free the container's memory.
GlobalFree (cap.hContainer);
The code fragment above introduces a TW_CAPABILITY data structure
that describes a capability to TWAIN. That data structure's Cap
field is assigned the ICAP_PIXELTYPE constant, identifying the
pixel type image capability. The ConType field is assigned the
TWON_ONEVALUE constant that identifies the kind of container
holding the new pixel type value as a single-value container. Memory for that
container must then be allocated. Under Windows, TWAIN requires that the
application call GlobalAlloc() to perform the allocation. Unlike
TWON_ONEVALUE, TW_ONEVALUE provides the correct size
to GlobalAlloc().
Because GlobalAlloc() returns a memory handle, a call is made to
GlobalLock() to lock the memory and return a pointer to that
memory. Using that pointer, the container's fields are then populated: the
ItemType field is assigned the TWTY_UINT16 constant,
identifying the type of the container's value as a 16-bit unsigned integer,
and the Item field is assigned the new value (stored in the variable
pixType) for the pixel type capability. Following the assignment,
the memory is unlocked by calling GlobalUnlock().
DSM_Entry() is invoked with the appropriate operation triplet for
changing the pixel type capability's value. The address of the capability data
structure is passed as the final argument to this function. Whether or not the
operation succeeds, GlobalFree() is invoked to release the memory
previously allocated for the container.
In addition to TWON_ONEVALUE and TW_ONEVALUE, JTwain
uses the TWON_ENUMERATION/TW_ENUMERATION container
to hold a list of supported pixel type values. As you add new capabilities to
JTwain, you might also work with TWON_ARRAY/TW_ARRAY
and TWON_RANGE/TW_RANGE. The TWAIN specification has
much more to say about these containers (and other TWAIN data structures).
The Revised JTwainDemo
I've created a revised JTwainDemo application that exercises nearly the entire
suite of methods in the new JTwain API. As before, JTwainDemo consists of
the source files ImageArea.java and JTwainDemo.java.
Compile both source files and execute java JTwainDemo to run that
application.
From the File menu, choose the Select Source... menu item. In response, you'll
see an all-Java "Select Source" dialog box that lists the names of all data
sources on your platform. As shown in Figure 1, only two data sources exist on
my platform.
Figure 1: "TWAIN_32 Sample Source" is highlighted in the all-Java "Select
Source" dialog box
Choose Acquire... from the File menu and study the "Config Source" dialog box.
That dialog box lists all values supported by the pixel type capability of the
currently open data source. Furthermore, one of those values should be
highlighted, indicating it's the current value of that capability. As Figure 2
reveals, "Config Source" displays a list of three pixel types (with one of the
pixel types highlighted) when "TWAIN_32 Sample Source" is the currently
open data source.
Figure 2: The all-Java "Config Source" dialog box highlights Grayscale
Now that you have seen JTwainDemo's GUI enhancements, you'll probably want to
study the application's source code. The following code fragment provides you
with a glimpse of that source code.
// Set any selected pixel type.
JTwain.setPixelType (cs.getPixType ());
// Acquire one or more images. Only one image
// should be returned because no capabilities
// involving an automatic document feeder have
// been set.
Image [] im = JTwain.acquire ();
// Update ImageArea panel with the first new
// image, and adjust the scrollbars.
ia.setImage (im [0]);
The code fragment is taken from the action listener attached to the Acquire...
menu item. After displaying the "Config Source" dialog box, opening the data
source manager, and opening the selected data source, the listener executes
the code fragment.
The code fragment's first task is to set the open data source's pixel type. An
image is then acquired from that data source. Usually, one image will be
returned in the array (at position 0) unless an automatic document feeder is
in use and various related capabilities have been appropriately initialized.
Once the image has been acquired, it's displayed in the ImageArea
panel.
Note: When acquiring an image, the sequence of JTwain API calls, from
openDSM() through closeDSM(), must be invoked on the
same thread. Doing that will prevent the "lockup" problem with the Win32
GetMessage() function that I discussed in part one. Also, although
you can execute the image-acquisition sequence on the event-handling thread,
your application GUIs will be more responsive by relegating that sequence to a
background thread.
Conclusion
JTwain has grown up. The new version of this API library solves three problems
present in the previous JTwain. You've been introduced to the expanded API and
have learned about the concept of capabilities, as we dug deeper into TWAIN.
You've also been introduced to a revised JTwainDemo application that exercises
almost the entire suite of methods in the new API.
This is the end of our TWAIN coverage. Although I would have loved to dig even
deeper into TWAIN (such as exploring more capabilities, discussing alternative
image transfer modes, showing how to build a custom data source, and so on),
this article is long enough. I believe you now have acquired sufficient
knowledge to continue exploring TWAIN on your own.
I have some homework for you to accomplish:
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.
Next time, Java Tech wraps up this series by exploring SANE.
The previous Java Tech article presented you with some
challenging homework on JTwain and JTwainDemo. Let's revisit that homework and
investigate solutions.
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:
Consult the 1 subdirectory of the answers directory in this
article's attached code file (see Resources).
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.
Consult the 2 subdirectory of the answers directory 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.
What else would you like the JTwain library to support?
Showing messages 1 through 35 of 35.
Acquire image directly from the new select source API
2008-04-19 04:15:52 kyser
[Reply | View]
Can someone help me find a away to acquire an image directly from the select source API using the new JTwain code by jeff
Acquire an image from Java-based "Select Source"
2008-04-18 15:33:38 kyser
[Reply | View]
Hello,
How can I display a data-source-specific dialog box and acquire an image, right when I press the ok button from Java-based "Select Source" API. I've been trying for hours but couldn't get it work. Please help
Testiong the dll I'm having this error: java.lang.UnsatisfiedLinkError on the "openDSM", I downloaded the latest dll from the author's website and the programs find it since the "JTwain.init" returns true. I'm using netbeans.5.5.
Solved.. it wasn't the dll, the compiled classes were not properly setup in Netbeans, I created the package in the project and compile from there and the error didn't appear again.
error in Virtual Machine
2006-10-11 00:01:27 seboss
[Reply | View]
Hi,
I've a JTwain version that include the following function :
JNIEXPORT jobject JNICALL
Java_transpo_system_jtwain_JTwain_acquireCfg
(JNIEnv *env, jclass clazz)
{
// Verify that the data source manager has not been opened.
if (g_hLib != 0)
{
throwJTE (env, "Data source manager already open (_acquire)");
return (jobject) 0; // Return value never reaches Java, but is needed
// to satisfy the compiler.
}
// Attempt to load TWAIN DLL.
if (!loadTWAIN ())
{
throwJTE (env, "Unable to load TWAIN (_acquire)");
return (jobject) 0;
}
// Create a static window whose handle is passed to DSM_Entry() when we
// open the data source manager.
// If window could not be created, throw exception. Because the exception
// is not actually thrown until execution returns to Java, we must return
// a value -- (jobject) 0 was chosen to represent Image null. This value
// will not be seen in the Java code because of the exception.
if (hwnd == 0)
{
unloadTWAIN ();
throwJTE (env, "Unable to create private window (_acquire)");
return (jobject) 0;
}
// Ensure that the default data source's dialog box does not disappear
// behind other windows, which can be very disconcerting to the user. We do
// that by making the hwnd -- created above and passed to DSM_Entry() below
// -- the handle of the topmost window.
// If data source manager could not be opened, throw exception. Because the
// exception is not actually thrown until execution returns to Java, we
// first exit current block to destroy previously-created window and return
// a value (which isn't seen in the Java code).
if (rc != TWRC_SUCCESS)
{
throwJTE (env, "Unable to open data source manager (_acquire)");
EXIT_CURRENT_BLOCK
}
cout << "block begin 2";
BLOCK_BEGIN(2)
// If default data source could not be opened, throw exception. Because the
// exception is not actually thrown until execution returns to Java, we
// first exit current block to close data source manager and destroy the
// previously-created window.
if (rc != TWRC_SUCCESS)
{
throwJTE (env, "Unable to open default data source (_acquire)");
EXIT_CURRENT_BLOCK
}
cout << "block begin 3";
BLOCK_BEGIN(3)
// Prepare to enable the default data source. Make sure to show the data
// source's own user interface (dialog box).
// If default data source could not be enabled, throw exception. Because
// the exception is not actually thrown until execution returns to Java, we
// first exit current block to close data source, close data source
// manager and destroy the previously-created window.
if (rc != TWRC_SUCCESS)
{
throwJTE (env, "Unable to enable default data source (_acquire)");
EXIT_CURRENT_BLOCK
}
// Begin the event-handling loop. Data transfer takes place in this loop.
MSG msg;
TW_EVENT event;
TW_PENDINGXFERS pxfers;
cout << "while (getmessage) begin";
while (GetMessage ((LPMSG) &msg, 0, 0, 0))
{
// Each window message must be forwarded to the default data source.
// If the default data source is requesting that the data source's
// dialog box be closed (user pressed Cancel), we must break out of the
// message loop.
if (event.TWMessage == MSG_CLOSEDSREQ)
break;
// If the default data source is requesting that it is ready to begin
// the data transfer, we must perform that transfer.
if (event.TWMessage == MSG_XFERREADY)
{
// Obtain information about the first image to be transferred.
this function, when using a HP scanner, crash the JVM with the error :
#
# An unexpected error has been detected by HotSpot Virtual Machine:
#
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x7c9211e0, pid=528, tid=588
#
# Java VM: Java HotSpot(TM) Client VM (1.5.0_08-b03 mixed mode, sharing)
# Problematic frame:
# C [ntdll.dll+0x111e0]
#
# An error report file with more information is saved as hs_err_pid528.log
#
# If you would like to submit a bug report, please visit:
# http://java.sun.com/webapps/bugreport/crash.jsp
#
but only after the scan. The system returns the image but the user interface of the scanner still visible and it's impossible to close it. (i need to go in windows process).
Is this problem can be link to the JTwain.cpp or to the scanner (it seems to run with a canon).
Thank for help.
error in Virtual Machine
2006-11-29 04:06:23 anbenham
[Reply | View]
I also have the same Problem with an HP scanner.
Did you find a solution for it?
Best Regards
error in Virtual Machine
2006-12-05 00:09:58 seboss
[Reply | View]
Unfortunately no...
I tought Mr Jeff could help me.
Sorry.
Problem compiling
2006-10-02 00:30:25 emarraro
[Reply | View]
Hi Mr. Jeff,
I successfully compiled your cpp code of example no 1.
But when i try to compile example no. 2 i get
Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland
jtwain.cpp:
Error E2356 jtwain.cpp 218: Type mismatch in redeclaration of '__stdcall
Java_net_javajeff_jtwain_JTwain_acquire(JNIEnv_ *,_jclass *)'
Error E2344 jtwain.h 16: Earlier declaration of '__stdcall
Java_net_javajeff_jtwain_JTwain_acquire(JNIEnv_ *,_jclass *)'
*** 2 errors in Compile ***
what am i doing wrong ?
Problem compiling
2006-10-02 14:08:53 javajeff
[Reply | View]
Hi emarraro,
Based on this information, I'm not sure what the problem is. If you want to ZIP up your source code and email it to jeff@javajeff.mb.ca, I'll see if I can find out what is causing the problem.
Hi, Does anyone have an idea on how to develop a scanner application that works with the ISIS driver, I am using a Fujitsu FI-4120C scanner which works fine with Jeff's code but it seems to scans the images very large by default unless you set the resolution. Moreover even if I you scan with a resolution of 75*75 which is the lowest with a fujitsu driver the scanned images if saved as tiff are around 1.5 mb. I guess that the same image if scanned and saved as tiff using an ISIS driver is only 50kb.
Any tips would be appreciated.
Thanks,
Suraj
RGB doesn't working properly
2006-09-20 22:23:06 dvernier
[Reply | View]
Hi Jeff,
I have the same issue as Jason, but what seems strange to me that it work fine with your Twain Toolkit!
Do you do something else then setting the pixelType before scanning?
Regards
Dominique
RGB doesn't working properly
2006-09-21 09:18:09 javajeff
[Reply | View]
Hi Dominique,
Subsequent to writing this article, I put together an improved JTwain library that merges the libraries from this article and its predecessor, adds new features, and fixes bugs. I recommend that you go to http://javajeff.mb.ca/sw/sw.html
and download this library. I believe it will solve your problems.
I recently installed a new computer with Windows XP SP2. I plan to enhance JTwain with new features and thoroughly test it under XP (a Web camera, and another scanner) a bit later this year (when time permits). Hopefully, this will make JTwain an even better product.
All the best.
Jeff
RGB doesn't working properly
2006-09-21 13:44:26 dvernier
[Reply | View]
thanks Jeff,
I just put the new dll and it works.
Great!
Regards
Dominique
How can i set the resolution ?
2006-06-28 00:22:38 seboss
[Reply | View]
Hello, it's a very good article but I have a question.
I want to know how to change the resolution of my image...
I understand i've to change the jtwain.dll with a new function, but i don't know how and i don't know the body of the function.
Somebody can help me ?
How can i set the resolution ?
2006-06-29 13:46:10 javajeff
[Reply | View]
Please check out the jtwain_preset() code in the Setting other ICAP properties in scanner...
2006-06-09 12:09:53 egrebal [Reply | View] reply further down this page. This method/function lets you specify a resolution argument; this may answer your question.
Jeff Friesen
How can i set the resolution ?
2006-06-30 01:14:32 seboss
[Reply | View]
Hello, thanks for your answer javajeff.
Inedeed, I saw egrebal's reply but when I try to add the given function in the jtwain.cpp, the dll creation fail.
The problem is that I don't know C++ and I don't know what is the problem.
Have you got an idea ?
Thanks.
How can i set the resolution ?
2006-07-01 16:12:40 javajeff
[Reply | View]
Please email me directly at jeff@gatewest.net. I'll send you modfiied jtwain.cpp, jtwain.dll, and JTwain.java files that support the new preset() function.
Jeff Friesen
How can i set the resolution ?
2007-01-17 04:21:31 zugurt
[Reply | View]
Seaboss, did you solve your problem? If so could you please send me the solution?
shocking_blue2000@yahoo.com
the article is very interesting but i have other questions
1.- the jtwain.dll and the jtwain.class manage every kind of scanner?
2.- if i want more functions i must modify by myself the jtwain.cpp?
3.- can i put the jtwain.dll, jtwain.class, jtwainexception.class and UnsupportedCapabilityException.class in a jar and use only this jar? these are all the files that i need?
4.- can i use your code in my commercial application?
5.- i look in the installation files of my scanner (hp) and i found some files called twain.dll, what are these files? can i use them?
Thanks for your kind words. Here are the answers to your questions:
> 1.- the jtwain.dll and the jtwain.class manage every kind of scanner?
Ideally, the DLL and class should work with every kind of scanner. However, I am aware that some scanners don't work properly with JTwain while other scanners have no problem. I assume that the problem is due to a scanner's data source DLL (I have learned that poorly-written data sources are usually the cause of many problems).
> 2.- if i want more functions i must modify by myself the jtwain.cpp?
You can contact me and I will see what I can do to help. I won't make any promises that I can implement your functionality, but I will consider it.
> 3.- can i put the jtwain.dll, jtwain.class, jtwainexception.class and UnsupportedCapabilityException.class in a jar and use only this jar? these are all the files that i need?
You cannot put a DLL into a Jar file. However, I believe the classfiles can be placed into a Jar, although I haven't tried to do so. The DLL is typically placed in the Windows directory -- such as c:\windows under Windows 98 SE. I believe these are all the files you need.
> 4.- can i use your code in my commercial application?
Feel free to use the code in a commerical application.
> 5.- i look in the installation files of my scanner (hp) and i found some files called twain.dll, what are these files? can i use them?
I explained these files in the first installment of this series. Here is the pertinent paragraph:
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.
I already used Jtwain and I need capabilty to change resolution.
Are there new sources ?
Somebody can help me ?
Thanks
wonzeu@voila.fr
Detect the end of the document
2005-09-22 13:48:26 pkhetiya
[Reply | View]
After few hiccups I was able to compile the code and create the dll files and was able to get the scanner piece working fine.
My basic purpose is to scan the insurance cards to get the image in my application and when I scan the card through my scanner (ScanShell800N) then even after the cards is out of the scanner, scanner keeps scanning (just black patch) and it takes almost 10-15 seconds.
How do I get the scanner to stop right after the card is out of the scanner?
Any help will be appriciated!
thanks, Pratik
Output as DIB
2005-06-29 06:05:24 jasonchewy
[Reply | View]
I've been working with your code for the twain stuff, and it's working great, just that for some reason, when I scan in color, the picture will appear slanted like this: / /. Also with grayscale, the whites on the page will appear in gray as well for some reason. So I've decided, why not let java do the job of decoding DIBs (since I work much better in java than c++). So the problem is, how do I do that? I see the twain handle for the DIB...and I kind of see how you get the BITMAPINFOHEADER....so let's say if I want to put all these info and make a bmp binary, but it into a byte array (or char array? not sure if c++ can do byte array or not, or is streaming possible?), and then put this byte array into the JObjectArray to output back to java. How do I do that?? I really want to do this myself, but I've been staring at your C++ code all day, and have no idea how to do it. Thanx for your support!!
Output as DIB
2005-06-29 17:31:56 javajeff
[Reply | View]
Hi Jason,
I'm sorry you are having trouble with the image conversion. I'd be happy to help you overcome that problem by creating a custom version of JTwain, with an alternative acquisition method that lets you specify a filename. When you invoke that method, the image would be stored in the file identified by the filename. You could then use Java's I/O streams capability to load the image and interpret the content as you see fit -- all from within Java.
Please contact me at jeff@gatewest.net and then I can get started on the custom JTwain.
I would like to post the code in this talkback but the talkback software doesn't properly format code. However, I'd be happy to email the code to anyone who contacts me at the above email address.
All the best.
Jeff
Setting other ICAP properties in scanner...
2005-05-25 19:55:40 camikiller
[Reply | View]
Hi Jeff,
I have read your article and i've found it very ilustrative...
The point is:
I'm trying to write a JNI method for the library to set the ICAP_FRAMES values, with the TWAIN toolkit i discover that ICAP_FRAMES property can be setted in my scanner... and i've setted step by step (Load -> Open Source -> Send )... and in the twacker it works... so i were expecting that it works through JNI.
// Populate container with appropriate data type and Physical Width
pTWOneValue->ItemType = TWTY_FRAME;
memcpy(&pTWOneValue->Item, &Frame, sizeof(TW_FRAME));
is not working, but my c++ skills are low... i don't know how to replace it....
Thanks
Setting other ICAP properties in scanner...
2005-05-25 22:02:50 javajeff
[Reply | View]
I would suggest replacing the memcpy() call with a simple assignment:
pTWOneValue->Item = (TW_UINT32) &Frame;
I recommend this approach because TW_FRAME contains four 32-bit fields and TW_ONEVALUE's Item field is only 32 bits. I believe the TWAIN implementers are looking for the address of a structure in this field.
If you still have a problem, it could be that Frame is a local variable. I can't remember off the top of my head but I think TWAIN might require you to dynamically allocate memory for Frame and assign that memory's address to Item. But try passing the address of the local Frame variable first.
By the way, I believe you can discard the "+ sizeof(TW_FRAME)" from the following line:
GlobalAlloc() returns a handle. You must call GlobalLock() to lock the memory identified by the handle before you can assign its address to pFrame. The correct code appears below:
Even after making the change, my implementation of setFrames() is still throwing an exception. It could be that none of my data sources support ICAP_FRAMES. However, it might be something else.
I recommend making the change above and trying again. Let me know what happens.
In the meantime, I will study the code and see if I can spot any other reason for the problem.
Jeff
Setting other ICAP properties in scanner...
2005-05-26 09:40:14 javajeff
[Reply | View]
As a followup to my previous message, I've discovered that you cannot set ICAP_FRAMES in twacker when TWAIN_32 Sample Source is the current data source. I say that because the initialization code for ICAP_FRAMES in the InitializeCaps() function (see dscaps.c) does not support the TWQC_SET message -- although the TWQC_GET, TWQC_GETDEFAULT, and TWQC_GETCURRENT messages are supported (so you can get the ICAP_FRAMES settings). My HP scanner also does not appear to support ICAP_FRAMES, so I'm assuming that the code I tested works -- though I cannot be 100% sure that it works since my data sources don't support ICAP_FRAMES. I look forward to hearing from you regarding your experiments.
Because this forum is awkward for sharing code, feel free to email me at jeff@gatewest.net. I'll be happy to send you my jtwain.cpp, jtwain.dll, and JTwain.java files (with the setFrames() method supported). Perhaps we can resolve this problem once I can send you the complete code.
Jeff
Setting other ICAP properties in scanner...
2006-06-09 12:09:53 egrebal
[Reply | View]
Here is a solution, starting from your code but arrange it a little bit.
Java Part...
Image im = null;
float left = 10.0f;
float right = 11.9f;
float top = 1.7f;
float bottom = 8.7f;
int resolution = 1000;
im = JTwain.preset(left,
right,
top,
bottom,
resolution);
JNI C++ Part... Excuse for the formating I don't realy have time to make it beautiful in that little box, but you can copy/paste into a C++ editor. By the way I was using Visual C++ 6.0, for Java it's Eclipse 3.1.2 but in Java it doesn't matter. The scanner I use is CanoScan 3000/3000F and like you with your example it was not working. Now it work perfectly. Take in note that you have a full example, because you have to change the PIXEL_TYPE before changing the ICAP_FRAMES. There is only two pixel type who's working TWUN_CENTIMETERS and TWUN_INCHES.
// If window could not be created, throw exception. Because the exception
// is not actually thrown until execution returns to Java, we must return
// a value -- (jobject) 0 was chosen to represent Image null. This value
// will not be seen in the Java code because of the exception.
if (hwnd == 0)
{
throwJTE (env, "Unable to create private window (preset)");
return (jobject) 0;
}
// Ensure that the default data source's dialog box does not disappear
// behind other windows, which can be very disconcerting to the user. We do
// that by making the hwnd -- created above and passed to DSM_Entry() below
// -- the handle of the topmost window.
// If data source manager could not be opened, throw exception. Because the
// exception is not actually thrown until execution returns to Java, we
// first exit current block to destroy previously-created window and return
// a value (which isn't seen in the Java code).
if (rc != TWRC_SUCCESS)
{
throwJTE (env, "Unable to open data source manager (preset)");
EXIT_CURRENT_BLOCK
}
// If default data source could not be opened, throw exception. Because the
// exception is not actually thrown until execution returns to Java, we
// first exit current block to close data source manager and destroy the
// previously-created window.
if (rc != TWRC_SUCCESS)
{
throwJTE (env, "Unable to open default data source (preset)");
EXIT_CURRENT_BLOCK
}
cout << "Data source enabled" << endl;
cout << "Try to set capabilities" << endl;
// Set the current value of the ICAP_FRAMES capability.
pval = (pTW_ONEVALUE)GlobalLock(cap.hContainer);
pval->ItemType = TWTY_UINT16;
pval->Item = TWUN_CENTIMETERS; // Only TWUN_CENTIMETERS and TWUN_INCHES working
// Application must allocate memory for the capability container.
GlobalUnlock(cap.hContainer);
status = TWRC_FAILURE;
status = (*g_pDSM_Entry) (&g_AppID,
&srcID,
DG_CONTROL,
DAT_CAPABILITY,
MSG_SET,
(TW_MEMREF) &cap);
GlobalFree((HANDLE)cap.hContainer);
if (status != TWRC_SUCCESS)
{
throwJTE (env, "Unable to set ICAP_UNITS (setPreset)");
} else {
cout << "ICAP_UNITS change accept" << endl;
}
// Populate container with appropriate data type and Physical Width
pTWOneValue->ItemType = TWTY_FRAME;
//pTWOneValue->Item = *((pTW_UINT32) pFrame);
memcpy(&pTWOneValue->Item, &Frame, sizeof(TW_FRAME));
// If default data source could not be enabled, throw exception. Because
// the exception is not actually thrown until execution returns to Java, we
// first exit current block to close data source, close data source
// manager and destroy the previously-created window.
if (rc != TWRC_SUCCESS)
{
throwJTE (env, "Unable to enable default data source (acquire)");
EXIT_CURRENT_BLOCK
}
// Begin the event-handling loop. Data transfer takes place in this loop.
MSG msg;
TW_EVENT event;
TW_PENDINGXFERS pxfers;
while (GetMessage ((LPMSG) &msg, 0, 0, 0))
{
// Each window message must be forwarded to the default data source.
// If the default data source is requesting that the data source's
// dialog box be closed (user pressed Cancel), we must break out of the
// message loop.
if (event.TWMessage == MSG_CLOSEDSREQ)
break;
// If the default data source is requesting that it is ready to begin
// the data transfer, we must perform that transfer.
if (event.TWMessage == MSG_XFERREADY)
{
// Obtain information about the first image to be transferred.
Setting other ICAP properties in scanner...
2006-09-29 13:36:12 sirsuraj
[Reply | View]
Hi, I tried your code but it keep sending an error from the twain driver saying that the resolution cannot be set. My c++ skills are very poor so maybe I am doing something wrong. I would appreciate if could kindly send me the dll created by you at suraj.shinde@lycos.com
Thanks & Best Regards,
Suraj
Support for 2 colors B/W..?
2005-05-04 03:24:55 erikgu
[Reply | View]
Hi.
I just had a quick view and tested the code. I'm just currious why 2 color black and white are left out? Is it just to test so say that the application "wants" more color depth, or are there any technical issues here as well?
I really like this articles but i have to have support for black and white as well.
My C++ skills are limited but if there are any technical problems, could i convert the 1-bit image to a 8-bit greyscale for example?
Support for 2 colors B/W..?
2005-05-04 14:04:34 javajeff
[Reply | View]
Hello,
I've read somewhere that the two most popular BMP formats are 8-bit color and
24-bit color. This is why I chose not to support 1-bit color.
However, I've expanded jtwain.cpp to support 1-bit color. Below are the code
fragments that have been modified or are new in that source file.
The first code fragment belongs in the acquire() function. It simply changes
the validation test to include ii.BitsPerPixel != 1. The error message is
modified as well.
// If image is compressed or is not 1-bit color and not 8-bit color
// and not 24-bit color ...
// Throw exception upon return to Java and break out of event
// loop.
throwJTE (env, "Image compressed or not 1-bit/8-bit/24-bit "
"(acquire)");
break;
}
The second code fragment belongs in the acquire() function. It simply adds an
if test to determine if ii.BitsPerPixel contains 1. If so, a new function is
called to perform the image transfer.
The third and final code fragment represents the beginning of the new image
transfer function. I haven't included all of the code because the remainder of
the function is identical to the other two image transfer functions. Don't
forget to supply a function protoype for this function at the beginning of the
source file -- where the function prototypes to the other two image transfer
functions are declared.
static jobject xferDIB1toImage (LPBITMAPINFOHEADER lpbmih, JNIEnv *env)
{
// Obtain the image's width and height -- both in pixels -- to pass to the
// MemoryImageSource constructor.
int width = lpbmih->biWidth;
int height = lpbmih->biHeight; // height < 0 if bitmap is top-down
if (height < 0)
height = -height;
// Create Java-based integer pixels array to pass to the MemoryImageSource
// constructor.
for (int row = 0; row < height; row++)
for (int col = 0; col < width; col++)
{
// Extract color information for pixel and build an equivalent
// Java pixel for storage in the Java-based integer array.
int byte = bitmap [rowBytes*row+col/8];
int masks [] = { 128, 64, 32, 16, 8, 4, 2, 1 };
int pixel = 0xff000000;
if ((byte & masks [col % 8]) != 0)
pixel |= 0xffffff;
// Store the pixel in the array at the appropriate index.
if (isCopy == JNI_TRUE)
env->ReleaseIntArrayElements (pixels, pixelsArray, 0);
// Build the equivalent of the following Java code fragement:
//
// MemoryImageSource mis;
// mis = new MemoryImageSource (width, height, pixels, 0, width);
// Image im = Toolkit.getDefaultToolkit ().createImage (mis);
There is a curiosity about this function. It includes a palette declaration
but does not use palette anywhere. Why? When I started writing the function, I
was under the impression that the bitmap I was testing against would contain a
palette -- because biClrUsed contains 2 and biBitCount contain 1. My Windows
documentation indicated that this would be the case. However, the
documentation also referred to a packed bitmap that would not contain a
palette. As I discovered, the bitmap was packed and so there was no palette.
When you test these changes in your own environment, it might be that your
bitmap is not packed. If so, simply replace pixel |= 0xffffff with
pixel |= palette [1]. Go figure Windows out!
If you would like a copy of the complete jtwain.cpp file, please send an email
to myself at jeff@gatewest.net. I hope this is helpful to you.
Jeff
Support for 2 colors B/W..?
2005-05-05 09:35:22 erikgu
[Reply | View]
Fantastic!
I got the code integreated =) and it works like clockwork. Looking forward to start integrate it with my application.
8- and 24-bit color modes covers the most cases i think.. But i am also planning on scaning papers/print out's, and a little higher resolution with 1-bit colors makes all the difference in quality and disk size..
I got it to work with my Canon flatbed (1,8,24-bit) and Creative webcam (only 24-bit of course)... IT ROCKS!
Best regards
Erik
(Hmm, I have a feeling I might have more questions in the future though. Hope many other see the value in these articles...)