dynarithmic / twain_library-java

Apache License 2.0
11 stars 4 forks source link

Acquire image without UI #2

Closed johandevarwaere closed 1 year ago

johandevarwaere commented 1 year ago

I'm trying to launch a scan with an Avision IDA6 scanner without showing the UI using the SimpleImageAcquireNoUIDemo example. After I've selected the scanner, no scan is executed and I get a warning 'Scan size < 64 pixel or 0.5 inch'

What can be causing this warning/problem?

Kind regards, Johan

dynarithmic commented 1 year ago

The error isn't one generated by DTWAIN itself. It looks like an error generated by the scanner's driver.

When acquiring an image without a UI, your program is responsible for setting any capabilities that would have been set if you scanned with a UI. Most scanners I know already set the scanner to a state that if no UI is present, the default settings are adequate.

Possibly your scanner requires you to set the frame size, which is done by getting the capabilities and calling CapabilityInterface.setFrames. I am not sure since I do not have this scanner model.

If you scanned with the UI, do you get this warning? If not, try and duplicate this warning by changing the UI settings (frame size, image type, etc.). If you can duplicate this with the UI, then that is more than likely the setting that isn't being set by your application with the UI turned off.

You could also set up a callback and check what the image information is by overriding the onTransferReady() function in the callback and getting the image information by calling on the source handle, TwainSource.getAcquiredImageInfo() and checking the information returned. Also you can generate a TWAIN log and see what the image information is in the log listing.

If none of this works, then the only thing to do is debug the code, as you have the JNI layer and the DTWAIN code. However as mentioned earlier, the error is generated by your scanner driver.

dynarithmic commented 1 year ago

Also, please try the DTWDEMO32 program that comes with the DTWAIN installation -- this is a pure C program working with the DTWAIN library. If that program works without the UI shown, then that is worth investigating further.

johandevarwaere commented 1 year ago

I've tested with both DTWDEMO32U.exe and the DTWDEMO64U.exe, and both work without showing the UI. Please find attached the outputlogging of this test. I've added logging to SimpleImageAcquireNoUIDemo as well and tested again (with the warning being shown).

I'm using your old library to scan with the same scanner in a java 8 32 bit application without UI successfully. So I suppose the scanner supports this.

I've tried to add the setFrames, but this doesn't change it. I'll try to do the other tests tomorrow.

DTWDEMO64U.log SimpleImageAcquireNoUIDemo.log

dynarithmic commented 1 year ago

The Java version of the log doesn't seem to have the maximum amount of output to see the TWAIN traffic.

Your code should look something like this:

        TwainLoggerCharacteristics logging = twainSession.getLoggerCharacteristics();

        logging.setVerbosity(LoggerVerbosity.MAXIMUM).
                setDestination(LoggingDestination.TOFILE).
                setFileName("SimpleImageAcquireNoUIDemo.log").
                enable(true);

        twainSession.startLogging();

This should log not only the call stack, but the TWAIN traffic.

Having said this, I will need to update the SimpleLoggingDemo program to add the startLogging() call.

dynarithmic commented 1 year ago

Here are some other places in the Java code you can take a look at: 1) com.dynarithmic.twain.highlevel.OptionsApplyer.java

This is responsible for physically setting the options on the scanner when the acquisition is attempted. There is a function in the TwainSource class called startApply. In that function are all of the options being applied for each characteristic of the scanner.

You can comment out the entire list of apply calls and see if you get around the error. If the error goes away, you can slowly uncomment the apply calls until you see the problem reappear. If the problem does reappear, then that apply call is causing the issue and then look into that call to see what is being set.

2) You can still call the JNI Native calls directly: You can get a handle to the native calls by calling TwainSession.getAPIHandle(). Then with the handle, you can directly call DTWAIN_AcquireNative or DTWAIN_AcquireFile, skipping over basically everything and going to the JNI layer. This probably would look something like this:

TwainSession session = new TwainSession();
TwainSource source = session.selectSource();
//...
DTwainJavaAPI handle = session.getAPIHandle();
long sourceHandle = source.getSourceHandle();
handle.DTWAIN_AcquireNative(sourceHandle, params); // or DTWAIN_AcquireFile

with the params, look for the showUI and make sure this is set to 0. If you need a sample call, take a look at TwainSource.java that makes this call (but of course everything has already been set up).

johandevarwaere commented 1 year ago

It's working now. There was actually a problem with the Frame as you already indicated. But setting it via CapabilityInterface.setFrame(..) didn't solve the problem. Calling twainSource.getAcquireCharacteristics().getPagesSupportOptions().getFrame() and setting the bottom, top,left and right values to positive values solves the problem.

Although setting some properties (xResolution, yResolution, brightness, contrast,..) doesn't seem to affect the scan. It did work using the old implementation, so I'm investigating a bit.

Thanks for your help until now anyway.

dynarithmic commented 1 year ago

Thanks for the information.

Take a look at OptionsApplyer.apply and check how the frame is set using the capability interface (using the TwainFrameDouble).

The X and Y resolutiuons are also set here.

You can run the com.dtwain.demos.GetResolutionValuesDemo.java demo program to see if the resolution values are set, and if so, use that as an example.

johandevarwaere commented 1 year ago

I've been playing with the GetResolutionValuesDemo, but whatever resolution value I set, the scan always results in the same image. In the OptionsApplyer I can see the values are correctly set.

dynarithmic commented 1 year ago

1) What happens if you set the resolution values, and you scan with the User interface turned on? Does the UI start off with the resolution values that you set?

2) Did the GetResolutionValuesDemo show that the resolution was set?

3) What happens if you called the CapabilityInterface.getXResolution() right after the acquire() call? Is the resolution that is returned the one you set? If not, then this indicates a driver issue, as the resolution that you set it for before acquire() is the same after acquire().

Note there is very little that can be done if it is an issue with the TWAIN driver, and unfortunately I do not have access to the Avision scanner (it is not available here in the USA). This comes down to possibly falling back to debugging the C++ side of things, and following what is going on when the scanner acquires.

johandevarwaere commented 1 year ago
  1. The UI doesn't show the values I've set (resolution, brightness, contrast,...). I've tried setting the values by using 'twainSource.getAcquireCharacteristics()....' and/or by the CapabilityInterface.
  2. Yes, it shows Setting resolution successful for this value: 172.0 f.e.
  3. Actually its doesn't seem to change ever. I've modified the demo as shown below, but the values always remain [72.0, 1200.0, 1.0, 200.0, 400.0] [400.0] [200.0] at the start, before and after the acquisition. In this example I tried setting the X and Y resolution to 172 dpi (which is in the list of supported values)
public class GetResolutionValuesDemo {

    public void run() {

        DTwainGlobalOptions.setJNIVersion(JNIVersion.JNI_64U);

        TwainSource twainSource = null;

        try {

            final TwainSession twSession = new TwainSession();

            twainSource = twSession.selectSource("IDA6");

            if (twainSource.isOpened()) {
                final String[] unit = { "Dots per Inch", "Dots per centimeter", "Picas", "Points", "TWIPS", "Pixels" };

                twainSource.getAcquireCharacteristics().getGeneralOptions().setAcquireType(AcquireType.NATIVE);
                twainSource.getAcquireCharacteristics().getImageTypeOptions().setPixelType(ICAP_PIXELTYPE.TWPT_RGB);
                twainSource.getAcquireCharacteristics().getAutoAdjustOptions().enableBorderDetection(true);
                twainSource.getAcquireCharacteristics().getAutoAdjustOptions().enableLengthDetection(true);
                // Get the capability interface. The CapabilityInterface is where all capabilities
                // are set, retrieved, and queried for the selected device.
                final CapabilityInterface ci = twainSource.getCapabilityInterface();

                System.err.println("Initial");
                System.err.println(ci.getXResolution(ci.get()));
                System.err.println(ci.getXResolution(ci.getCurrent()));
                System.err.println(ci.getXResolution(ci.getDefault()));

                // Get an operation that will perform a MSG_GET
                final GetCapOperation capOp = ci.get();

                // turn on range expansion, just in case capability values returned are stored in a range
                capOp.enableExpandRange(true); // If this is false, only the range's description will be displayed

                // Test if X-RESOULUTION is supported
                if (ci.isXResolutionSupported()) {
                    // Now get the values
                    final List<Double> xres = ci.getXResolution(capOp);

                    // X-resolution supported, let's see what the available values are for this device
                    final boolean validRange = TwainRangeUtils.isValidRange(xres);

                    // Write out whether the values are range descriptors, or all the actual values.
                    System.out.println("The X resolution values are as follows:" + (validRange ? " (as range)" : ""));

                    // Now get the device's unit setting. This must be supported by all devices, however we
                    // will check it for support using exception handling instead of explicitly calling
                    // a function and testing the return value (as we did with the X-Resolution).
                    List<Integer> units = null;
                    int currentUnit = 0;
                    try {
                        // We want to get the current value
                        capOp.setOperation(MSG.MSG_GETCURRENT);

                        // Get the current unit of Measure
                        units = ci.getUnits(capOp);
                        if (!units.isEmpty()) {
                            currentUnit = units.get(0); // This will always be the first value
                        }
                    } catch (final DTwainRuntimeException capFail) {
                        System.out.println(capFail.getMessage());
                    }

                    // Output each value for the x-resolution
                    for (int i = 0; i < xres.size(); ++i) {
                        System.out.println(xres.get(i) + " " + unit[currentUnit]);
                    }
                    System.err.println("Number of resolutions: " + xres.size());
                    final int index = 100;
                    // Now test setting the resolution to first value.
                    final List<Double> setValues = new ArrayList<>();
                    System.err.println("Going to set resolution: " + xres.get(index));
                    setValues.add(xres.get(index));

                    // This will set the values using the default "set" operation (MSG_SET)
                    ci.setXResolution(setValues, ci.set());
                    ci.setYResolution(setValues, ci.set());

                    // Check if this worked
                    if (ci.getLastCapError().getErrorCode() != 0) {
                        System.out.println("Setting resolution failed for this value: " + xres.get(index));
                    } else {
                        System.out.println("Setting resolution successful for this value: " + xres.get(index));
                    }
                    System.err.println("After set");
                    System.err.println(ci.getXResolution(ci.get()));
                    System.err.println(ci.getXResolution(ci.getCurrent()));
                    System.err.println(ci.getXResolution(ci.getDefault()));

                    final AcquireReturnInfo acquireInfo = twainSource.acquire();

                    System.err.println("after acquire");
                    System.err.println(ci.getXResolution(ci.get()));
                    System.err.println(ci.getXResolution(ci.getCurrent()));
                    System.err.println(ci.getXResolution(ci.getDefault()));

                    final ImageHandler iHandler = acquireInfo.getImageHandler();

                    FileUtils.writeByteArrayToFile(new File("D:/scan_" + xres.get(index) + ".bmp"), iHandler.getImageData(0, 0));

                    // end the TWAIN session
                    twSession.stop();
                }
            }
        } catch (final Exception e1) {
            System.out.println(e1.getMessage());
        }
    }

    public static void main(String[] args) {
        final GetResolutionValuesDemo gValues = new GetResolutionValuesDemo();
        gValues.run();
    }
}`
johandevarwaere commented 1 year ago

I've done a test with another scanner (Epson Perfection V600) which seems to have the same behaviour.

dynarithmic commented 1 year ago

You shoud generate a full TWAIN log that shows what is happening when the capability is being set. That is the only way to determine if the setting is being recognized by the driver.

In the log will be several TW_CAPABILITY Twain calls, with both the setting and return values (the capability would be numbered as 4376 and 4377, meaning X-Resolution and Y-Resolution, respectively).

dynarithmic commented 1 year ago

I may have found an issue with the DTWAIN_SetCapValues native function.

Just to see if this may be the issue, in your application, try the following:

1) Create a public void postSetup() function that looks something like this:

public void postSetup()
{
    DTwainJavaAPI handle = this.getTwainSession().getAPIHandle();
    long sourceHandle = this.getSourceHandle();
    double [] vals = {172.0};
    int [] caps = { TwainConstants.CAPS.ICAP_XRESOLUTION, TwainConstants.CAPS.ICAP_YRESOLUTION };
    for (int i = 0; i < 2; ++i) {
        try {
                  int retValue = handle.DTWAIN_SetCapValuesDouble(sourceHandle,  
                                      caps[i],
                                      MSG.MSG_SET, 
                                      vals);
                  System.out.println("The value returned is: " + retValue);
            }
            catch (Exception e) {  }
      }
}  

2) Place this function in the TwainSource.java (add it as a member function of TwainSource).

3) In TwainSource.java, after the startApply() in prepareAcquisitions(), call postSetup():

   private void prepareAcquisitions() throws DTwainJavaAPIException
    {
        if ( !preAcquireSetup() )
            return;
        startApply();
        postSetup();
       //...

If this successfully sets the resolution, then you can use this until I get a patch in for the generic DTWAIN_SetCapValues native function (which is where the issue is).

johandevarwaere commented 1 year ago

Adding the postSetup method didn't solve the problem.

I've update the library to the latest 1.0.6 release this morning, and now I'm able to set the resolution, contrast etc.. So it seems to be better now. What caused the problem?

dynarithmic commented 1 year ago

The only change was in the JNI layer. See the diff here.

The incorrect casting was done for double types, and resolution, contrast, brightness, and gamma use floating point values.

johandevarwaere commented 1 year ago

I see. Thanks very much for your help!

For me this issue can be closed.