Using a User-Defined Buffer and Buffered Transfers

Top  Previous  Next

This section should be utilized by programmers who are familiar with handling images, Windows bitmap handling, and creating image headers.  Optionally, if you plan on acquiring a TIFF image using a user-defined buffer, you must be familiar with TIFF headers and tag information.

 

By default, all image creation and memory allocation issues are handled by DTWAIN when DTWAIN_AcquireBuffered or DTWAIN_AccquireBufferedEx is called.  Calling DTWAIN_AcquireBuffered(Ex) is all that is necessary to acquire an image using the buffered transfer mode.

 

However, there are cases where the programmer who is familiar with image handling will want to control the buffered transfers.  It is possible for an application to utilize a user-defined buffer for the returned strip of data and allowing the application to handle the strips of image data.  When the application decides to handle the data, DTWAIN no longer attempts to create the entire image.  Instead, DTWAIN returns the strip that was transferred by the device back to the application.

 

The advantage of this is:

 

Programmers that are familiar with assembling image data from strips now can do so.
Very large images can be handled in strips by the application.
The strips are being sent directly to the application without DTWAIN making extra steps in processing the strips.  This makes high-speed acquisitions possible.

 

The last two points mentioned above are important.  Having a user-defined buffer may be the only way to process very large images (100 megabytes or greater) since the memory allocation that would need to be done for DTWAIN to build the entire image in memory would be very prohibitive .

 

Also, high-speed image acquisition is only possible if there is very little processing of the image strips that are being sent from the device to the application.  DTWAIN will just return the raw data without further processing directly to the application, thereby eliminating a potential bottleneck that DTWAIN may introduce.

 

The user-defined buffer is currently only available for the DTWAIN_AcquireBuffered and DTWAIN_AcquireBufferedEx functions.  It is not available for DTWAIN_AcquireFile, DTWAIN_AcquireFileEx, or DTWAIN_AcquireToClipboard if the flags specify DTWAIN_USEBUFFERED.  DTWAIN_AcquireFile(Ex) requires that DTWAIN handle all of the image building, therefore these functions cannot currently utilize the user-defined buffer.

 

The following table summarizes the differences between allowing DTWAIN to handle the image creation (no user-defined buffer) and when a user-defined buffer is specified:

 

DTWAIN_AcquireBuffered(Ex) action

no user-defined buffer

with user-defined buffer

 

DTWAIN creates entire image 1

Yes

No

DTWAIN creates image header 2

Yes

No

DTWAIN allows user to define buffer size

No

Yes

Notification processing neccessary?

No

No, but preferred

DTWAIN handles all memory allocation / deallocation issues

Yes

No

Application can handle strips if notifications used

No

Yes

Application can query image size of each strip3

Yes

Yes

 

1 When not using a user-defined buffer, DTWAIN_AcquireBuffered(Ex) will create an entire image if DIB's or JPEG compression is used.  For TIFF compressed transfers, only the image data (TIFF without the TIFF tags fields) are generated for both user-defined and non user-defined buffer transfers.  For example, if the compression is Group 4 TIFF, only the Group 4 data is acquired.

 

2 When using a user-defined buffer, only JPEG compressed image strips can be appended to create an entire image.  For any other types of images (DIB, TIFF, etc), the application must create the image header / TIFF tags for the file type.  For every type except JPEG, only the image data is transferred - the image header must be created by the application.

 

3 Notification processing must be used to retrieve data for each strip processed.

 

To allow for a user-defined buffer, the programming language used must be able to process DTWAIN Notifications, either by window procedure or callback function.  Whenever a strip is acquired, DTWAIN will fill the user-defined buffer with the strip of data, and then send the notification DTWAIN_TN_TRANSFERSTRIPDONE.  The last strip is sent when DTWAIN_TN_TRANSFERDONE notification is sent.  This is important to always check for the DTWAIN_TN_TRANSFERDONE strip is sent, since this will contain the final strip of data.

 

Creating the image header

The image header is the block of data that describes the image's width, height, bits-per-pixel, palette, etc.  Except for JPEG compressed transfers, the data that is transferred does not include the image header.  It is the responsibility of the application to create the image header, based on the values returned from DTWAIN_GetImageInfo.  For DIB's, the image header is described by a Windows structure known as a BITMAPINFO.  For TIFF files, the header is made up of a block of data describing that the image is a TIFF image, followed by data that describes the image as a series of TIFF tags.

 

DTWAIN does not create the header or tag information.  This is the responsibility of the programmer.  To aid the programmer in building this information, the programmer should call DTWAIN_GetImageInfo when the DTWAIN_TN_TRANSFERREADY notification is sent, and the values returned from this function can be used to create the image header.

 

Getting started

Step 1: The first function that your application should call is DTWAIN_GetAcquireStripSizes.  This function returns the minimum, maximum, and preferred size of the buffer (in bytes) that the application should use when acquiring an image in strips.  It is up to the programmer to determine the buffer size desired based on these values.  The programmer should not specify a buffer size less than the minimum, or more than the maximum.  The preferred size should be used most of the time, but any size between the minimum and maximum are valid.

 

If the value is close to the minimum, the device will transfer more strips, increasing the acquisition time, but the memory usage is low.  If the value chosen is close to the maximum, less strips will be transferred, reducing the acquisition time, but memory usage increased.  The preferred size is usually a balance between number of strips transferred and memory usage.

 

Step 2: The next function that your application will need to call is to allocate the memory for the buffer.  To do this, your application must call the Windows API function GlobalAlloc using the GHND flag type.  If you are not familiar with this function, it is documented in any Windows API function reference.  Basically, GlobalAlloc allocates memory and returns a handle to your application.  This handle will be used in the next step.

 

So for the first two steps, your application code (if you code in 'C' or C++) will look similar to this:

 

DTWAIN_SOURCE Source;

/* Assume that the Source is open */

 

LONG minSize, maxSize, prefSize;

HANDLE hTheDibStrip;

 

/* Get the minimum, maximum, and preferred size */

DTWAIN_GetAcquireStripSizes( Source, &minSize, &maxSize, &prefSize );

 

/* Use the preferred size to allocate memory */

hTheDibStrip = GlobalAlloc( GHND, prefSize );

 

Step 3: The next step is to let DTWAIN know your application will use a user-defined buffer.  The DTWAIN_SetAcquireStripBuffer does this.

 

DTWAIN_SOURCE Source;

/* Assume that the Source is open */

 

LONG minSize, maxSize, prefSize;

HANDLE hTheDibStrip;

 

/* Get the minimum, maximum, and preferred size */

DTWAIN_GetAcquireStripSizes( Source, &minSize, &maxSize, &prefSize );

 

/* Use the preferred size to allocate memory */

hTheDibStrip = GlobalAlloc( GHND, prefSize );

 

/* Let DTWAIN know about the buffer */

DTWAIN_SetAcquireStripBuffer( Source, hTheDibStrip );

 

If the handle is not NULL, DTWAIN will automatically use it for acquiring strips of data.  To turn off user-defined buffer processing, the handle passed to DTWAIN_SetAcquireStripBuffer must be NULL.

 

Optional:  Call DTWAIN_SetCompressionType at this point if compressed data is desired.  If you are acquiring DIB's, there is no need to call DTWAIN_SetCompressionType.

 

Step 4: Call DTWAIN_AcquireBuffered to start the acquisition.

 

The next steps require that you handle DTWAIN Notifications.

 

Step 5: Build the image header (see special note below).  To do this, you must first trap the DTWAIN_TN_TRANSFERREADY notification and call DTWAIN_GetImageInfo.  Below is a small sample of building a DIB header for an RGB (24-bit or higher image) using a callback function (see DTWAIN_SetCallback for more information on setting up a callback function):

 

BYTE* pTheDib;

HANDLE hTheDib;

//...

LRESULT MyCallback(WPARAM wParam, LPARAM lPara, LONG UserData)

{

    if ( wParam == DTWAIN_TN_TRANSFERREADY )

   {

           double xres, yres;

           LONG width, height, bpp;

           LPBITMAPINFO pBitmap;

           int nSize;

 

           /* Get the image information */

          DTWAIN_GetImageInfo(SelectedSource, &xres, &yres, &width, &height, NULL, NULL, &bpp, NULL, NULL, NULL );

 

           /* Allocate image data */

           /* The image will be an RGB image, so palette is not needed */

           /* Image is width * bits-per-pixel * height.  The calculation makes sure that

           /* the image is aligned on a DWORD boundary (required for DIB images) */

 

           /* Note that if you were saving to a file, the only thing you would really need to */

           /* allocate is the size of a single row of data.  This conserves memory if the only */

           /* need is to write to a file. */

           nSize =  (((width * bpp + 31) / 32) * 4) * height;

 

          /* Allocate memory for the entire DIB */

           hTheDib = GlobalAlloc( GHND, nSize );

 

           /* Lock the memory and access it as a pointer to an LPBITMAPINFO

           pTheDib = (BYTE *)GlobalLock ( hTheDib );

           pBitmap = (LPBITMAPINFO)pTheDib;

 

            /* fill in the image header */

            pBitmap->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);

            pBitmap->bmiHeader.biWidth  = width;

            pBitmap->bmiHeader.biHeight = height;

            pBitmap->bmiHeader.biPlanes = 1;

            pBitmap->bmiHeader.biBitCount = bpp;

            pBitmap->bmiHeader.biCompression = BI_RGB;

            pBitmap->bmiHeader.biSizeImage   = nSize;

            pBitmap->bmiHeader.biClrUsed = 0;

 

           /* Advance past the header and point to where the bitmap data is placed */

            pTheDib += sizeof(BITMAPINFOHEADER);

            return TRUE;

     }

}

//...        

 

For images less than 24-bits, usually a palette would be defined that would be added at the end of the BITMAPINFO data.  Note that the example allocates memory for the entire DIB.  If you are transferring a very large DIB and want to save to a file, you would only allocate for a single strip of data, and append the DIB strip to the file.

 

Step 6: Trap the DTWAIN_TN_TRANSFERSTRIPDONE and DTWAIN_TN_TRANSFERDONE to collect the strips as they are being acquired.   The DTWAIN_GetAcquireStripData function returns the information concerning the strip that was transferred.  The following code is an extension of the code in steps 1 - 5:

 

LRESULT MyCallback(WPARAM wParam, LPARAM lPara, LONG UserData)

{

      LONG bytesWritten;

      BYTE *pTheDibStrip;

 

    if ( wParam == DTWAIN_TN_TRANSFERSTRIPDONE ||

         wParam == DTWAIN_TN_TRANSFERDONE )

   {

           /* Add the strip to the buffer */

           /* First, get the number of bytes received (that's all we care about in the example) */

          DTWAIN_GetAcquireStripData(SelectedSource, NULL, NULL, NULL, NULL, NULL, NULL, &bytesWritten);

         

           /* Lock the strip of data to a pointer */

           pTheDibStrip = (BYTE *)GlobalLock( hTheDibStrip );

 

           /* Now copy the strip to the DIB, incrementally building the DIB.  If saving to a

           /* file, you may want to append each strip to a file. */

           memcpy(pTheDib, pTheDibStrip, bytesWritten);

 

           /* Advance the pointer within the DIB to the next "empty" spot. */

           pTheDib += bytesWritten;

 

          /* Unlock data */

          GlobalUnlock( pTheDibStrip );

 

         if ( wParam == DTWAIN_TN_TRANSFERDONE )

         {

               /* The entire DIB has been transferred */

               /* hTheDib has the entire DIB */

              /* Call DTWAIN helper function to flip the DIB */

            DTWAIN_FlipBitmap( hTheDib );

         }

          return TRUE;

    }

    //.....

}

 

From step 1), hTheDibStrip was the original strip that was passed to the DTWAIN_SetAcquireStripBuffer function.  This buffer will contain the latest strip that was acquired from the Twain device.  The example above gets the strip and appends it to the DIB that is being built (the DIB memory was created in step 5).  You may also want to call GlobalLock on the strip buffer before you passed it to DTWAIN_SetAcquireStripBuffer so that you don't have to make calls to GlobalLock and GlobalUnlock repeatedly when the strips are being transferred.

 

Also note that both DTWAIN_TN_TRANSFERDONE and DTWAIN_TN_TRANSFERSTRIPDONE are handled.  This is important, since the last strip is transferred when DTWAIN_TN_TRANSFERDONE is sent.  Note that if multiple pages are acquired, these steps 5 and 6 will be repeated for each page.

 

After all of the strips of data have been acquired, your application may have to "flip" the bitmap if DIBs are acquired.  DIBs are stored upside-down, so your application must be aware of this.  DTWAIN has a helper function DTWAIN_FlipBitmap that takes a handle to the DIB and turns the DIB right-side up.

 

If you are attempting to save the DIB to a file a strip at a time, you must append each strip to the front of the file to "flip" the image.  Also, you will have to swap all the Red (R) and Green (G) values within the strip before saving to a file (if you are acquring an RGB image).  If not, the DIB will have the wrong colors.

 

The example above is just an outline of how to handle the user-defined buffer and strips of data.  For TIFF transfers, you must be familiar with how to build the TIFF header and the tag information.  For JPEG transfers, the process is much simpler in that all that is needed is to append each new strip to the last strip acquired (similar to the example above), but there is no need to create a header.

 

The functions that handle user-defined buffers are as follows:

 

DTWAIN_GetAcquireStripBuffer

DTWAIN_SetAcquireStripBuffer

DTWAIN_GetAcquireStripData

DTWAIN_GetAcquireStripSizes