dynarithmic / twain_library

Dynarithmic TWAIN Library, Version 5.x
Apache License 2.0
60 stars 25 forks source link

How to get an image without saving it as a file #18

Closed VIIICENTURY closed 3 years ago

VIIICENTURY commented 3 years ago

I'm using Qt and I need to get the image directly without saving it as a file, so that after scanning it will display it on the form. To do this, I need to cast it to the QPixmap type. The DTWAIN description contains the following code example: `

#include "dtwain.h"
int main()
{
    DTWAIN_SysInitialize();
    DTWAIN_SOURCE Source = DTWAIN_SelectSource();
    DTWAIN_ARRAY images;
    if ( Source )
    {
        images = DTWAIN_AcquireNative(Source, DTWAIN_PT_DEFAULT, DTWAIN_MAXACQUIRE, TRUE, TRUE, NULL);
        HANDLE hDib = DTWAIN_GetAcquiredImage(images, 0, 0); // handle to first DIB acquired is returned
        GlobalLock(hDib); // lock image
        /*Now our app has a DIB. We can do any function that needs a DIB
          This time, we'll just delete it.  Your app is responsible for deleting 
          the DIBs, since DTWAIN does not delete any DIBs generated 
          by the Source  */
        GlobalFree( hDib );
    }   
    DTWAIN_SysDestroy();         
} 

` But I don't understand how I can cast this to the QPixmap type.

Dcreeron commented 3 years ago

In my QT app I create a QImage and then convert that to a QPixMap. Something like:

BYTE* pImageData;

    switch (pixelType)
    {
    case TWPT_RGB: // Full Color RGB (24 bit | 8-bit/pixel)
        _sourceImage = QImage(pImageData, width, height, QImage::Format_RGB888);
        break;

    case TWPT_GRAY: // Grayscale (8-bit)
        _sourceImage = QImage(pImageData, width, std::abs(height), QImage::Format_Grayscale8);

        // Set the palette
        for (int i = 0; i < 256; ++i)
        {
            _sourceImage.setColor(i, qRgb(i, i, i));
        }

        break;

    case TWPT_BW: // Black & White (1-bit)
        _sourceImage = QImage(pImageData, width, height, QImage::Format_Mono);

        // Set the palette
        _sourceImage.setColor(0, qRgb(0, 0, 0));
        _sourceImage.setColor(1, qRgb(255, 255, 255));

        break;
    }

To convert it to a QPixmap for use in a QLabel I do:

_ui->lblImageDisplayArea->setPixmap(QPixmap::fromImage(_sourceImage).scaled(_ui->lblImageDisplayArea->width(), _ui->lblImageDisplayArea->height(), Qt::KeepAspectRatio));

VIIICENTURY commented 3 years ago

What am I doing wrong?

  DTWAIN_SysInitialize();
  DTWAIN_SOURCE Source = DTWAIN_SelectSource();
  DTWAIN_ARRAY images;

  if (Source)
  {
      images = DTWAIN_AcquireNative(Source, DTWAIN_PT_RGB, DTWAIN_MAXACQUIRE, TRUE, TRUE, NULL);
      QImage result = QImage(static_cast<BYTE*>(images), 150,150, QImage::Format_RGB888);

      ui->label->setPixmap(QPixmap::fromImage(result).scaled(ui->label->width(), ui->label->height(), Qt::KeepAspectRatio));
  }
  DTWAIN_SysDestroy();

I get this result: Screenshot_1

dynarithmic commented 3 years ago

@VIIICENTURY

The DTWAIN_AcquireNative returns an array of acquisitions, and each acquisition can contain multiple images. Your last code is using a DTWAIN_ARRAY of DTWAIN_ARRAY's, not a separate image.

You should look at the first code posted:

images = DTWAIN_AcquireNative(Source, DTWAIN_PT_RGB, DTWAIN_MAXACQUIRE, TRUE, TRUE, NULL);
HANDLE hDib = DTWAIN_GetAcquiredImage(images, 0, 0); // handle to first DIB acquired is returned

The hDib is what you should be working with. The DTWAIN_GetAcquiredImages allows you to easily get the actual image handle from the aquisition array.

Since Dcreeron has demonstrated how to handle this, here is a detailed explanation of what is going on in the DTWAIN side of things:

Once you have the hDib, then you need to know using the language or image library you're using, how to handle Device Independent Bitmaps. The handle that is returned requires the Windows GlobalLock function to return a pointer to the underyling image, given the handle. Again, this is illustrated in the first example you had. The return value of GlobalLock is a pointer to the actual image data.

Here is another small snippet of code (albeit longer than Dcreeron's) that will convert the device independent bitmap into a Device Dependent Bitmap (a DIB with a bitmap header). The return value is a vector that represents the data:

            #include <vector>
            #include <algorithm>
            #include <iterator>
            #include <cstring>

            #include <windows.h>  // may not be needed, but just in case Qt doesn't include this header
            //...

            int CalculateUsedPaletteEntries(int bit_count) {
                if ((bit_count >= 1) && (bit_count <= 8))
                    return 1 << bit_count;
                return 0;
            }

            std::vector<unsigned char> get_image_as_BMP(HANDLE hDib)  // This is the passed in `hDib`)
            {
                std::vector<unsigned char> retval;
                if (!hDib)
                    return retval;
                BITMAPFILEHEADER fileheader;
                LPBITMAPINFOHEADER lpbi = NULL;
                memset((char *)&fileheader, 0, sizeof(BITMAPFILEHEADER));

                fileheader.bfType = 0x4D42;

                // Fill in the fields of the file header.  Note the call to GlobalLock
                BYTE *pImage2 = (BYTE *)GlobalLock(hDib);

                lpbi = reinterpret_cast<LPBITMAPINFOHEADER>(pImage2);

                fileheader.bfSize = (DWORD)GlobalSize(hDib) + sizeof(BITMAPFILEHEADER);
                fileheader.bfReserved1 = 0;
                fileheader.bfReserved2 = 0;
                fileheader.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) +
                    lpbi->biSize + CalculateUsedPaletteEntries(lpbi->biBitCount) * sizeof(RGBQUAD);

                // Write the file header
                char *ptrFileheader = reinterpret_cast<char *>(&fileheader);
                std::copy(ptrFileheader, ptrFileheader + sizeof(BITMAPFILEHEADER), std::back_inserter(retval));

                // Write the data
                DWORD gSize = static_cast<DWORD>(GlobalSize(hDib));
                std::copy(pImage2, pImage2 + gSize, std::back_inserter(retval));

                // Now unlock the data
                GlobalUnlock(hDib);
                return retval;
            }

Then you can call it like this:

        // This will get a handle to the first image of the first set of acquisitions
        HANDLE hDib = DTWAIN_GetAcquiredImage(image, 0, 0);
        if ( hDib )
        {
             QPixmap pixmap;
             std::vector<unsigned_char> imageData = get_image_as_BMP(hDib);
             pixmap.loadFromData( imageData.data(), imageData.size() );
             //...
             // Do stuff with pixmap  
             ui->label->setPixmap(pixmap.scaled(ui->label->width(), ui->label->height(), Qt::KeepAspectRatio));
        }
dynarithmic commented 3 years ago

Just to let you know, the next version of DTWAIN will have a routine available to convert from DIB to DDB, so the code above won't be necessary.

Dcreeron commented 3 years ago

I thought I had mentioned in my original response that I'm using the DTWAIN_AcquireBuffered() call; looks like I missed pointing that out though. In my code that handles the various DTWAIN Notifications I do things very similar to what dynarithmic has shown in his posts (creating the Device Dependent Bitmap, etc.).

Sorry that I didn't clarify that earlier.

VIIICENTURY commented 3 years ago

@dynarithmic @Dcreeron
Thank you very much! You were very helpful, and your explanations helped to understand the issue. The issue is resolved and can be closed.