This merge request adds support to scan a batch of images from a scanner with Automatic Document Feeder (ADF) to the sane.ds. It also fixes problems with the buffered memory transfer mode TWSX_MEMORY, that are caused by related reasons.
Before, activating the ADF only scanned the first image in the batch and then dischared the remaining images without scanning.
# Why ADF scans failed
Prior to this merge request, the sane.ds was based on the principle that for every successful call to sane_start there is exactly one call to sane_cancel. This was already established on the lowest level in unixlib.c:
``` static NTSTATUS start_device( void *args ) { SANE_Status status;
- if (device_started) return STATUS_SUCCESS; status = sane_start( device_handle );
[...] static NTSTATUS cancel_device( void *args ) { if (device_started) sane_cancel( device_handle ); device_started = FALSE; return STATUS_SUCCESS; ```
However the sane API is designed so that for every image in a batch, sane_start must be called without a prior call to sane_cancel, as sane_cancel ends the whole batch. So for an ADF-Scan with 5 pages, sane_start is called 5 times and sane_cancel is to be called once.
https://sane-project.gitlab.io/standard/api.html#code-flow
So the call to
``` SANE_CALL( cancel_device, NULL ); ```
in SANE_ImageNativeXferGet prevented the scan of the 2nd frame. But as long as the "decice_started" flag in unixlib.c was not cleared, it was not possible to scan the next frame, so just removing that call is not sufficient.
# New Approach
An application can scan a whole image with DG_IMAGE/DAT_IMAGEMEMXFER/MSG_GET but then the application will not know the scan resolution. So it is usual to call DG_IMAGE/DAT_IMAGEINFO/MSG_GET to retrieve the scan resolution before the DAT_IMAGEMEMXFER. However the sane specification says that the obtained scan parameters "are guaranteed to be accurate between the time a scan has been started (`sane_start()` has been called) and the completion of that request. Outside of that window, the returned values are best-effort estimates of what the parameters will be when `sane_start()` gets invoked." So it is neccessary to call sane_start to make sure that the scan parameters are valid.
https://sane-project.gitlab.io/standard/api.html#sane-get-parameters
The TWAIN Spec says about DG_IMAGE / DAT_IMAGEINFO / MSG_GET:
"Data Source writers are strongly encouraged to report back finished image values in State 6"
The values that sane reports before calling sane_start can be completely wrong. One driver gave a 0-width estimate.
So this code calls sane_start when the application does call one of the functions that require addition informations and then set the currentState from 6 to 7. With a state of 7, sane_start is not called again.
# Terminating the Batch
Batch scans are done when either:
* The application negotiated a value >0 for CAP_XFERCOUNT or * The sane-parameter "source" is set to ADF.
The applcation calls DG_CONTROL/DAT_PENDINGXFERS/MSG_ENDXFER and the field pPendingXfers->Count determines if the application expects more frames.
Without a batch scan, SANE_Cancel is called to make sure the process ends (else some backends could restart scanning the flatbed on and on).
In batch scan the application counts the number of pages and stops when it is reached.
Else it calls SANE_Start() without SANE_Cancel() to initiate scanning the next page. If this fails, DAT_PENDINGXFERS/MSG_ENDXFER returns pendingcount 0 to inform the application that there are no further pages.
# Why buffered memory transfers failed oftenly
1. The scan parameters from SANE retrieved by the application in DG_IMAGE / DAT_IMAGEINFO / MSG_GET were wrong. For example the image width could be 0. 2. The YOffset parameter in cally to DG_IMAGE/DAT_IMAGEMEMXFER/MSG_GET was always set to 0. It is unclear if this violates the TWAIN specification, as this value is only required in tiled transfer mode. But it more robust to fill in that value, as does the gphoto2.ds and many other TWAIN drivers.
# Tests performed
As this change heavily changes the way the sane API is applied, the exact consequences are not easily predictable. So I tested both with different application programs and with different sane backends.
## Testapplication
There are rather few application programs out there with the capability to do an ADF batch scan and buffered memory transfers. I'm attaching a minimalistic program with 600 lines of C-Code that can do batch scans and buffered memory transfers and save the results into .bmp files. I tested the 32-Bit testprogram to work correctly in Windows 11 with the official driver for the HP Officejet Pro 8600 N911a provided by HP.
## Tested applications
* IrfanView 4.72 both 32-Bit and 64-Bit. 64-Bit needs twaindsm.dll from TWAIN .org to use 64-Bit sane.ds. Irfanview is one of the few easily available applications that can do ADF batch scans. * LibreOffice_6.2.8.2 64-Bit with twaindsm.dll from TWAIN .org to use 64-Bit sane.ds. Newer versions of Libreoffice 64-Bit are using a 32-Bit EXE to address 32-Bit TWAIN. 6.2.8 is the last version with native 64-Bit TWAIN based on twaindsm.dll. * LibreOffice_25.8.2_Win_x86.msi 32-Bit. * OpenOffice.org 3.3.0 32-Bit (May 2011). * Microsoft Word 97 32-Bit (Starts Microsoft Photo Editor for scanning).
## Tested Backends
* HP Officejet Pro 8600 N911a using the hpaio backend. * Brother MFC-L9570CDW using the escl backend. * Brother MFC-L9570CDW using the brscan4 backend (Prprietary driver from brother).
## State of Tests
Some application tests were not repeated with the very last changes in the code. I'd like to see if more changes are neccessary before a wine release before I repeat tests.
[twain_memxfer.zip](/uploads/b0ce7dd6738e0921f1714c09207e3c73/twain_memxfer.zip)
``` Length Date Time Name --------- ---------- ----- ---- 17942 2025-11-05 11:10 memxfer.c 77104 2025-10-16 14:02 twain.h 302 2025-11-05 08:00 Makefile --------- ------- 95348 3 files ```
# Note on twaindsm.dll
The twain data source manager is called twain_32.dll for 32-Bit Windows. However on windows there is not such module for 64-Bit environments. Instead the twain consortium offers twaindsm.dll (Both 32-Bit and 64-Bit).
https://github.com/twain/twain-dsm
It contains an extension in the interface allowing a DS to work with a DSM of any name by transfering the callback entry point. Some documentation on the reasons can be found here: https://github.com/twain/twain-dsm/blob/master/TWAIN_DSM/src/readme.doc even if the filenames documented in later documentations are different.
When renaming the 64-Bit twain_32.dll from wine to twaindsm.dll, a problem occurs due to sane_main.c explicitly searching for twain_32:
``` static TW_UINT16 SANE_OpenDS( pTW_IDENTITY pOrigin, pTW_IDENTITY self) { if (SANE_dsmentry == NULL) { HMODULE moddsm = GetModuleHandleW(L"twain_32"); ```
And the current code (from me) that sends the entry points is called too late (After SANE_OpenDS) so runs into an error. I've got a patch for this. But essentially for 64-Bit TWAIN, the twain_32.dll should be renamed to twaindsm.dll as expected by Windows applications for 64-Bit TWAIN to work out of the box. I have no idea how to patch wine to create twain_32.dll for 32-Bit windows and twaindsm.dll for 64-Bit Windows.
#
-- v2: dlls/sane.ds: Implement DG_CONTROL/DAT_PENDINGXFERS/MSG_GET based on remainingImages counter dlls/sane.ds: Read frame data until EOF in native transfer mode dlls/sane.ds: Fill TW_IMAGEMEMXFER.YOffset in SANE_ImageMemXferGet dlls/sane.ds: Apply SANE_Start() and SANE_Cancel() dlls/sane.ds: Store CAP_XFERCOUNT to activeDS.capXferCount dlls/sane.ds: Replace LocalLock/LocalUnlock with GlobalLock/GlobalUnlock
From: Bernd Herd codeberg@herdsoft.com
The TWAIN Specification defines to use GlobalLock and GlobalUnlock, not LocalLock and LocalUnlock --- dlls/sane.ds/ds_image.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/dlls/sane.ds/ds_image.c b/dlls/sane.ds/ds_image.c index 316fcceffeb..032cc755bb4 100644 --- a/dlls/sane.ds/ds_image.c +++ b/dlls/sane.ds/ds_image.c @@ -238,10 +238,7 @@ TW_UINT16 SANE_ImageMemXferGet (pTW_IDENTITY pOrigin, }
if (pImageMemXfer->Memory.Flags & TWMF_HANDLE) - { - FIXME("Memory Handle, may not be locked correctly\n"); - buffer = LocalLock(pImageMemXfer->Memory.TheMem); - } + buffer = GlobalLock(pImageMemXfer->Memory.TheMem); else buffer = pImageMemXfer->Memory.TheMem;
@@ -286,7 +283,7 @@ TW_UINT16 SANE_ImageMemXferGet (pTW_IDENTITY pOrigin, }
if (pImageMemXfer->Memory.Flags & TWMF_HANDLE) - LocalUnlock(pImageMemXfer->Memory.TheMem); + GlobalUnlock(pImageMemXfer->Memory.TheMem);
return twRC; }
From: Bernd Herd codeberg@herdsoft.com
--- dlls/sane.ds/capability.c | 31 ++++++++++++++++++++++++++----- dlls/sane.ds/sane_i.h | 1 + 2 files changed, 27 insertions(+), 5 deletions(-)
diff --git a/dlls/sane.ds/capability.c b/dlls/sane.ds/capability.c index 5afa4de7d72..908e853a783 100644 --- a/dlls/sane.ds/capability.c +++ b/dlls/sane.ds/capability.c @@ -215,8 +215,13 @@ static TW_UINT16 SANE_ICAPXferMech (pTW_CAPABILITY pCapability, TW_UINT16 action twCC = msg_set(pCapability, &val); if (twCC == TWCC_SUCCESS) { - activeDS.capXferMech = (TW_UINT16) val; - FIXME("Partial Stub: XFERMECH set to %ld, but ignored\n", val); + if (val == TWSX_NATIVE || val == TWSX_MEMORY) + { + activeDS.capXferMech = (TW_UINT16) val; + FIXME("Partial Stub: XFERMECH set to %ld, but ignored\n", val); + } + else + twCC = TWCC_BADVALUE; } break;
@@ -241,6 +246,7 @@ static TW_UINT16 SANE_ICAPXferMech (pTW_CAPABILITY pCapability, TW_UINT16 action static TW_UINT16 SANE_CAPXferCount (pTW_CAPABILITY pCapability, TW_UINT16 action) { TW_UINT32 val; + TW_INT16 val16; TW_UINT16 twCC = TWCC_BADCAP;
TRACE("CAP_XFERCOUNT\n"); @@ -259,8 +265,22 @@ static TW_UINT16 SANE_CAPXferCount (pTW_CAPABILITY pCapability, TW_UINT16 action
case MSG_SET: twCC = msg_set(pCapability, &val); - if (twCC == TWCC_SUCCESS) - FIXME("Partial Stub: XFERCOUNT set to %ld, but ignored\n", val); + val16 = (TW_INT16) val; + if (val16==0) + { + // This case is explicitly mentioned in the TWAIN specification + activeDS.capXferCount = -1; + twCC = TWCC_CHECKSTATUS; + } + else if (val16>0 || val16==-1) + { + activeDS.capXferCount = val16; + TRACE("Set XFERCOUNT %d", activeDS.capXferCount); + } + else + { + twCC = TWCC_BADVALUE; + } break;
case MSG_GETDEFAULT: @@ -268,10 +288,11 @@ static TW_UINT16 SANE_CAPXferCount (pTW_CAPABILITY pCapability, TW_UINT16 action break;
case MSG_RESET: + activeDS.capXferCount = -1; /* .. fall through intentional .. */
case MSG_GETCURRENT: - twCC = set_onevalue(pCapability, TWTY_INT16, -1); + twCC = set_onevalue(pCapability, TWTY_INT16, activeDS.capXferCount); break; } return twCC; diff --git a/dlls/sane.ds/sane_i.h b/dlls/sane.ds/sane_i.h index e35ed77c935..9d6f28d1e80 100644 --- a/dlls/sane.ds/sane_i.h +++ b/dlls/sane.ds/sane_i.h @@ -45,6 +45,7 @@ struct tagActiveDS
/* Capabilities */ TW_UINT16 capXferMech; /* ICAP_XFERMECH */ + TW_INT16 capXferCount; /* ICAP_XFERCOUNT */ BOOL PixelTypeSet; TW_UINT16 defaultPixelType; /* ICAP_PIXELTYPE */ BOOL XResolutionSet;
From: Bernd Herd codeberg@herdsoft.com
Replace all direct calls to start_device and cancel_device by calls to SANE_Start and SANE_Cancel, controlled by the activeDS.currentState.activeDS.currentState
Allow calling sane_start more than once per sane_cancel. --- dlls/sane.ds/ds_ctrl.c | 53 ++++++++++++- dlls/sane.ds/ds_image.c | 165 +++++++++++++++++++++++---------------- dlls/sane.ds/sane_i.h | 5 ++ dlls/sane.ds/sane_main.c | 2 + dlls/sane.ds/ui.c | 1 + dlls/sane.ds/unixlib.c | 1 - 6 files changed, 157 insertions(+), 70 deletions(-)
diff --git a/dlls/sane.ds/ds_ctrl.c b/dlls/sane.ds/ds_ctrl.c index 79c268a36ae..9564ffe80d6 100644 --- a/dlls/sane.ds/ds_ctrl.c +++ b/dlls/sane.ds/ds_ctrl.c @@ -190,6 +190,29 @@ TW_UINT16 SANE_ProcessEvent (pTW_IDENTITY pOrigin, return twRC; }
+/** @brief Check if Automatic Document Feeder is enabled + * + * If the application does not set the number of images it expects, + * finish after the first image if ADF is not enabled. + * + * The source names used depends on the sane backend. + * A complete list of all values for "source" that indicates + * the use of an ADF is in SANE_CAPFeederEnabled + * + * @return TRUE if Automatic Document feeder is enabled. + */ +static +BOOL +SANE_ADF_Enabled(void) +{ + char current_source[256]; + return + sane_option_get_str("source", current_source, sizeof(current_source)) == TWCC_SUCCESS && + (current_source[0]=='A' || current_source[0]=='a'); +} + + + /* DG_CONTROL/DAT_PENDINGXFERS/MSG_ENDXFER */ TW_UINT16 SANE_PendingXfersEndXfer (pTW_IDENTITY pOrigin, TW_MEMREF pData) @@ -199,6 +222,9 @@ TW_UINT16 SANE_PendingXfersEndXfer (pTW_IDENTITY pOrigin,
TRACE("DG_CONTROL/DAT_PENDINGXFERS/MSG_ENDXFER\n");
+ if (activeDS.remainingImages > 0) + activeDS.remainingImages--; + if (activeDS.currentState != 6 && activeDS.currentState != 7) { twRC = TWRC_FAILURE; @@ -206,15 +232,35 @@ TW_UINT16 SANE_PendingXfersEndXfer (pTW_IDENTITY pOrigin, } else { - pPendingXfers->Count = -1; - activeDS.currentState = 6; - if (SANE_CALL( start_device, NULL )) + pPendingXfers->Count = activeDS.remainingImages; + if (!activeDS.remainingImages || + !SANE_ADF_Enabled()) { + /* All requested images transfered. Stop scanning */ pPendingXfers->Count = 0; activeDS.currentState = 5; + SANE_Cancel(); /* Notify the application that it can close the data source */ SANE_Notify(MSG_CLOSEDSREQ); } + else + { + /* To find out if there are more Xfers waiting for us, + * we need to call sane_start. + * On success, this will prepare the next frame and + * bring us back into currentState==7 + */ + activeDS.currentState = 6; + if (SANE_Start()) + { + /* No more frames... tell the application */ + pPendingXfers->Count = 0; + activeDS.currentState = 5; + SANE_Cancel(); + /* Notify the application that it can close the data source */ + SANE_Notify(MSG_CLOSEDSREQ); + } + } twRC = TWRC_SUCCESS; activeDS.twCC = TWCC_SUCCESS; } @@ -371,6 +417,7 @@ TW_UINT16 SANE_EnableDSUserInterface (pTW_IDENTITY pOrigin, { /* no UI will be displayed, so source is ready to transfer data */ activeDS.currentState = 6; /* Transitions to state 6 directly */ + activeDS.remainingImages = activeDS.capXferCount; SANE_Notify(MSG_XFERREADY); }
diff --git a/dlls/sane.ds/ds_image.c b/dlls/sane.ds/ds_image.c index 032cc755bb4..a5e68f1ba44 100644 --- a/dlls/sane.ds/ds_image.c +++ b/dlls/sane.ds/ds_image.c @@ -26,6 +26,75 @@
WINE_DEFAULT_DEBUG_CHANNEL(twain);
+ +/* transition from state 6 to state 7. + * + * Called by either SANE_ImageMemXferGet, SANE_ImageInfoGet or + * SANE_ImageNativeXferGet, whatever the application calls first. + * + * - start the scan with a call to sane start_devince + * - open the progress dialog window + * - call get_sane_params to retrieve parameters of current scan frame + * + * @return TWAIN result code, TWRC_SUCCESS on success + */ +TW_UINT16 SANE_Start(void) +{ + TW_UINT16 twRC = TWRC_SUCCESS; + TRACE("SANE_Start currentState:%d\n", activeDS.currentState); + if (activeDS.currentState != 6) + { + twRC = TWRC_FAILURE; + activeDS.twCC = TWCC_SEQERROR; + } + else + { + /* Open progress dialog */ + activeDS.progressWnd = ScanningDialogBox(activeDS.progressWnd,0); + + /* Start the scan process in sane */ + if (SANE_CALL( start_device, NULL )) + { + activeDS.progressWnd = ScanningDialogBox(activeDS.progressWnd, -1); + activeDS.twCC = TWCC_OPERATIONERROR; + return TWRC_FAILURE; + } + + if (get_sane_params( &activeDS.frame_params )) + { + WARN("sane_get_parameters failed\n"); + SANE_CALL( cancel_device, NULL ); + activeDS.progressWnd = ScanningDialogBox(activeDS.progressWnd, -1); + activeDS.twCC = TWCC_OPERATIONERROR; + return TWRC_FAILURE; + } + + TRACE("Acquiring image %dx%dx%d bits (format=%d last=%d) from sane...\n" + , activeDS.frame_params.pixels_per_line, activeDS.frame_params.lines, + activeDS.frame_params.depth, activeDS.frame_params.format, + activeDS.frame_params.last_frame); + + activeDS.currentState = 7; + } + return twRC; +} + +/** transition from state 7 or 6 to state 5. + * + * Called when an error occurs or when the last frame + * has been transfered successfully. + * - call sane cancel_device to finish any open transfer + * - close the progress dialog box. + */ +void SANE_Cancel(void) +{ + SANE_CALL( cancel_device, NULL ); + activeDS.progressWnd = ScanningDialogBox(activeDS.progressWnd, -1); + activeDS.currentState = 5; +} + + + /* DG_IMAGE/DAT_IMAGEINFO/MSG_GET */ TW_UINT16 SANE_ImageInfoGet (pTW_IDENTITY pOrigin, TW_MEMREF pData) @@ -45,14 +114,11 @@ TW_UINT16 SANE_ImageInfoGet (pTW_IDENTITY pOrigin, { if (activeDS.currentState == 6) { - /* return general image description information about the image about to be transferred */ - TRACE("Getting parameters\n"); - if (SANE_CALL( get_params, &activeDS.frame_params )) + /* Transition from state 6 to state 7 by starting the scan */ + twRC = SANE_Start(); + if (twRC != TWRC_SUCCESS) { - WARN("sane_get_parameters failed\n"); - SANE_CALL( cancel_device, NULL ); - activeDS.twCC = TWCC_OPERATIONERROR; - return TWRC_FAILURE; + return twRC; } }
@@ -201,38 +267,18 @@ TW_UINT16 SANE_ImageMemXferGet (pTW_IDENTITY pOrigin, /* Transfer an image from the source to the application */ if (activeDS.currentState == 6) { - - /* trigger scanning dialog */ - activeDS.progressWnd = ScanningDialogBox(NULL,0); - - ScanningDialogBox(activeDS.progressWnd,0); - - if (SANE_CALL( start_device, NULL )) - { - activeDS.twCC = TWCC_OPERATIONERROR; - return TWRC_FAILURE; - } - - if (get_sane_params( &activeDS.frame_params )) + /* Transition from state 6 to state 7 by starting the scan */ + twRC = SANE_Start(); + if (twRC != TWRC_SUCCESS) { - WARN("sane_get_parameters failed\n"); - SANE_CALL( cancel_device, NULL ); - activeDS.twCC = TWCC_OPERATIONERROR; - return TWRC_FAILURE; + return twRC; } - - TRACE("Acquiring image %dx%dx%d bits (format=%d last=%d) from sane...\n" - , activeDS.frame_params.pixels_per_line, activeDS.frame_params.lines, - activeDS.frame_params.depth, activeDS.frame_params.format, - activeDS.frame_params.last_frame); - - activeDS.currentState = 7; }
/* access memory buffer */ if (pImageMemXfer->Memory.Length < activeDS.frame_params.bytes_per_line) { - SANE_CALL( cancel_device, NULL ); + SANE_Cancel(); activeDS.twCC = TWCC_BADVALUE; return TWRC_FAILURE; } @@ -265,18 +311,15 @@ TW_UINT16 SANE_ImageMemXferGet (pTW_IDENTITY pOrigin,
if (retlen < activeDS.frame_params.bytes_per_line * rows) { - ScanningDialogBox(activeDS.progressWnd, -1); TRACE("sane_read: %u / %u\n", retlen, activeDS.frame_params.bytes_per_line * rows); - SANE_CALL( cancel_device, NULL ); twRC = TWRC_XFERDONE; } activeDS.twCC = TWRC_SUCCESS; } else { - ScanningDialogBox(activeDS.progressWnd, -1); WARN("sane_read: %u\n", twRC); - SANE_CALL( cancel_device, NULL ); + SANE_Cancel(); activeDS.twCC = TWCC_OPERATIONERROR; twRC = TWRC_FAILURE; } @@ -307,28 +350,23 @@ TW_UINT16 SANE_ImageNativeXferGet (pTW_IDENTITY pOrigin,
TRACE("DG_IMAGE/DAT_IMAGENATIVEXFER/MSG_GET\n");
- if (activeDS.currentState != 6) + if (activeDS.currentState == 6) + { + /* Transition from state 6 to state 7 by starting the scan */ + twRC = SANE_Start(); + if (twRC != TWRC_SUCCESS) + { + return twRC; + } + } + + if (activeDS.currentState != 7) { twRC = TWRC_FAILURE; activeDS.twCC = TWCC_SEQERROR; } else { - /* Transfer an image from the source to the application */ - if (SANE_CALL( start_device, NULL )) - { - activeDS.twCC = TWCC_OPERATIONERROR; - return TWRC_FAILURE; - } - - if (SANE_CALL( get_params, &activeDS.frame_params )) - { - WARN("sane_get_parameters failed\n"); - SANE_CALL( cancel_device, NULL ); - activeDS.twCC = TWCC_OPERATIONERROR; - return TWRC_FAILURE; - } - switch (activeDS.frame_params.format) { case FMT_GRAY: @@ -337,8 +375,9 @@ TW_UINT16 SANE_ImageNativeXferGet (pTW_IDENTITY pOrigin, else { FIXME("For NATIVE, we support only 1 bit monochrome and 8 bit Grayscale, not %d\n", activeDS.frame_params.depth); - SANE_CALL( cancel_device, NULL ); + SANE_Cancel(); activeDS.twCC = TWCC_OPERATIONERROR; + activeDS.currentState = 6; return TWRC_FAILURE; } break; @@ -346,16 +385,12 @@ TW_UINT16 SANE_ImageNativeXferGet (pTW_IDENTITY pOrigin, break; case FMT_OTHER: FIXME("For NATIVE, we support only GRAY and RGB\n"); - SANE_CALL( cancel_device, NULL ); + SANE_Cancel(); activeDS.twCC = TWCC_OPERATIONERROR; + activeDS.currentState = 6; return TWRC_FAILURE; }
- TRACE("Acquiring image %dx%dx%d bits (format=%d last=%d bpl=%d) from sane...\n" - , activeDS.frame_params.pixels_per_line, activeDS.frame_params.lines, - activeDS.frame_params.depth, activeDS.frame_params.format, - activeDS.frame_params.last_frame, activeDS.frame_params.bytes_per_line); - dib_bytes_per_line = ((activeDS.frame_params.bytes_per_line + 3) / 4) * 4; dib_bytes = activeDS.frame_params.lines * dib_bytes_per_line;
@@ -365,8 +400,9 @@ TW_UINT16 SANE_ImageNativeXferGet (pTW_IDENTITY pOrigin,
if (!header) { - SANE_CALL( cancel_device, NULL ); + SANE_Cancel(); activeDS.twCC = TWCC_LOWMEMORY; + activeDS.currentState = 6; if (hDIB) GlobalFree(hDIB); return TWRC_FAILURE; @@ -441,24 +477,21 @@ TW_UINT16 SANE_ImageNativeXferGet (pTW_IDENTITY pOrigin, } line -= dib_bytes_per_line; } - activeDS.progressWnd = ScanningDialogBox(activeDS.progressWnd, -1); - - GlobalUnlock(hDIB);
if (twRC != TWCC_SUCCESS) { WARN("sane_read: %u, reading line %d\n", twRC, i); - SANE_CALL( cancel_device, NULL ); + SANE_Cancel(); activeDS.twCC = TWCC_OPERATIONERROR; GlobalFree(hDIB); return TWRC_FAILURE; }
- SANE_CALL( cancel_device, NULL ); + GlobalUnlock(hDIB); + *pHandle = (TW_HANDLE)hDIB; twRC = TWRC_XFERDONE; activeDS.twCC = TWCC_SUCCESS; - activeDS.currentState = 7; } return twRC; } diff --git a/dlls/sane.ds/sane_i.h b/dlls/sane.ds/sane_i.h index 9d6f28d1e80..75cc9076a9a 100644 --- a/dlls/sane.ds/sane_i.h +++ b/dlls/sane.ds/sane_i.h @@ -52,6 +52,9 @@ struct tagActiveDS TW_FIX32 defaultXResolution; BOOL YResolutionSet; TW_FIX32 defaultYResolution; + + /* Remaining number of images to transfer or -1 for unlimited */ + TW_INT16 remainingImages; };
extern struct tagActiveDS activeDS; @@ -60,6 +63,8 @@ extern struct tagActiveDS activeDS; extern TW_UINT16 SANE_SaneCapability (pTW_CAPABILITY pCapability, TW_UINT16 action); extern TW_UINT16 SANE_SaneSetDefaults (void); extern void SANE_Notify (TW_UINT16 message); +extern TW_UINT16 SANE_Start(void); +extern void SANE_Cancel(void);
/* Implementation of operation triplets * From Application to Source (Control Information) */ diff --git a/dlls/sane.ds/sane_main.c b/dlls/sane.ds/sane_main.c index 360509e2bf6..5fd33ae2aaa 100644 --- a/dlls/sane.ds/sane_main.c +++ b/dlls/sane.ds/sane_main.c @@ -80,6 +80,8 @@ static TW_UINT16 SANE_OpenDS( pTW_IDENTITY pOrigin, pTW_IDENTITY self) activeDS.currentState = 4; activeDS.identity.Id = self->Id; activeDS.appIdentity = *pOrigin; + activeDS.capXferMech = TWSX_NATIVE; + activeDS.capXferCount = -1; return TWRC_SUCCESS; } SANE_CALL( close_ds, NULL ); diff --git a/dlls/sane.ds/ui.c b/dlls/sane.ds/ui.c index 0e32e274d9c..86a968b1907 100644 --- a/dlls/sane.ds/ui.c +++ b/dlls/sane.ds/ui.c @@ -1101,6 +1101,7 @@ static INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM if (psn->lParam) { activeDS.currentState = 6; + activeDS.remainingImages = activeDS.capXferCount; SANE_Notify(MSG_XFERREADY); } break; diff --git a/dlls/sane.ds/unixlib.c b/dlls/sane.ds/unixlib.c index 0002e64eacb..9531c758746 100644 --- a/dlls/sane.ds/unixlib.c +++ b/dlls/sane.ds/unixlib.c @@ -295,7 +295,6 @@ static NTSTATUS start_device( void *args ) { SANE_Status status;
- if (device_started) return STATUS_SUCCESS; status = sane_start( device_handle ); if (status != SANE_STATUS_GOOD) {
From: Bernd Herd codeberg@herdsoft.com
Many applications need YOffset to make the mandatory Buffered Memory transfer work. --- dlls/sane.ds/ds_image.c | 4 +++- dlls/sane.ds/sane_i.h | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/dlls/sane.ds/ds_image.c b/dlls/sane.ds/ds_image.c index a5e68f1ba44..a1e7d91553b 100644 --- a/dlls/sane.ds/ds_image.c +++ b/dlls/sane.ds/ds_image.c @@ -75,6 +75,7 @@ TW_UINT16 SANE_Start(void) activeDS.frame_params.last_frame);
activeDS.currentState = 7; + activeDS.YOffset = 0; } return twRC; } @@ -304,8 +305,9 @@ TW_UINT16 SANE_ImageMemXferGet (pTW_IDENTITY pOrigin, pImageMemXfer->Columns = activeDS.frame_params.pixels_per_line; pImageMemXfer->Rows = rows; pImageMemXfer->XOffset = 0; - pImageMemXfer->YOffset = 0; + pImageMemXfer->YOffset = activeDS.YOffset; pImageMemXfer->BytesWritten = retlen; + activeDS.YOffset += rows;
ScanningDialogBox(activeDS.progressWnd, retlen);
diff --git a/dlls/sane.ds/sane_i.h b/dlls/sane.ds/sane_i.h index 75cc9076a9a..69ded09b3fd 100644 --- a/dlls/sane.ds/sane_i.h +++ b/dlls/sane.ds/sane_i.h @@ -53,6 +53,9 @@ struct tagActiveDS BOOL YResolutionSet; TW_FIX32 defaultYResolution;
+ /* Number of scan lines already transfered in DG_IMAGE / DAT_IMAGEMEMXFER / MSG_GET. */ + TW_UINT32 YOffset; + /* Remaining number of images to transfer or -1 for unlimited */ TW_INT16 remainingImages; };
From: Bernd Herd codeberg@herdsoft.com
Makes sure SANE transistions to the next frame on some backends. Extends or shrinks DIB to the number of lines actually transfered by the SANE data source. --- dlls/sane.ds/ds_image.c | 130 +++++++++++++++++++++++++++++++++------- 1 file changed, 107 insertions(+), 23 deletions(-)
diff --git a/dlls/sane.ds/ds_image.c b/dlls/sane.ds/ds_image.c index a1e7d91553b..120c0b52203 100644 --- a/dlls/sane.ds/ds_image.c +++ b/dlls/sane.ds/ds_image.c @@ -348,7 +348,9 @@ TW_UINT16 SANE_ImageNativeXferGet (pTW_IDENTITY pOrigin, RGBTRIPLE *pixels; int color_size = 0; int i, j; + int y=0, eof = 0; BYTE *p; + DWORD tmp, *ptop, *pbot;
TRACE("DG_IMAGE/DAT_IMAGENATIVEXFER/MSG_GET\n");
@@ -394,9 +396,9 @@ TW_UINT16 SANE_ImageNativeXferGet (pTW_IDENTITY pOrigin, }
dib_bytes_per_line = ((activeDS.frame_params.bytes_per_line + 3) / 4) * 4; - dib_bytes = activeDS.frame_params.lines * dib_bytes_per_line; + dib_bytes = (activeDS.frame_params.lines+1) * dib_bytes_per_line;
- hDIB = GlobalAlloc(GMEM_ZEROINIT, dib_bytes + sizeof(*header) + color_size); + hDIB = GlobalAlloc(GMEM_MOVEABLE, dib_bytes + sizeof(*header) + color_size); if (hDIB) header = GlobalLock(hDIB);
@@ -410,9 +412,10 @@ TW_UINT16 SANE_ImageNativeXferGet (pTW_IDENTITY pOrigin, return TWRC_FAILURE; }
+ memset(header, 0, sizeof(BITMAPINFOHEADER)); header->biSize = sizeof (*header); header->biWidth = activeDS.frame_params.pixels_per_line; - header->biHeight = activeDS.frame_params.lines; + header->biHeight = -activeDS.frame_params.lines-1; header->biPlanes = 1; header->biCompression = BI_RGB; switch (activeDS.frame_params.format) @@ -426,7 +429,6 @@ TW_UINT16 SANE_ImageNativeXferGet (pTW_IDENTITY pOrigin, case FMT_OTHER: break; } - header->biSizeImage = dib_bytes; header->biXPelsPerMeter = 0; header->biYPelsPerMeter = 0; header->biClrUsed = 0; @@ -442,53 +444,135 @@ TW_UINT16 SANE_ImageNativeXferGet (pTW_IDENTITY pOrigin, { /* Sane uses 1 to represent minimum intensity (black) and 0 for maximum (white) */ colors[0].rgbBlue = colors[0].rgbRed = colors[0].rgbGreen = 255; + colors[0].rgbReserved = 0; colors[1].rgbBlue = colors[1].rgbRed = colors[1].rgbGreen = 0; + colors[1].rgbReserved = 0; } else for (i = 0; i < (color_size / sizeof(*colors)); i++) + { colors[i].rgbBlue = colors[i].rgbRed = colors[i].rgbGreen = i; + colors[i].rgbReserved = 0; + } }
- - /* Sane returns data in top down order. Acrobat does best with - a bottom up DIB being returned. */ - line = p + (activeDS.frame_params.lines - 1) * dib_bytes_per_line; - for (i = activeDS.frame_params.lines - 1; i >= 0; i--) + do { int retlen; - struct read_data_params params = { line, activeDS.frame_params.bytes_per_line, &retlen }; + struct read_data_params params = { NULL, activeDS.frame_params.bytes_per_line, &retlen }; + + if (y >= -header->biHeight) + { + TRACE("Data source transfers more lines than expected. Extend DIB to %ld lines\n", (-header->biHeight)+1000); + GlobalUnlock(hDIB); + + dib_bytes += 1000 * dib_bytes_per_line; + hDIB = GlobalReAlloc(hDIB, dib_bytes + sizeof(*header) + color_size, GMEM_MOVEABLE); + + if (!hDIB) + { + SANE_Cancel(); + activeDS.twCC = TWCC_LOWMEMORY; + activeDS.currentState = 6; + return TWRC_FAILURE; + } + + header = (BITMAPINFOHEADER *) GlobalLock(hDIB); + header->biHeight -= 1000; + + p = ((BYTE *) header) + header->biSize + color_size; + }
activeDS.progressWnd = ScanningDialogBox(activeDS.progressWnd, - ((activeDS.frame_params.lines - 1 - i) * 100) - / - (activeDS.frame_params.lines - 1)); + MulDiv(y, 100, activeDS.frame_params.lines)); + + params.buffer = + line = p + y * dib_bytes_per_line; + + if (activeDS.frame_params.bytes_per_line & 3) + { + /* Set padding-bytes at the end of the line buffer to 0 */ + memset(line+dib_bytes_per_line-4, 0, 4); + }
twRC = SANE_CALL( read_data, ¶ms ); if (twRC != TWCC_SUCCESS) break; - if (retlen < activeDS.frame_params.bytes_per_line) break; - /* TWAIN: for 24 bit color DIBs, the pixels are stored in BGR order */ - if (activeDS.frame_params.format == FMT_RGB && activeDS.frame_params.depth == 8) + if (retlen < activeDS.frame_params.bytes_per_line) + { /* EOF reached */ + eof = 1; + } + else { - pixels = (RGBTRIPLE *) line; - for (j = 0; j < activeDS.frame_params.pixels_per_line; ++j) + y++; + /* TWAIN: for 24 bit color DIBs, the pixels are stored in BGR order */ + if (activeDS.frame_params.format == FMT_RGB && activeDS.frame_params.depth == 8) { - color_buffer = pixels[j].rgbtRed; - pixels[j].rgbtRed = pixels[j].rgbtBlue; - pixels[j].rgbtBlue = color_buffer; + pixels = (RGBTRIPLE *) line; + for (j = 0; j < activeDS.frame_params.pixels_per_line; ++j) + { + color_buffer = pixels[j].rgbtRed; + pixels[j].rgbtRed = pixels[j].rgbtBlue; + pixels[j].rgbtBlue = color_buffer; + } } } - line -= dib_bytes_per_line; } + while (!eof);
if (twRC != TWCC_SUCCESS) { - WARN("sane_read: %u, reading line %d\n", twRC, i); + WARN("sane_read: %u, reading line %d\n", twRC, y); SANE_Cancel(); activeDS.twCC = TWCC_OPERATIONERROR; GlobalFree(hDIB); return TWRC_FAILURE; }
+ if (y < -header->biHeight && y>0) + { + /* Data source sent less data than allocated. + * This is common when using an ADF. + * Reduce the DIB to the size that has actually been transfered. */ + GlobalUnlock(hDIB); + + dib_bytes += y * dib_bytes_per_line; + hDIB = GlobalReAlloc(hDIB, dib_bytes + sizeof(*header) + color_size, GMEM_MOVEABLE); + + if (!hDIB) + { + SANE_Cancel(); + activeDS.twCC = TWCC_LOWMEMORY; + activeDS.currentState = 6; + return TWRC_FAILURE; + } + + header = (BITMAPINFOHEADER *) GlobalLock(hDIB); + header->biHeight = -y; + + p = ((BYTE *) header) + header->biSize + color_size; + } + + /* Sane returns data in top down order. Acrobat does best with + a bottom up DIB being returned. Flip image */ + header->biHeight *= -1; + header->biSizeImage = dib_bytes; + for (y = 0; y<header->biHeight / 2; y++) + { + ptop = (DWORD *) (p + dib_bytes_per_line * y ); + pbot = (DWORD *) (p + dib_bytes_per_line * (header->biHeight-y-1) ); + + for (i = dib_bytes_per_line/4; --i>=0; ) + { + tmp = *ptop; + *ptop = *pbot; + *pbot = tmp; + ptop++; + pbot++; + } + } + + activeDS.progressWnd = ScanningDialogBox(activeDS.progressWnd, -1); + GlobalUnlock(hDIB);
*pHandle = (TW_HANDLE)hDIB;
From: Bernd Herd codeberg@herdsoft.com
--- dlls/sane.ds/ds_ctrl.c | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-)
diff --git a/dlls/sane.ds/ds_ctrl.c b/dlls/sane.ds/ds_ctrl.c index 9564ffe80d6..0ff0bc69a3a 100644 --- a/dlls/sane.ds/ds_ctrl.c +++ b/dlls/sane.ds/ds_ctrl.c @@ -282,11 +282,39 @@ TW_UINT16 SANE_PendingXfersGet (pTW_IDENTITY pOrigin, twRC = TWRC_FAILURE; activeDS.twCC = TWCC_SEQERROR; } + else if (activeDS.currentState < 6) + { + /* We have not yet started scanning */ + pPendingXfers->Count = activeDS.capXferCount; + twRC = TWRC_SUCCESS; + activeDS.twCC = TWCC_SUCCESS; + + } + else if (activeDS.remainingImages != -1) + { + /* The application gave us a counter with CAP_XFERCOUNT */ + pPendingXfers->Count = activeDS.remainingImages; + twRC = TWRC_SUCCESS; + activeDS.twCC = TWCC_SUCCESS; + } + else if (activeDS.currentState == 7) + { + /* We are already scanning a frame, so there obviously is one */ + pPendingXfers->Count = -1; + twRC = TWRC_SUCCESS; + activeDS.twCC = TWCC_SUCCESS; + } else { pPendingXfers->Count = -1; - if (SANE_CALL( start_device, NULL )) + if (SANE_Start()) + { + /* No more frames... tell the application */ pPendingXfers->Count = 0; + activeDS.currentState = 5; + SANE_Cancel(); + SANE_Notify(MSG_CLOSEDSREQ); + } twRC = TWRC_SUCCESS; activeDS.twCC = TWCC_SUCCESS; }
Vijay Kiran Kamuju (@infyquest) commented about dlls/sane.ds/capability.c:
case MSG_SET: twCC = msg_set(pCapability, &val);
if (twCC == TWCC_SUCCESS)FIXME("Partial Stub: XFERCOUNT set to %ld, but ignored\n", val);
val16 = (TW_INT16) val;if (val16==0){// This case is explicitly mentioned in the TWAIN specification
Please avoid C++ style // comments, But use C style /* .. */ comments
Esme Povirk (@madewokherd) commented about dlls/sane.ds/ds_image.c:
struct read_data_params params = { NULL, activeDS.frame_params.bytes_per_line, &retlen };if (y >= -header->biHeight){TRACE("Data source transfers more lines than expected. Extend DIB to %ld lines\n", (-header->biHeight)+1000);GlobalUnlock(hDIB);dib_bytes += 1000 * dib_bytes_per_line;hDIB = GlobalReAlloc(hDIB, dib_bytes + sizeof(*header) + color_size, GMEM_MOVEABLE);if (!hDIB){SANE_Cancel();activeDS.twCC = TWCC_LOWMEMORY;activeDS.currentState = 6;return TWRC_FAILURE;
hDIB needs to be freed in this case, but we lost its value by assigning from GlobalReAlloc. With GMEM_MOVEABLE, that assignment isn't necessary, so we could just do a check here like `if (!GlobalReAlloc(...))`.
Esme Povirk (@madewokherd) commented about dlls/sane.ds/ds_image.c:
if (y < -header->biHeight && y>0){/* Data source sent less data than allocated.* This is common when using an ADF.* Reduce the DIB to the size that has actually been transfered. */GlobalUnlock(hDIB);dib_bytes += y * dib_bytes_per_line;hDIB = GlobalReAlloc(hDIB, dib_bytes + sizeof(*header) + color_size, GMEM_MOVEABLE);if (!hDIB){SANE_Cancel();activeDS.twCC = TWCC_LOWMEMORY;activeDS.currentState = 6;return TWRC_FAILURE;
hDIB needs to be freed in this case.
Esme Povirk (@madewokherd) commented about dlls/sane.ds/ds_image.c:
WARN("sane_read: %u, reading line %d\n", twRC, i);
WARN("sane_read: %u, reading line %d\n", twRC, y); SANE_Cancel(); activeDS.twCC = TWCC_OPERATIONERROR; GlobalFree(hDIB); return TWRC_FAILURE; }if (y < -header->biHeight && y>0){/* Data source sent less data than allocated.* This is common when using an ADF.* Reduce the DIB to the size that has actually been transfered. */GlobalUnlock(hDIB);dib_bytes += y * dib_bytes_per_line;
I think we want to assign to this directly instead of adding?
Esme Povirk (@madewokherd) commented about dlls/sane.ds/ds_image.c:
header = (BITMAPINFOHEADER *) GlobalLock(hDIB);header->biHeight = -y;p = ((BYTE *) header) + header->biSize + color_size;}/* Sane returns data in top down order. Acrobat does best witha bottom up DIB being returned. Flip image */header->biHeight *= -1;header->biSizeImage = dib_bytes;for (y = 0; y<header->biHeight / 2; y++){ptop = (DWORD *) (p + dib_bytes_per_line * y );pbot = (DWORD *) (p + dib_bytes_per_line * (header->biHeight-y-1) );for (i = dib_bytes_per_line/4; --i>=0; )
I'm not sure if this is correct or not. I think we should avoid side-effects in the loop condition.