Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Bitmap From Screen

0.00/5 (No votes)
14 Feb 2006 1  
The anatomy of a bitmap.

Sample Image

Introduction

This article demonstrates how to create and save bitmaps from an image on the screen and convert a bitmap of one bit count to another. The project contains a folder of bitmap samples. Different sizes of bitmaps are available named from Bitmap1.bmp through Bitmap32.bmp. BitmapDemo.bmp is the test image, and the bitmaps used to create the samples are: fish*.bmp, lake.bmp, and star.bmp. The fish bitmaps are 32 bit, the lake bitmap is 8 bit, and the star is 4 bit. You will notice that Bitmap8 through Bitmap32 have good color properties, Bitmap4 loses some of the color because it only uses 8 colors, and Bitmap1 is 2 colors, black or white. Typical usage of the program is Ctrl+O to open a bitmap file, Ctrl+C to create an image, and Ctrl+S to save the file, or Ctrl+A to use Save As to save the image to a bitmap file. Ctrl+I and Ctrl+T switch between text or bitmap images. The default image size is 24.

Background

I started working with Microsoft Visual C++ 6.0 in September, 1998. Soon I was using bitmaps but never understood how they were created or their file format. That is why I wrote this program. It creates bitmaps of standard sizes, 1, 4, 8, 16, 24, and 32 bit. The latter two, 24 and 32, are created from an image array, where I get the pixels from the screen, save them in a BYTE array, create the bitmap, and save it to a file. The format of these is very simple, the file contains a BITMAPFILEHEADER of 14 bytes, followed by the BITMAPINFO structure which is a BITMAPINFOHEADER of 40 bytes, and then the bitmap data which is 4 bytes of RGB color information, only the first three are used, the fourth is ignored. A 32 bit value is used for each pixel. The 24 bit bitmap is the same as the 32 bit one except that the fourth byte is not created, saving 1 byte for each pixel of data. The file format is little-endian, LSB first. The first three types, 32, 24, 16 are never compressed and do not use a palette. 16 bit bitmap color fields are either 3 bytes of 5 bits or 3 DWORD color masks depending on the biCompression DWORD value of BITMAPINFOHEADER; for this reason, I decided not to write code for them and used the DIBBLE bitmap and palette program from "Programming Windows Fifth Edition" by Charles Petzold. DIBBLE is a "C" program and I'm using "C++" which causes some problems (see Points of Interest) later.

Using the code

The code started as a Win32 application, the typical "Hello World" application. To make writing the code easier, I decided to use some of the "MFC" classes, CMenu, CBitmap, CString, and the Common Dialog, CFileDialog. Note to reader: to change to an "MFC" application, in the Project Settings menu, General tab, change it to "Use MFC in a Shared DLL", and in the C\C++ tab, for Code Generation, use "Multithread DLL". These changes have to be set for both the Debug and Release configurations. The functions WinMain, MyRegisterClass, InitInstance, and WndProc were created by the Visual Studio C++ Wizard. The rest of the code is added. A change to the message loop is necessary, I'm using keyboard accelerators. For the message loop to handle them and open the corresponding menu item, the following change is required:

int APIENTRY WinMain(...)
{
    HACCEL hAccelTable;
    MSG msg;
    //

    // Main message loop

    //

    hAccelTable = LoadAccelerators (hInstance, szWindowClass);
    while (GetMessage (&msg, NULL, 0, 0))
    {
       if (!TranslateAccelerator (msg.hwnd, hAccelTable, &msg))
       {
           TranslateMessage(&msg);
           DispatchMessage(&msg);
       }
   }
   return msg.wParam;
}

Here is the function that gets pixels from the screen and creates an image array:

bool CreateImageFromArray(HDC hdc) 
{
    long w = m_nWidth;
    long h = m_nHeight;
    BYTE c = m_nBitsPerPixel/8;  // bitsperpixel.

    
    BYTE* pArray = (BYTE*)malloc(w*h*c);
    int cxCursor = cxClient/2 - w/2;
    int cyCursor = cyClient/2 - h/2;
    int cxCapture = cxCursor;
    int cyCapture = cyCursor;
    
    //  ==========================================

    //  Get the device context of the desktop

    //  and from it get the color 

    //  of the pixel at the current position.

    //  ==========================================

    
    BYTE rVal, gVal, bVal;
    COLORREF m_color;
    
    int n, x, y;
    
    obTimer.Start();
    
    for (y = 0; y < h; y++)
    {
        for (x = 0; x < w; x++)
        {
            m_color = ::GetPixel(hdc, cxCapture, cyCapture);
            rVal = GetRValue(m_color);
            gVal = GetGValue(m_color);
            bVal = GetBValue(m_color);
            n = c* (x + w*y);
            if (c == 4)    // if bitsperpixel = 32.

                pArray[n + 3] = (BYTE)(0);
            pArray[n + 2] = (BYTE)(rVal);
            pArray[n + 1] = (BYTE)(gVal);
            pArray[n]     = (BYTE)(bVal);
            
            cxCapture = cxCursor + x;
            cyCapture = cyCursor + y;
        }
    }
    CreateFromArray(pArray, w, h, 8*c, c*w, true);
    
    free(pArray);

A problem I encountered when using Afx support in a non-MFC generated program is, in Debug mode, when stepping into CFileDialog(...), you will get an exception from AfxGetResourceHandle(). I found a hack for this in the MSDN Library:

BOOL PromptForFileName(BOOL bOpenFileDialog)
{
    // Hack for AfxGetResourceHandle Exception

    HMODULE hMod;
    hMod = ::GetModuleHandle("BitmapFromScreen.exe");
    AfxSetResourceHandle(hMod);
    
    BOOL bRet;
    CString szFilter = "Windows Bitmap Files" 
                       " (*.BMP; *.DIB) |*.BMP; *.DIB||";
    LPSTR title;
    if (bOpenFileDialog)
    {
        title = "Open image file";
        CFileDialog dlg(TRUE, _T("bmp"), _T("*.bmp"),
            OFN_FILEMUSTEXIST | OFN_HIDEREADONLY, szFilter);
        dlg.m_ofn.lpstrTitle = title;
        CenterWindow(dlg.m_hWnd);
        if (dlg.DoModal() == IDOK)
        ...
        ...
}

Points of Interest

The source files for DIBBLE are all "C". When converted to "C++" to compile them, several differences show up, VOID* is not handled in "C++", and all calls to malloc have to be cast to the appropriate type. All variables are not initialized to zero. The "C" routine qsort doesn't compile in "C++" again because of the VOID* in the Compare callback. I wrote a replacement for qsort:

// Sorts the array of structures

// in decending order, largest first.

VOID DibSortPal(BOXES* boxes, int iEntry)
{
    int nCmp;
    BOXES box;
    for (int i=0; i<iEntry; i++)
        for (int j=0; j<iEntry; j++)
        {
            nCmp = boxes[j].iBoxCount - boxes[j+1].iBoxCount;
            if (nCmp < 0)
            {
                box.iBoxCount  = boxes[j].iBoxCount;
                box.rgbBoxAv = boxes[j].rgbBoxAv;
                boxes[j].iBoxCount = boxes[j+1].iBoxCount;
                boxes[j].rgbBoxAv = boxes[j+1].rgbBoxAv;
                boxes[j+1].iBoxCount = box.iBoxCount;
                boxes[j+1].rgbBoxAv = box.rgbBoxAv;
            }
        }
}

The next problem was an error made by the author of DIBHELP.C, he has this code:

     int cEntries = 0 ;

     if (cColors != 0)
          cEntries = cColors ;
     else if (cBits <= 8)
          cEntries = 1 << cBits ;

     // here is the error, if cColors = 0 and cBits

     // is greater than 8, cEntries will = 0

     // the sizeof BITMAPINFOHEADER = 40 + 0 - 1 = - 1 * sizeof of RGBQUAD = 4

     // dwInfoSize will = 36, not 40

     dwInfoSize = sizeof (BITMAPINFOHEADER) + 
                  (cEntries - 1) * sizeof (RGBQUAD) ;
     // the fix

     dwInfoSize = sizeof (BITMAPINFOHEADER) + (cEntries - 1) * 
                  sizeof (RGBQUAD) + sizeof (RGBQUAD) ; // = 40

     // this call to malloc will alocate only 36 bytes.

     if (NULL == (pbmi = malloc (dwInfoSize)))
     {
          return NULL ;
     }
     // then:

     pbmi->bmiHeader.biSize = sizeof (BITMAPINFOHEADER) ; // this is 40.

     ...
     ...
     free (pbmi)
     // Heap corruption, the previous

     // operation wrote past the end of the buffer.

The first time I executed this code, cEntries was defined as int cEntries; carried over from "C", therefore cEntries was equal to 0xcdcdcdcd. It tried to write 300+ MB to the Page File.

Credits

  • Charles Petzold - "Programming Windows - Fifth Edition".

History

  • February 8, 2006 - Version 1.0.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here