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;
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;
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;
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)
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)
{
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
:
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 ;
dwInfoSize = sizeof (BITMAPINFOHEADER) +
(cEntries - 1) * sizeof (RGBQUAD) ;
dwInfoSize = sizeof (BITMAPINFOHEADER) + (cEntries - 1) *
sizeof (RGBQUAD) + sizeof (RGBQUAD) ;
if (NULL == (pbmi = malloc (dwInfoSize)))
{
return NULL ;
}
pbmi->bmiHeader.biSize = sizeof (BITMAPINFOHEADER) ;
...
...
free (pbmi)
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.