Page 1 of 1

BitMap Printing Tutorial in C++ Win32 Rate Topic: ***** 1 Votes

#1 snoopy11  Icon User is offline

  • Engineering ● Software
  • member icon

Reputation: 841
  • View blog
  • Posts: 2,472
  • Joined: 20-March 10

Posted 25 December 2011 - 04:41 AM

Printing a Bitmap to Screen and Printer in C++ Win32

In this Tutorial I would like to cover loading a bitmap from a file

Primarily displaying it inside a window.

Once this has been done, give the user the option to Print it out to a Printer chosen through the Windows Print Dialog or to save it under another name through the Windows Save Dialog.

These type of Dialogs are called the 'Common Dialogs'.
They are generally easy to use and are customisable but we wont be doing that here, just using them as they appear on Windows.

Displaying bitmaps inside a window can generally be quite tricky there are bitmaps to consider there are handles to bitmaps to consider and there are device contexts or DC's to consider.

Also Windows routinely erases the background of any individual window when it is resized or minimised or maximised or obscured by other individual windows this means you are constantly redrawing your window all of the time, you have to keep track of things so you don't end up with a squished screen.

This is where DC's come in device contexts basically hold what the Screen or Printer or other device should look like in Memory.

This is actually quite neat as once you have something in Memory you can manipulate it quite easily.

Also because its in memory Windows cant erase that and you can just repaint your window from Memory, neat huh ?

Ok let us do some programming....

First up is our resource.rc file this is a resource file.

resource.rc
#include "resource.h"


ICO1 ICON DISCARDABLE "snoopy.ico"


//Menu creation
hMenu MENU DISCARDABLE
BEGIN
POPUP "&File"
BEGIN
MENUITEM "Open Bitmap",  IDM_OPEN_BM
MENUITEM "Print Bitmap", IDM_PRINT_BM, GRAYED
MENUITEM "Save Bitmap",  IDM_SAVE_BM, GRAYED
MENUITEM SEPARATOR
MENUITEM "Exit Program", IDM_EXIT
END
END



It contains a link to one ICON the snoopy.ico, well what do you expect it is my Tutorial after all. Put this icon which is available for download at the bottom of this Tutorial in your project directory that is where your main.cpp and header files etc live.

It also contains a Menu called hMenu. There are four menu items on this menu.

IDM_OPEN_BM
IDM_PRINT_BM, GRAYED
IDM_SAVE_BM, GRAYED
IDM_EXIT

They are largely self explanatory but I will add that two of the items are grayed out that means they are not yet enabled. We will enable them when we load a bitmap into our window... and not before. This is because before we load a bitmap we have nothing to save in our window and nothing to print to a printer either so that would be foolish if we tried that.

I will add that this menu would look nicer with some bitmaps on them check out my Tutorial on 'How to add bitmaps to a Menu' elsewhere in the C++ Tutorials forum for advice on how to do this, right now we will concentrate on getting a bitmap on to screen, disk and paper.

Next we have our resource.h header you must give the header this exact name as it appears here as we #include it in our project with this exact name.



resource.h

#define WINVER 0x0500
#define _WIN32_WINNT 0x0500

#include <windows.h>

#define ICO1           100

#define hMenu          1000
#define IDM_OPEN_BM    1001
#define IDM_PRINT_BM   1002
#define IDM_SAVE_BM    1003
#define IDM_EXIT       1004



as you can see it's short but perfectly formed.

we set up some #defines and give them unique nunbers this is important

they each have unique names too.

these numbers are used later on by MAKEINTRESOURCE to extract our resources, as meagre as they are.


The main.cpp


I will explain in great detail the implementation of our main code but first I have NOT
made this a unicode build so if you are using MSVC 2010 then you need to change the character set to Multi Byte.

I will include all the main code at first then go through it top to bottom expaining each step as we go its about 500 lines of code small for a program but reasonably big for a Tutorial.


main.cpp


#include "resource.h"
#include <Winspool.h>
#include <CommDlg.h>
#include <fstream>


/*   variables  */
char szClassName[ ] = "Snoopy's Bitmap Printing Demo";
static OPENFILENAME ofn;
char szFileName[500]= "\0";
RECT rect;
HBITMAP hBitmap;
BITMAP bitmap;
int bxWidth, bxHeight, flag=0;
HDC hdc,hdcMem;
HMENU menu;
HPALETTE hpal;
int cxsize = 0, cxpage = 0;
int cysize = 0, cypage = 0;

/*  Declare  procedures  */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);
HDC GetPrinterDC (HWND Hwnd);
BOOL OpenFileDialog(HWND hwnd, LPTSTR pFileName ,LPTSTR pTitleName);
BOOL SaveFileDialog(HWND hwnd, LPTSTR pFileName ,LPTSTR pTitleName);
void InitialiseDialog(HWND hwnd);
PBITMAPINFO CreateBitmapInfoStruct(HWND hwnd, HBITMAP hBmp);
void SaveBMPFile(HWND hwnd, LPTSTR pszFile, PBITMAPINFO pbi, HBITMAP hBMP, HDC hDC);
BITMAP ConvertToGrayscale(BITMAP source, HDC hDC);




/*Start of Program Entry point*/

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 */
    wincl.style = CS_DBLCLKS;                 /* Catch double-clicks */
    wincl.cbSize = sizeof (WNDCLASSEX);

    /* Use default icon and mouse-pointer */
    wincl.hIcon = LoadIcon (GetModuleHandle(NULL), MAKEINTRESOURCE(ICO1));
    wincl.hIconSm = LoadIcon (GetModuleHandle(NULL), MAKEINTRESOURCE(ICO1));
    wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName = MAKEINTRESOURCE(hMenu);                 /* No 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_OVERLAPPEDWINDOW, /* default window */
               CW_USEDEFAULT,       /* Windows decides the position */
               CW_USEDEFAULT,       /* where the window ends up on the screen */
               544,                 /* The programs width */
               375,                 /* and height in pixels */
               HWND_DESKTOP,        /* The window is a child-window to desktop */
               NULL,                /* menu */
               hThisInstance,       /* Program Instance handler */
               NULL                 /* No Window Creation data */
           );

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

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

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


/*  This function is called by the Windows function DispatchMessage()  */

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

    PAINTSTRUCT ps;
	

    switch (message)                  /* handle the messages */
    {
    case WM_CREATE:
        InitialiseDialog(hwnd);
        menu = GetMenu(hwnd);
        return 0;

    case WM_QUERYNEWPALETTE:
        if (!hpal)
            return FALSE;
        hdc = GetDC(hwnd);
        SelectPalette (hdc, hpal, FALSE);
        RealizePalette (hdc);
        InvalidateRect(hwnd,NULL,TRUE);
        ReleaseDC(hwnd,hdc);
        return TRUE;

    case WM_PALETTECHANGED:
        if (!hpal || (HWND)wParam == hwnd)
            break;
        hdc = GetDC(hwnd);
        SelectPalette (hdc, hpal, FALSE);
        RealizePalette (hdc);
        UpdateColors(hdc);
        ReleaseDC(hwnd,hdc);
        break;




    case WM_COMMAND:
        switch LOWORD(wParam)
        {

        case IDM_OPEN_BM:
        {
            
            OpenFileDialog(hwnd, szFileName, "Open a Bitmap File.");

            if(szFileName)
            {
                ZeroMemory(&hBitmap, sizeof(HBITMAP));
                hBitmap = (HBITMAP)LoadImage(NULL,szFileName,IMAGE_BITMAP,0,0,LR_CREATEDIBSECTION|LR_DEFAULTSIZE|LR_LOADFROMFILE|LR_VGACOLOR);
                if(hBitmap)
                {
                    EnableMenuItem(menu, IDM_SAVE_BM, MF_ENABLED);
                    EnableMenuItem(menu, IDM_PRINT_BM, MF_ENABLED);

                    cxpage = GetDeviceCaps (hdc, HORZRES);
                    cypage = GetDeviceCaps (hdc, VERTRES);
                    GetObject(hBitmap,sizeof(BITMAP),&bitmap);
                    bxWidth = bitmap.bmWidth;
                    bxHeight = bitmap.bmHeight;

                    rect.left = 0;
                    rect.top =0;
                    rect.right = (long)&cxpage;
                    rect.bottom = (long)&cypage;


                    InvalidateRect(hwnd,&rect,true);
                }
            }
            return 0;
		}

            case IDM_PRINT_BM:
            {
                DOCINFO di= { sizeof (DOCINFO), TEXT ("Printing Picture...")};

                HDC prn;

                

                prn = GetPrinterDC(hwnd);
                cxpage = GetDeviceCaps (prn, HORZRES);
                cypage = GetDeviceCaps (prn, VERTRES);
                hdcMem = CreateCompatibleDC(prn);
                HBITMAP hbmOld = (HBITMAP)SelectObject(hdcMem, hBitmap);

                StartDoc (prn, &di);
                StartPage (prn) ;
                SetMapMode (prn, MM_ISOTROPIC);
                SetWindowExtEx(prn, cxpage,cypage, NULL);
                SetViewportExtEx(prn, cxpage, cypage,NULL);

                SetViewportOrgEx(prn, 0, 0, NULL);
                StretchBlt(prn, 0, 0, cxpage, cypage, hdcMem, 0, 0,bxWidth,bxHeight, SRCCOPY);
                EndPage (prn);
                EndDoc(prn);
                DeleteDC(prn);
                SelectObject(hdcMem, hbmOld);
                DeleteDC(hdcMem);

                return 0;
            }

            case IDM_SAVE_BM:
            {
                BOOL result = SaveFileDialog(hwnd,szFileName,"Save a Bitmap.");
                if(result != false)
                {
                    PBITMAPINFO pbi = CreateBitmapInfoStruct(hwnd, hBitmap);
                    hdc= GetDC(hwnd);
                    SaveBMPFile(hwnd, szFileName, pbi, hBitmap, hdc);
                }
                return 0;
            }

            case IDM_EXIT:
            {
                PostQuitMessage(0);
                return 0;
            }
       



        break;
        }
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);

        hdcMem = CreateCompatibleDC(hdc);

        SelectObject(hdcMem, hBitmap);


        cxpage = GetDeviceCaps (hdc, HORZRES);
        cypage = GetDeviceCaps (hdc, VERTRES);

        SetMapMode (hdc, MM_ISOTROPIC);
        SetWindowExtEx(hdc, cxpage,cypage, NULL);
        SetViewportExtEx(hdc, cxsize, cysize,NULL);

        SetViewportOrgEx(hdc, 0, 0, NULL);

        SetStretchBltMode(hdc,COLORONCOLOR);

        StretchBlt(hdc, 0, 0, bxWidth, bxHeight, hdcMem, 0, 0,bxWidth,bxHeight, SRCCOPY);



        DeleteDC(hdcMem);





        EndPaint(hwnd, &ps);
        DeleteDC(hdcMem);
        return 0;

    case  WM_SIZE:

        cxsize = LOWORD(lParam);
        cysize = HIWORD(lParam);
        rect.right = cxsize;
        rect.bottom = cysize;

        InvalidateRect(hwnd, &rect, true);
        return 0;




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

    }
    return 0;

}

HDC GetPrinterDC (HWND Hwnd)
{

// Initialize a PRINTDLG structure's size and set the PD_RETURNDC flag set the Owner flag to hwnd.
// The PD_RETURNDC flag tells the dialog to return a printer device context.
    HDC hdc;
    PRINTDLG pd = {0};
    pd.lStructSize = sizeof( pd );
    pd.hwndOwner = Hwnd;
    pd.Flags = PD_RETURNDC;

// Retrieves the printer DC
    PrintDlg(&pd);
    hdc =pd.hDC;
    return hdc ;


}

BOOL OpenFileDialog(HWND hwnd, LPTSTR pFileName ,LPTSTR pTitleName)

{
    ofn.lStructSize = sizeof(OPENFILENAME);
    ofn.hInstance = GetModuleHandle(NULL);
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter = 0;
    ofn.nFilterIndex = 0;

    ofn.hwndOwner = hwnd;
    ofn.lpstrFile = pFileName;
    ofn.lpstrFileTitle = NULL;
    ofn.lpstrTitle = pTitleName;
    ofn.Flags = OFN_EXPLORER|OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST;
    ofn.lpstrFilter = TEXT("Bitmap Files (*.bmp)\0*.bmp\0\0");



    return GetOpenFileName(&ofn);
}

BOOL SaveFileDialog(HWND hwnd, LPTSTR pFileName ,LPTSTR pTitleName)

{
    ofn.lStructSize = sizeof(OPENFILENAME);
    ofn.hInstance = GetModuleHandle(NULL);
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter = 0;
    ofn.nFilterIndex = 0;

    ofn.hwndOwner = hwnd;
    ofn.lpstrFile = pFileName;
    ofn.lpstrFileTitle = NULL;
    ofn.lpstrTitle = pTitleName;
    ofn.Flags = OFN_EXPLORER|OFN_OVERWRITEPROMPT;
    ofn.lpstrFilter = TEXT("Bitmap Files (*.bmp)\0*.bmp\0\0");



    return GetSaveFileName(&ofn);
}

void InitialiseDialog(HWND hwnd)
{
    ZeroMemory(&ofn, sizeof(ofn));

    ofn.lStructSize = sizeof(OPENFILENAME);
    ofn.hwndOwner = hwnd;
    ofn.hInstance = NULL;
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter = 0;
    ofn.nFilterIndex = 0;
    ofn.lpstrFile = szFileName;
    ofn.nMaxFile = 500;
    ofn.lpstrFileTitle = NULL;
    ofn.nMaxFileTitle = MAX_PATH;
    ofn.lpstrInitialDir = NULL;
    ofn.lpstrTitle = NULL;
    ofn.Flags = 0;
    ofn.nFileOffset = 0;
    ofn.nFileExtension = 0;
    ofn.lpstrDefExt = NULL;
    ofn.lCustData = 0L;
    ofn.lpfnHook = NULL;
    ofn.lpTemplateName = NULL;
}


void SaveBMPFile(HWND hwnd, LPTSTR pszFile, PBITMAPINFO pbi, HBITMAP hBMP, HDC hDC)
{
    std::ofstream 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.
    hf.open(pszFile,std::ios::binary);
    if (hf.fail() == true)
    {
        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.
    hf.write((char*) &hdr, sizeof(BITMAPFILEHEADER));
    if (hf.fail() == true )
    {
        MessageBox(hwnd,"WriteFileHeader","Error",MB_OK );
    }
// Copy the BITMAPINFOHEADER and RGBQUAD array into the file.
     hf.write((char*) pbih, sizeof(BITMAPINFOHEADER) + pbih->biClrUsed * sizeof (RGBQUAD));
    if (hf.fail() == true )
    {
        MessageBox(hwnd,"WriteInfoHeader","Error",MB_OK );
    }
// Copy the array of color indices into the .BMP file.
    dwTotal = cb = pbih->biSizeImage;
    hp = lpBits;
    hf.write((char*) hp, (int) cb);
    if (hf.fail() == true )
    {
        MessageBox(hwnd,"WriteData","Error",MB_OK );
    }
// Close the .BMP file.
    hf.close();
    if (hf.fail() == true)
    {
        MessageBox(hwnd,"CloseHandle","Error",MB_OK );
    }

// Free memory.
    GlobalFree((HGLOBAL)lpBits);
}
//End of BMP Save


PBITMAPINFO CreateBitmapInfoStruct(HWND hwnd, HBITMAP hBmp)
{
    BITMAP bmp;
    PBITMAPINFO pbmi;
    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.
    else
        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
}





The Explanation Part.


Part 1 The Variables

/*   variables  */
char szClassName[ ] = "Snoopy's Bitmap Printing Demo";
static OPENFILENAME ofn;
char szFileName[500]= "\0";
RECT rect;
HBITMAP hBitmap;
BITMAP bitmap;
int bxWidth, bxHeight, flag=0;
HDC hdc,hdcMem;
HMENU menu;
HPALETTE hpal;
int cxsize = 0, cxpage = 0;
int cysize = 0, cypage = 0;




Here we set up some variables.
The classname which also doubles as our title is first.
ofn is declared as an openfilename structure, we will fill in the relevant parts later.
a filename of type char[500] is declared this means our filename is limited to
500 characters including the path, so if you like a lot of nested directories you may
want to enlarge to some more suitable figure.
'rect' is declared as a RECT structure,
hBitmap is declared as a handle to a bitmap, we can place these types of objects into DC's.

bitmap is declared a BITMAP Object.
We set up some int's and handles to device context's.
menu is a handle to a menu.
hpal is a handle to a palette.
some int's for page and screen sizes are setup.


Part 2 The Procedures.

/*  Declare  procedures  */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);
HDC GetPrinterDC (HWND Hwnd);
BOOL OpenFileDialog(HWND hwnd, LPTSTR pFileName ,LPTSTR pTitleName);
BOOL SaveFileDialog(HWND hwnd, LPTSTR pFileName ,LPTSTR pTitleName);
void InitialiseDialog(HWND hwnd);
PBITMAPINFO CreateBitmapInfoStruct(HWND hwnd, HBITMAP hBmp);
void SaveBMPFile(HWND hwnd, LPTSTR pszFile, PBITMAPINFO pbi, HBITMAP hBMP, HDC hDC);



The first procedure is our Windows Callback procedure which we declare later in WinMain.
A printer selection procedure GetPrinterDC is next.
A file dialog procedure for opening files OpenFileDialog is next.
A file dialog procedure for saving files SaveFileDialog is next.

An Initialisation procedure is next it initialises the fields of 'ofn'.
CreateBitmapInfoStruct makes a pointer to a BITMAPINFO structure from a handle to a bitmap object.
SaveBMPfile saves a bitmap to disk.

Part 3 program entry point WinMain.


/*Start of Program Entry point*/

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 */
    wincl.style = CS_DBLCLKS;                 /* Catch double-clicks */
    wincl.cbSize = sizeof (WNDCLASSEX);

    /* Use default icon and mouse-pointer */
    wincl.hIcon = LoadIcon (GetModuleHandle(NULL), MAKEINTRESOURCE(ICO1));
    wincl.hIconSm = LoadIcon (GetModuleHandle(NULL), MAKEINTRESOURCE(ICO1));
    wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName = MAKEINTRESOURCE(hMenu);                 /* No 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_OVERLAPPEDWINDOW, /* default window */
               CW_USEDEFAULT,       /* Windows decides the position */
               CW_USEDEFAULT,       /* where the window ends up on the screen */
               544,                 /* The programs width */
               375,                 /* and height in pixels */
               HWND_DESKTOP,        /* The window is a child-window to desktop */
               NULL,                /* menu */
               hThisInstance,       /* Program Instance handler */
               NULL                 /* No Window Creation data */
           );

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

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

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



It is the entry point of our program it registers a new class and sets up a new window hwnd.

It shows it maximised as this makes more sense for loading picture files.

It sets up a message loop which it translates and dispatches.

It names WindowProcedure as the callback procedure where some of these messages will be intercepted and acted on.


Part 4 Our Windows callback procedure

/*  This function is called by the Windows function DispatchMessage()  */

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

    PAINTSTRUCT ps;
	

    switch (message)                  /* handle the messages */
    {



This is the start of it declares a paintstruct ps; for use in WM_PAINT;
a switch statement handles the messages.

Part 5 WM_CREATE

case WM_CREATE:
        InitialiseDialog(hwnd);
        menu = GetMenu(hwnd);
        return 0;



With this section of code we Initialise the dialogs through setting values for 'ofn'

We get the class menu from hwnd and assign it to the HMENU menu.


Part 6 Getting the correct colours / colors.

case WM_QUERYNEWPALETTE:
        if (!hpal)
            return FALSE;
        hdc = GetDC(hwnd);
        SelectPalette (hdc, hpal, FALSE);
        RealizePalette (hdc);
        InvalidateRect(hwnd,NULL,TRUE);
        ReleaseDC(hwnd,hdc);
        return TRUE;

    case WM_PALETTECHANGED:
        if (!hpal || (HWND)wParam == hwnd)
            break;
        hdc = GetDC(hwnd);
        SelectPalette (hdc, hpal, FALSE);
        RealizePalette (hdc);
        UpdateColors(hdc);
        ReleaseDC(hwnd,hdc);
        break;



This section of code basically deals with getting the corrrect color palette from
our bitmaps that we load into the computer. Each bitmap has its own palette and it would be
good if we loaded that palette into memory and used it to display our pictures
on screen with.

That is all that is basically happening here.

Part 7 The windows commands

case WM_COMMAND:
        switch LOWORD(wParam)
        {



switches the low - order - word of wParam usually for things like buttons and the like.

We dont have any buttons in this demo, everything is done with menus

so we are just dealing with the IDM tags we set up earlier in resource.h.


Part 8 IDM_OPEN_BM (Open BitMap)

 case IDM_OPEN_BM:
        {
            

            OpenFileDialog(hwnd, szFileName, "Open a Bitmap File.");

            if(szFileName)
            {
                ZeroMemory(&hBitmap, sizeof(HBITMAP));
                hBitmap = (HBITMAP)LoadImage(NULL,szFileName,IMAGE_BITMAP,0,0,LR_CREATEDIBSECTION|LR_DEFAULTSIZE|LR_LOADFROMFILE|LR_VGACOLOR);
                if(hBitmap)
                {
                    EnableMenuItem(menu, IDM_SAVE_BM, MF_ENABLED);
                    EnableMenuItem(menu, IDM_PRINT_BM, MF_ENABLED);
                    cxpage = GetDeviceCaps (hdc, HORZRES);
                    cypage = GetDeviceCaps (hdc, VERTRES);
                    GetObject(hBitmap,sizeof(BITMAP),&bitmap);
                    bxWidth = bitmap.bmWidth;
                    bxHeight = bitmap.bmHeight;

                    rect.left = 0;
                    rect.top =0;
                    rect.right = (long)&cxpage;
                    rect.bottom = (long)&cypage;


                    InvalidateRect(hwnd,&rect,true);
                }
            }
          return 0;
	}



Basically .....

Enables the 2 menu items that were grayed out.

Save and Print

Opens a Open File Dialog to return a filename with full path information.
ZeroMemory initialises a block of memory with zero's that are as big as the handle to the Bitmap.
Uses loadimage to actually load the bitmap.
Sets up some variable values and forces a repaint of the window.

Part 9 IDM_PRINT_BM (Print BitMap)

 case IDM_PRINT_BM:
        {
            DOCINFO di= { sizeof (DOCINFO), TEXT ("Printing Picture...")};

            HDC prn;

            

            prn = GetPrinterDC(hwnd);
            cxpage = GetDeviceCaps (prn, HORZRES);
            cypage = GetDeviceCaps (prn, VERTRES);
            hdcMem = CreateCompatibleDC(prn);
            HBITMAP hbmOld = (HBITMAP)SelectObject(hdcMem, hBitmap);

            StartDoc (prn, &di);
            StartPage (prn) ;
            SetMapMode (prn, MM_ISOTROPIC);
            SetWindowExtEx(prn, cxpage,cypage, NULL);
            SetViewportExtEx(prn, cxpage, cypage,NULL);

            SetViewportOrgEx(prn, 0, 0, NULL);
            StretchBlt(prn, 0, 0, cxpage, cypage, hdcMem, 0, 0,bxWidth,bxHeight, SRCCOPY);
            EndPage (prn);
            EndDoc(prn);
            DeleteDC(prn);
            SelectObject(hdcMem, hbmOld);
            DeleteDC(hdcMem);

            return 0;
        }



Next we have the menu item for Printing a Bitmap to paper.
We set up a DOCINFO structure di and Set its caption to "Printing Picture...."
We next get a Device Context to the Printer by calling our procedure GetPrinterDC
once we have that we create a compatible DC in memory then using SelectObject
we draw a bitmap into the memory loaded device context.

We issue the commands StartDoc and StartPage to commence printing operations.
we set the viewport for the printer to print the full size of the page ignoring
any aspect ratios. You could set it up so there would be preservation of aspect
ratios, I do this for displaying the bitmap in a Window through WM_PAINT.

But I am quite happy just to print full size of the page just now without any
algorithm.
We use StretchBlt to basically resize the bitmap to the page size, all this is done in
memory so far.
We issue the commands EndPage and EndDoc, this starts up the printer and the printer spools
the picture out.

We release the Device Context's at the end of this.

Part 10 The Save Bitmap Option

case IDM_SAVE_BM:
        {
            BOOL result = SaveFileDialog(hwnd,szFileName,"Save a Bitmap.");
            if(result != false)
            {
                PBITMAPINFO pbi = CreateBitmapInfoStruct(hwnd, hBitmap);
                hdc= GetDC(hwnd);
                SaveBMPFile(hwnd, szFileName, pbi, hBitmap, hdc);
            }
            return 0;
        }




We call our procedure SaveFileDialog and if the return value is not false we call our
bitmap saving procedure SaveBMPFile

Part 11 The Exit Option.

  case IDM_EXIT:
            {
                PostQuitMessage(0);
                return 0;
            }
       



        break;
        }



We send a PostQuitMessage if this option is selected.
This concludes our Menu options.

Part 12 Painting our window.

ase WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);

        hdcMem = CreateCompatibleDC(hdc);

        SelectObject(hdcMem, hBitmap);


        cxpage = GetDeviceCaps (hdc, HORZRES);
        cypage = GetDeviceCaps (hdc, VERTRES);

        SetMapMode (hdc, MM_ISOTROPIC);
        SetWindowExtEx(hdc, cxpage,cypage, NULL);
        SetViewportExtEx(hdc, cxsize, cysize,NULL);

        SetViewportOrgEx(hdc, 0, 0, NULL);

        SetStretchBltMode(hdc,COLORONCOLOR);

        StretchBlt(hdc, 0, 0, bxWidth, bxHeight, hdcMem, 0, 0,bxWidth,bxHeight, SRCCOPY);



        DeleteDC(hdcMem);





        EndPaint(hwnd, &ps);
        DeleteDC(hdcMem);
        return 0;



We create a Compatible DC in memory
we select our handle to our bitmap into this DC.
we get the device capabilities of this DC, which is
basically the screen capabilities.

We Set the Window extent to the size of the device capabilities
while setting our viewport to the size of the bitmap.

This enures we maintain the aspect ratio of the original picture
at all future re-sizing events for the picture.

We release the Device Context at the end.

Part 13 The Resizing handler WM_SIZE

case  WM_SIZE:

        cxsize = LOWORD(lParam);
        cysize = HIWORD(lParam);
        rect.right = cxsize;
        rect.bottom = cysize;

        InvalidateRect(hwnd, &rect, true);
        return 0;



We get the new size from the lParam of the low order word and high order word for
the resized window we put these sizes into a rect structure.

Then force a repaint of the window.

Part 14 Destroying the window

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

    }
    return 0;

}



If a WM_DESTROY message is sent we send a postquit message to the OS.

All unhandled messages are returned through the message loop in WinMain.

Part 15 Our Printer Selection Procedure

HDC GetPrinterDC (HWND Hwnd)
{

// Initialize a PRINTDLG structure's size and set the PD_RETURNDC flag set the Owner flag to hwnd.
// The PD_RETURNDC flag tells the dialog to return a printer device context.
    HDC hdc;
    PRINTDLG pd = {0};
    pd.lStructSize = sizeof( pd );
    pd.hwndOwner = Hwnd;
    pd.Flags = PD_RETURNDC;

// Retrieves the printer DC
    PrintDlg(&pd);
    hdc =pd.hDC;
    return hdc ;


}



we set HwndOwner to the current window
we set the flag variables pd.Flags to PD_RETURNDC.

this means pd.hDC returns a handle to a selected printer.

Part 16 OpenFileDialog - Opening a File

BOOL OpenFileDialog(HWND hwnd, LPTSTR pFileName ,LPTSTR pTitleName)

{
    ofn.lStructSize = sizeof(OPENFILENAME);
    ofn.hInstance = GetModuleHandle(NULL);
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter = 0;
    ofn.nFilterIndex = 0;

    ofn.hwndOwner = hwnd;
    ofn.lpstrFile = pFileName;
    ofn.lpstrFileTitle = NULL;
    ofn.lpstrTitle = pTitleName;
    ofn.Flags = OFN_EXPLORER|OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST;
    ofn.lpstrFilter = TEXT("Bitmap Files (*.bmp)\0*.bmp\0\0");



    return GetOpenFileName(&ofn);
}



Flags are set to explorer type dialog, the file must exist before we try to load it, and its path.

We setup a filter so only .bmp files can be selected,
we return the entire get open file name structure.

Part 17 Initialising the values of 'ofn'

void InitialiseDialog(HWND hwnd)
{
    ZeroMemory(&ofn, sizeof(ofn));

    ofn.lStructSize = sizeof(OPENFILENAME);
    ofn.hwndOwner = hwnd;
    ofn.hInstance = NULL;
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter = 0;
    ofn.nFilterIndex = 0;
    ofn.lpstrFile = szFileName;
    ofn.nMaxFile = 500;
    ofn.lpstrFileTitle = NULL;
    ofn.nMaxFileTitle = MAX_PATH;
    ofn.lpstrInitialDir = NULL;
    ofn.lpstrTitle = NULL;
    ofn.Flags = 0;
    ofn.nFileOffset = 0;
    ofn.nFileExtension = 0;
    ofn.lpstrDefExt = NULL;
    ofn.lCustData = 0L;
    ofn.lpfnHook = NULL;
    ofn.lpTemplateName = NULL;
}



We use ZeroMemory to fill a block of memory with zeroes.

ofn.nMaxFile is set to 500 you might want to change this to a larger number.
Basic initialistion of ofn to use in Opening and Saving bitmaps.

Part 18 Saving a Bitmap

void SaveBMPFile(HWND hwnd, LPTSTR pszFile, PBITMAPINFO pbi, HBITMAP hBMP, HDC hDC)
{
    std::ofstream 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.
    hf.open(pszFile,std::ios::binary);
    if (hf.fail() == true)
    {
        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.
    hf.write((char*) &hdr, sizeof(BITMAPFILEHEADER));
    if (hf.fail() == true )
    {
        MessageBox(hwnd,"WriteFileHeader","Error",MB_OK );
    }
// Copy the BITMAPINFOHEADER and RGBQUAD array into the file.
    hf.write((char*) pbih, sizeof(BITMAPINFOHEADER) + pbih->biClrUsed * sizeof (RGBQUAD));
    if (hf.fail() == true )
    {
        MessageBox(hwnd,"WriteInfoHeader","Error",MB_OK );
    }
// Copy the array of color indices into the .BMP file.
    dwTotal = cb = pbih->biSizeImage;
    hp = lpBits;
    hf.write((char*) hp, (int) cb);
    if (hf.fail() == true )
    {
        MessageBox(hwnd,"WriteData","Error",MB_OK );
    }
// Close the .BMP file.
    hf.close();
    if (hf.fail() == true)
    {
        MessageBox(hwnd,"CloseHandle","Error",MB_OK );
    }

// Free memory.
    GlobalFree((HGLOBAL)lpBits);
}
//End of BMP Save



I decided to use ofstream to write this bitmap saver.
first we create a new pointer to a bitmapheader.
Allocate enough memory for the bitmap.
First we need to create a bitmapheader as this is the first part of
any bmp file followed by a bitmapinfoheader, followed by the pixel data.

Any bitmapheader must start with the definition 'BM' for Bit Map
This can be the Hex value 0x4d42
or
the Dec value 19778
we write the rest of the values into the bitmap file header.

Then write the values into bitmap info header.

Then write the pixel and color data and close the file.
Release the Memory previously allocated.

The Final Part creating an Info Struct for our Bitmap.

PBITMAPINFO CreateBitmapInfoStruct(HWND hwnd, HBITMAP hBmp)
{
    BITMAP bmp;
    PBITMAPINFO pbmi;
    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.
    else
        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



We get the bitmap color, width and height.

We convert the color format to a count of bits,
initialize the fields in the BITMAPINFO structure,
set the BI_RGB flag.
Compute the number of bytes in the array of color
indices and store the result in biSizeImage.
Set biClrImportant to 0,
and return the bitmapinfo.

There we have it a Tutorial that could be used for the beginning of an
image manipulation program. In fact I might just develop this into just that
in the coming months.

At the beginning of this tutorial there are 3 files

resource.rc
resource.h
main.cpp


You can build these into your project using the snoopy.ico as a resource.
I also provide sample .bmp files for you to load and print or indeed save.


References

Petzold- Programming Windows
MSDN


Best Wishes

Snoopy.

Attached File(s)



Is This A Good Question/Topic? 2
  • +

Replies To: BitMap Printing Tutorial in C++ Win32

#2 stayscrisp  Icon User is offline

  • フカユ
  • member icon

Reputation: 1011
  • View blog
  • Posts: 4,215
  • Joined: 14-February 08

Posted 20 January 2012 - 10:12 AM

Nice work!
Was This Post Helpful? 0
  • +
  • -

#3 snoopy11  Icon User is offline

  • Engineering ● Software
  • member icon

Reputation: 841
  • View blog
  • Posts: 2,472
  • Joined: 20-March 10

Posted 22 January 2012 - 03:59 PM

View Poststayscrisp, on 20 January 2012 - 05:12 PM, said:

Nice work!



Thanks !

Coming from someone so well respected in the community means a lot to me.

Snoopy.
Was This Post Helpful? 0
  • +
  • -

#4 retsgorf297  Icon User is offline

  • New D.I.C Head

Reputation: 1
  • View blog
  • Posts: 12
  • Joined: 03-September 13

Posted 03 September 2013 - 05:37 PM

Good job!
I have been reading this and it has helped a lot. I am now reading your image editor forum and it is great too.
Was This Post Helpful? 0
  • +
  • -

#5 CPPsquid  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 30-April 14

Posted 30 April 2014 - 04:22 PM

I know it is a few years since you put this up, but thanks a lot for posting this. I have been looking for working code on how to print with C++. All I kept finding were code bits that wouldn't even compile. And I could not figure it out on my own from MSDN.
Kudos for great code!!!
and thanks for posting.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1