A Quick and Simple Way to Display a Bitmap in C++

DanRollins
CERTIFIED EXPERT
Published:
Updated:
Perhaps the hardest part of working with bitmap image data is that while doing the programming, it looks like random bytes rather than people or houses.  

Here at Experts-Exchange, I've worked on a number of questions along the lines of "I have binary image data from source X, and I need to do Y with it."  Finding a solution often requires intermediate steps:  Tweak the data and look at the result as an image to see if the tweaks went the right way.

This talks about that problem, but it also describes a simple way to display any bitmap in your MFC programs.

Example:
I am getting a stream of binary data from a barcode reader, and I want to display it.  I'm told that it is monochrome image data -- 1 bit per pixel (bpp) -- but I don't know how to turn it into a usable HBITMAP.  Can you help?

The asker posted a binary file containing the raw data.  I was told that the image was 364 wide by 212 high.

As a starting point, I did what I always do:  Create a dialog-based Win32 Application with MFC support.  I added a button and an OnClick handler, and pounded out some code to read the data into memory:
void CBitmapTesterDlg::OnBnClickedButton1() 
                      {
                      	int nWide= 364;
                      	int nHigh= 212;
                      	int nDataLen= (nWide/8) * nHigh;  // amount of data to read
                       	//--------------------------------- read the data into memory
                      	BYTE* pabBits=    new BYTE[nDataLen];
                      	CFile cFile( L"c:\\temp\\bits.bin", CFile::modeRead );
                      	int n= cFile.Read(	pabBits, nDataLen );
                      	cFile.Close();
                       	//--------------------------------- create a monochrome bitmap
                      	HBITMAP hBmp= ::CreateBitmap( nWide, nHigh, 1, 1, pabBits );
                      	//  NOW what?!??!?!?
                      	delete pabBits;           // cleanup allocation
                      }

Open in new window

I can breakpoint on line 12 and use the Visual Studio memory-viewing window to see:
Fig 1.  Rawdata in the Memory viewer...which actually looks promising:  It looks like it could be monochrome bitmap data.  I know that the ff values are each a set of eight black (foreground color) pixels.  A 00 value is a set of eight white (background color) pixels and other values will be a sequence of eight pixels, some black and some white.

But where do I go from here?  I need to see the image before I can make any progress.   Here's how I proceed:

1. Add a Static Picture Control

In the dialog editor, I added a Picture control:
Fig 2.  Adding a picture control

2. Set Control Properties

Right-click the control, choose Properties, and set them to:
ID:             IDC_Image
Type:         Bitmap

3. Create a Control-type Variable

Right-click on the control (the little cactus icon) and select Add Variable.....  Set:
Variable name:  m_ctlBmp

Leave all of the other settings at their defaults.
Click [Finish]

If you look into the .H file, you'll now see the new object declaration:
      CStatic m_ctlBmp;
and the top of the CPP files has a new element in the DoDataExchange() function:
      DDX_Control(pDX, IDC_Image, m_ctlBmp);
Thus, we now have a member variable that gives us access to CStatic member functions.  One of them is SetBitmap().  So...

4. Modify the Program to Display the Bitmap

In the above code snippet,  replace the
       //  NOW what?!??!?!?
line with
 	m_ctlBmp.SetBitmap( hBmp ); // this lets me see the result

Open in new window

Run the program, click [Button1] and see this result:
Fig 3.  First pass result
Now I can see the problem that needs to be solved:

        The diagonal characteristic is symptomatic of horizontal misalignment.  
        The width must be wrong.  

So I use my "programming by debugger" technique -- tweak the code and run it -- trying different widths.  (Note: There was a time when a rebuild used to take minutes or even hours, but these days. a program like this is running by the time you lift your finger from the mouse button :-).

I could see that I was getting closer as I increased the width, but I had to go way wide -- to 384 -- before the scanlines would resolve into a clean image.  Eventually, I remembered that bitmap scanlines are always a multiple of four bytes (DWORD) in length.  I had been told that the width was 364 pixels, and 364 looks like a nice multiple of four, but it is not! (at least when counting by 8s -:)  That would make each line 45.5 bytes wide.  The next multiple of four above 45.5 is 48, so I know that each scanline will contain 48 bytes of data.  I add-in some generic code to calculate the needed values  (in multiple steps rather than all in one confusing formula, just because that's the way I roll at EE).  Modify the code like so:
void CBitmapTesterDlg::OnBnClickedButton1()
                      {
                      	int nWide= 364;
                      	int nHigh= 212;
                       
                      	//-------------------- account for line length needing to be 32-bit chunks
                       	int nDwordsPerLine= (nWide + 31)/32;   // rounds up
                      	int nBytesPerLine=  nDwordsPerLine * 4;
                      	int nPixelsPerLine= nBytesPerLine  * 8;
                       
                      	int nDataLen= nBytesPerLine * nHigh;  // how much of the file to read
                       
                      	BYTE* pabBits=    new BYTE[nDataLen];
                      	CFile cFile(L"c:\\temp\\bits.bin", CFile::modeRead );
                      	int n= cFile.Read(	pabBits, nDataLen );
                      	cFile.Close();
                      
                      	HBITMAP hBmp= ::CreateBitmap( nPixelsPerLine, nHigh, 1, 1, pabBits );
                      
                       	m_ctlBmp.SetBitmap( hBmp ); // display the result
                      
                      	delete pabBits;           // cleanup allocations
                      }

Open in new window

FIg 4.  After fixing the widthAnd the problem is clearly solved!
(Well, almost!  That image turned out to be upside down, but changing that was a minor issue. See the EE question for the fix or if you want to download the binary image data file).

Review:
To quickly begin writing program testing code, create a dialog-based application with MFC support.
The memory-viewing window can be used as a starting point to see the data as a series of bytes, but that's not enough!
To quickly be able to see a bitmap during your testing:  Add a STATIC Picture Control to the dialog and a control-type variable.  Then you can view any bitmap (including one you create from raw data) by using that control's SetBitmap() function.
References:

CreateBitmap Function
http://msdn.microsoft.com/en-us/library/dd183485(VS.85).aspx

CStatic::SetBitmap
http://msdn.microsoft.com/en-us/library/aa314164(VS.60).aspx

Bitmaps (Overview)
http://msdn.microsoft.com/en-us/library/dd183377(VS.85).aspx
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
If you liked this article and want to see more from this author,  please click the Yes button near the:
      Was this article helpful?
label that is just below and to the right of this text.   Thanks!
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
2
10,881 Views
DanRollins
CERTIFIED EXPERT

Comments (3)

kk8

Commented:
CImage MFC seems more straightforward
CERTIFIED EXPERT
Author of the Year 2009

Author

Commented:
CImage is a useful tool.  However, it does not provide a means to create an image from raw data (one needs to use the lower-level bitmap functions described here anyway) and it does not automatically display a bitmap (the key point illustrated here).  Thus, using it would not be any more "straightforward" -- more concise, easier to understand, or requiring fewer steps -- than what is shown here.  But I really do appreciate well-thought-out, detailed criticism, so thanks for your comment!

Commented:
Maybe, because I like CreateDibSection and use it everywhere, but I didn't know about such way with the CreateBitmap. Thanks.
Of course you remember about GetDiBits/SetDIBits function.
BYTE* pabBits=    new BYTE[nDataLen];
...
HBITMAP hBmp= ::CreateBitmap( nPixelsPerLine, nHigh, 1, 1, pabBits );

Open in new window

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.