Page 1 of 1

Scanner Application with EZTWAIN Library How to write a scanner application in c++ Rate Topic: -----

#1 snoopy11  Icon User is offline

  • Engineering ● Software
  • member icon

Reputation: 710
  • View blog
  • Posts: 2,033
  • Joined: 20-March 10

Posted 03 October 2010 - 04:13 PM

Scanning Application Tutorial with EZTWAIN Library.

The scope of this tutorial covers how to download dosadi’s
EZTWAIN classic free twain scanner Library, build it, install it
and then use it in an example program.

Go to

click on the EZTWAIN classic icon
and download which is about 190kb

unzip it and you should see a list including the folder VC
click on that and you should see a RELEASE folder
this is where EZTW32.lib is and where EZTW32.dll is.

If you are using MSVC as your IDE/Compiler you do not
have much to do just place EZTW32.lib in your lib folder of
your compiler.

If you are using Code::Blocks with MinGW as I am we are going to have to build the Library
dont worry this is painless and quite straightforward.

First of all fire up Code::Blocks and do -> File -> Import Project ->
Import MS Visual C++ workspace.

Go to the VC folder of your dowloaded EZTWAIN as mentioned before.
And select VC.dsw
this should bring up two projects in your project workspace
TWERP and EZTW32 close TWERP as we dont need it then click on build to
build the EZTW32 library.

Once you have built the Library go to VC -> RELEASE as mentioned before
where you found EZTW32.dll.

You should now see a MinGW library -> libEZTW32.a
copy it and paste it into the MinGW lib folder.

Your going to have to link to it in our example program.
If you are using MSVC then your going to have to link to EZTW32.lib

Next Create an empty Win32 c++ application

Call it ‘scanner’ or some other imaginative title.

Once thats done add a header file and call it resource.h
also while your at it create a resource file and call it resource.rc.

In your resource.h file copy and paste the following


#define APP_SCAN 102
#define APP_SAVE 103
#define APP_EXIT  104


In your resource.rc file type or copy and paste the following

#include "resource.h"

    POPUP "&Image"
        MENUITEM "&Scan",           APP_SCAN
        MENUITEM "&Save",           APP_SAVE, GRAYED
        MENUITEM "&Exit",             APP_EXIT

both resource.h and resource.rc concern themselves with Menu Creation
you will notice that Save is grayed out. This is because we dont want
the Image -> Save Menu function to be activated until we scan something in.

Next we concern ourselves with our main.cpp if you dont already have one
create one.

And put the following lines of code in it.

#include <windows.h>
#include <eztwain.h>
#include <twain.h>
#include "resource.h"

you will notice 2 headers eztwain.h and twain.h
these are located in the VC folder.

Copy and Paste them into your compilers include folder.

The next bit of code creates a window just windows standard stuff

HBITMAP		hdib;
HPALETTE 	hpal;               // logical palette
HDC hdcMem;
/*  Declare Windows procedure  */
void CreateBMPFile(HWND hwnd, LPTSTR pszFile, PBITMAPINFO pbi, HBITMAP hBMP, HDC hDC);
PBITMAPINFO CreateBitmapInfoStruct(HWND hwnd, HBITMAP hBmp);
/*  Make the class name into a global variable  */
char szClassName[ ] = "Scanner App";

int WINAPI WinMain (HINSTANCE hThisInstance,
                    HINSTANCE hPrevInstance,
                    LPSTR lpszArgument,
                    int nCmdShow)
    HWND hwnd;               /* This is the handle for our window */
    MSG messages;            /* Here messages to the application are saved */
    WNDCLASSEX wincl;        /* Data structure for the windowclass */

    /* The Window structure */
    wincl.hInstance = hThisInstance;
    wincl.lpszClassName = szClassName;
    wincl.lpfnWndProc = WindowProcedure;      /* This function is called by windows */ = CS_HREDRAW|CS_VREDRAW;
    wincl.cbSize = sizeof (WNDCLASSEX);

    /* Use default icon and mouse-pointer */
    wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName = "APP_MENU";                 /* menu */
    wincl.cbClsExtra = 0;                      /* No extra bytes after the window class */
    wincl.cbWndExtra = 0;                      /* structure or the window instance */
    /* Use Windows's default colour as the background of the window */
    wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

    /* Register the window class, and if it fails quit the program */
    if (!RegisterClassEx (&wincl))
        return 0;

    /* The class is registered, let's create the program*/
    hwnd = CreateWindowEx (
               0,                   /* Extended possibilites for variation */
               szClassName,         /* Classname */
               szClassName,       /* Title Text */
               WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX, /* default window */
               CW_USEDEFAULT ,       /* Windows decides the position */
               CW_USEDEFAULT,       /* where the window ends up on the screen */
               600,                 /* The programs width */
               800,                 /* and height in pixels */
               HWND_DESKTOP,        /* The window is a child-window to desktop */
               NULL,                /* No menu */
               hThisInstance,       /* Program Instance handler */
               NULL                 /* No Window Creation data */

    /* Make the window visible on the screen */
    ShowWindow (hwnd, nCmdShow);

    /* Run the message loop. It will run until GetMessage() returns 0 */
    while (GetMessage (&messages, NULL, 0, 0))
        /* Translate virtual-key messages into character messages */
        /* Send message to WindowProcedure */

    /* The program return-value is 0 - The value that PostQuitMessage() gave */
    return messages.wParam;

The next bit of code is our windows callback procedure

LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

    HDC  hDC,hdc,Memhdc;

    RECT rc;
    HMENU hMenu;
    hMenu = GetMenu(hwnd);
    switch (message)                  /* handle the messages */

The next bit of code is WM_CREATE

        if (!TWAIN_IsAvailable())
            MessageBox(hwnd, " No Twain devices Available ","Notice",1);

            if (!TWAIN_SelectImageSource(hwnd))
        return 0;

The first part of this code tests for whether any TWAIN devices are available
if not a Message Box is displayed informing the user.

The next bit of code is a call to a dialog to select a twain device if one is not selected
the window is destroyed.

The next bit of code is WM_COMMAND this concerns itself with the menu commands.

        switch (wParam)
        case APP_SCAN:
            TWAIN_AcquireToClipboard(hwnd, TWAIN_RGB);

            if (OpenClipboard(hwnd))
                hdib = (HBITMAP) GetClipboardData(CF_BITMAP);
                GetObject(hdib, sizeof(BITMAP), (LPSTR)&bmp);
                GetClientRect(hwnd, &rc);
                hpal = TWAIN_CreateDibPalette(hdib);
                EnableMenuItem(hMenu, APP_SAVE, MF_ENABLED);


this bit when Scan is selected from the menu
opens a local interface with your scanner driver
and copies it to the clipboard. A new palette
is created and the Save function which was grayed out
is enabled.
InvalidateRect forces a repaint of the window.

The next bit of WM_COMMAND is

case APP_EXIT:

This exits the application

The next bit of WM_COMMAND saves the scanned bitmap to a bmp file.
First it opens a Save As dialog and allows the user to place
it in any folder and directory they choose.

case APP_SAVE:
            char szFileName[MAX_PATH] = "";
            Memhdc = GetDC(hwnd);
            OPENFILENAME ofn;
            ZeroMemory(&ofn, sizeof(ofn));

            ofn.lStructSize= sizeof(ofn);
            ofn.hwndOwner = hwnd;
            ofn.lpstrFilter = "Bitmaps(*.bmp)\0*.bmp\0";
            ofn.lpstrFile = szFileName;
            ofn.nMaxFile = MAX_PATH;
            ofn.lpstrDefExt = "bmp";

            if (GetSaveFileName(&ofn))
                if (szFileName)
                    PBITMAPINFO pbi = CreateBitmapInfoStruct(hwnd, hdib);
                    CreateBMPFile(hwnd, szFileName, pbi, hdib, Memhdc);


        return 0;

The next bit of WM_COMMAND concerns itself with the change in palette

        if (!hpal)
            return FALSE;
        hdc = GetDC(hwnd);
        SelectPalette (hdc, hpal, FALSE);
        RealizePalette (hdc);
        return TRUE;

        if (!hpal || (HWND)wParam == hwnd)
        hdc = GetDC(hwnd);
        SelectPalette (hdc, hpal, FALSE);
        RealizePalette (hdc);

The next bit of code is our WM_PAINT
this obviously draws our bitmap onto the Window
this is a preview window and I will be using
StretchBlt to compress the image into an area
of 600 wide and 800 height. There is a slight loss of colour
information with this method. But its just for the preview
window and so I feel its ok. The resultant saved image will
have no such information loss.

case WM_PAINT:
        PAINTSTRUCT 	ps;
        hDC = BeginPaint(hwnd, &ps);
        if (hDC)
            RECT rc;
            GetClientRect(hwnd, &rc);

            if (hdib)

                hdcMem = CreateCompatibleDC (hDC);
                SetMapMode(hDC, MM_ANISOTROPIC);
                SetWindowExtEx(hDC, 600, 800, NULL);
                SetViewportExtEx(hDC, rc.right, rc.bottom, NULL);

                SelectObject(hdcMem, hdib);
                SelectPalette (hDC, hpal, FALSE);
                RealizePalette (hDC);
                StretchBlt(hDC,rc.left , ,rc.right,rc.bottom,hdcMem,0,0,bmp.bmWidth,bmp.bmHeight,SRCCOPY);


        EndPaint(hwnd, &ps);

    return 0;

Our final piece of the windows callback procedure is

        PostQuitMessage (0);       /* send a WM_QUIT to the message queue */
    default:                      /* for messages that we don't deal with */
        return DefWindowProc (hwnd, message, wParam, lParam);

    return 0;

its pretty self explanantory.

The final (Thank God) piece of code deals with the creation of a BitmapInfoStruct
and the actual saving of a bitmap file.

Sing along if you think you know the tune

void CreateBMPFile(HWND hwnd, LPTSTR pszFile, PBITMAPINFO pbi, HBITMAP hBMP, HDC hDC)
    HANDLE hf;                  // file handle
    BITMAPFILEHEADER hdr;       // bitmap file-header
    PBITMAPINFOHEADER pbih;     // bitmap info-header
    LPBYTE lpBits;              // memory pointer
    DWORD dwTotal;              // total count of bytes
    DWORD cb;                   // incremental count of bytes
    BYTE *hp;                   // byte pointer
    DWORD dwTmp;

    pbih = (PBITMAPINFOHEADER) pbi;
    lpBits = (LPBYTE) GlobalAlloc(GMEM_FIXED, pbih->biSizeImage);

    if (!lpBits)
        MessageBox(hwnd,"GlobalAlloc","Error", MB_OK );
// Retrieve the color table (RGBQUAD array) and the bits
// (array of palette indices) from the DIB.
    if (!GetDIBits(hDC, hBMP, 0, (WORD) pbih->biHeight, lpBits, pbi,DIB_RGB_COLORS))
        MessageBox(hwnd,"GetDIBits","Error",MB_OK );
// Create the .BMP file.
        MessageBox( hwnd,"CreateFile","Error", MB_OK);

    hdr.bfType = 0x4d42;  // File type designator "BM" 0x42 = "B" 0x4d = "M"
// Compute the size of the entire file.
    hdr.bfSize = (DWORD) (sizeof(BITMAPFILEHEADER) + pbih->biSize + pbih->biClrUsed * sizeof(RGBQUAD) + pbih->biSizeImage);
    hdr.bfReserved1 = 0;
    hdr.bfReserved2 = 0;
// Compute the offset to the array of color indices.
    hdr.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) + pbih->biSize + pbih->biClrUsed * sizeof (RGBQUAD);
// Copy the BITMAPFILEHEADER into the .BMP file.
    if (!WriteFile(hf, (LPVOID) &hdr, sizeof(BITMAPFILEHEADER), (LPDWORD) &dwTmp,  NULL) )
        MessageBox(hwnd,"WriteFileHeader","Error",MB_OK );
// Copy the BITMAPINFOHEADER and RGBQUAD array into the file.
    if (!WriteFile(hf, (LPVOID) pbih, sizeof(BITMAPINFOHEADER) + pbih->biClrUsed * sizeof (RGBQUAD), (LPDWORD) &dwTmp, NULL))
        MessageBox(hwnd,"WriteInfoHeader","Error",MB_OK );
// Copy the array of color indices into the .BMP file.
    dwTotal = cb = pbih->biSizeImage;
    hp = lpBits;
    if (!WriteFile(hf, (LPSTR) hp, (int) cb, (LPDWORD) &dwTmp,NULL))
        MessageBox(hwnd,"WriteFile","Error",MB_OK );
// Close the .BMP file.
    if (!CloseHandle(hf))
        MessageBox(hwnd,"CloseHandle","Error",MB_OK );

// Free memory.
//End of BMP Save

PBITMAPINFO CreateBitmapInfoStruct(HWND hwnd, HBITMAP hBmp)
    BITMAP bmp;
    WORD cClrBits;
// Retrieve the bitmap color format, width, and height.
    if (!GetObject(hBmp, sizeof(BITMAP), (LPSTR)&bmp))
        MessageBox(hwnd,"GetObject","Error",MB_OK );
// Convert the color format to a count of bits.
    cClrBits = (WORD)(bmp.bmPlanes * bmp.bmBitsPixel);
    if (cClrBits == 1)
        cClrBits = 1;
    else if (cClrBits <= 4)
        cClrBits = 4;
    else if (cClrBits <= 8)
        cClrBits = 8;
    else if (cClrBits <= 16)
        cClrBits = 16;
    else if (cClrBits <= 24)
        cClrBits = 24;
    else cClrBits = 32;

// Allocate memory for the BITMAPINFO structure. (This structure
// contains a BITMAPINFOHEADER structure and an array of RGBQUAD
// data structures.)

    if (cClrBits != 24)
        pbmi = (PBITMAPINFO) LocalAlloc(LPTR,sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * (1<< cClrBits));
// There is no RGBQUAD array for the 24-bit-per-pixel format.
        pbmi = (PBITMAPINFO) LocalAlloc(LPTR, sizeof(BITMAPINFOHEADER));

// Initialize the fields in the BITMAPINFO structure.
    pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    pbmi->bmiHeader.biWidth = bmp.bmWidth;
    pbmi->bmiHeader.biHeight = bmp.bmHeight;
    pbmi->bmiHeader.biPlanes = bmp.bmPlanes;
    pbmi->bmiHeader.biBitCount = bmp.bmBitsPixel;
    if (cClrBits < 24)
        pbmi->bmiHeader.biClrUsed = (1<<cClrBits);
// If the bitmap is not compressed, set the BI_RGB flag.
    pbmi->bmiHeader.biCompression = BI_RGB;

// Compute the number of bytes in the array of color
// indices and store the result in biSizeImage.
// For Windows NT, the width must be DWORD aligned unless
// the bitmap is RLE compressed. This example shows this.
// For Windows 95/98/Me, the width must be WORD aligned unless the
// bitmap is RLE compressed.
    pbmi->bmiHeader.biSizeImage = ((pbmi->bmiHeader.biWidth * cClrBits +31) & ~31) /8 * pbmi->bmiHeader.biHeight;
// Set biClrImportant to 0, indicating that all of the
// device colors are important.
    pbmi->bmiHeader.biClrImportant = 0;

    return pbmi; //return BITMAPINFO

and there we have it if you have followed the tutorial exactly you should have
a piece of code that compiles. I have used Code::Blocks for this its free and
thats good for me. If you have used MSVC remember to make your project
to use Multibyte character set. As I have not made it a unicode build.

The final thing you have to do is copy EZTW32.dll into your project directory
where your main.cpp is saved and run the application.

The possibilities for building scanning applications are endless and it is easy to
ammend this program for your specific needs. Enjoy and bon chance.

Is This A Good Question/Topic? 1
  • +

Page 1 of 1