Bitmap From Screen






2.98/5 (14 votes)
Feb 7, 2006
4 min read

80907

1595
The anatomy of a bitmap.
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.