Avatar of PMH4514
PMH4514 asked on

C++ bitmap question

I have this chunk of code that writes out a bmp file from supplied data. (See snippet) It functions propertly.

How is this code best transformed so that instead it returns an HBITMAP for use in MFC, given the exact same parameters? (minus the filename of course)

int write8BitBmpFile(char *filename, unsigned int width, unsigned int height, 
					 unsigned char *image)
{
	BITMAPINFOHEADER bmpInfoHeader;
	BITMAPFILEHEADER bmpFileHeader;
	FILE *filep;
	unsigned int row;
	unsigned int extrabytes, bytesize;
	unsigned char *paddedImage = NULL;
	RGBQUAD palette[256];
	unsigned int i;
	unsigned int numPaletteEntries = 256;
 
 
	// Create the palette - each pixel is an index into the palette
	for (i = 0; i < numPaletteEntries; i++) {
		palette[i].rgbRed = i;
		palette[i].rgbGreen = i;
		palette[i].rgbBlue = i;
		palette[i].rgbReserved = 0;
	}
 
 
 
	/* The .bmp format requires that the image data is aligned on a 4 byte boundary.  For 8 - bit bitmaps,
	this means that the width of the bitmap must be a multiple of 4. This code determines
	the extra padding needed to meet this requirement. */
	extrabytes = (4 - width % 4) % 4;
 
 
	// This is the size of the padded bitmap
	bytesize = (width + extrabytes) * height;
 
 
	// Fill the bitmap file header structure
	bmpFileHeader.bfType = 'MB';   // Bitmap header
	bmpFileHeader.bfSize = 0;      // This can be 0 for BI_RGB bitmaps
	bmpFileHeader.bfReserved1 = 0;
	bmpFileHeader.bfReserved2 = 0;
	bmpFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * numPaletteEntries;
 
 
	// Fill the bitmap info structure
	bmpInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
	bmpInfoHeader.biWidth = width;
	bmpInfoHeader.biHeight = height;
	bmpInfoHeader.biPlanes = 1;
	bmpInfoHeader.biBitCount = 8;            // 8 - bit bitmap
	bmpInfoHeader.biCompression = BI_RGB;
	bmpInfoHeader.biSizeImage = bytesize;     // includes padding for 4 byte alignment
	bmpInfoHeader.biXPelsPerMeter = 0;
	bmpInfoHeader.biYPelsPerMeter = 0;
	bmpInfoHeader.biClrUsed = numPaletteEntries;
	bmpInfoHeader.biClrImportant = 0;
 
 
 
 
	// Open file
	if ((filep = fopen(filename, "wb")) == NULL) {
		printf("Error opening file %s\n", filename);
		return FALSE;
	}
 
 
	// Write bmp file header
	if (fwrite(&bmpFileHeader, 1, sizeof(BITMAPFILEHEADER), filep) < sizeof(BITMAPFILEHEADER)) {
		printf("Error writing bitmap file header\n");
		fclose(filep);
		return FALSE;
	}
 
 
	// Write bmp info header
	if (fwrite(&bmpInfoHeader, 1, sizeof(BITMAPINFOHEADER), filep) < sizeof(BITMAPINFOHEADER)) {
		printf("Error writing bitmap info header\n");
		fclose(filep);
		return FALSE;
	}
 
 
	// Write bmp palette
	if (fwrite(palette, 1, numPaletteEntries * sizeof(RGBQUAD), filep) < numPaletteEntries * sizeof(RGBQUAD)) {
		printf("Error writing bitmap palette\n");
		fclose(filep);
		return FALSE;
	}
 
 
	// Allocate memory for some temporary storage
	paddedImage = (unsigned char *)calloc(sizeof(unsigned char), bytesize);
	if (paddedImage == NULL) {
		printf("Error allocating memory \n");
		fclose(filep);
		return FALSE;
	}
 
 
	/* Flip image - bmp format is upside down.  Also pad the paddedImage array so that the number
	of pixels is aligned on a 4 byte boundary. */
	for (row = 0; row < height; row++)
		memcpy(&paddedImage[row * (width + extrabytes)], &image[(height - 1 - row) * width], width);
 
	// Write bmp data
	if (fwrite(paddedImage, 1, bytesize, filep) < bytesize) {
		printf("Error writing bitmap data\n");
		free(paddedImage);
		fclose(filep);
		return FALSE;
	}
 
 
	// Close file
	fclose(filep);
	free(paddedImage);
	return TRUE;
}

Open in new window

System ProgrammingC++

Avatar of undefined
Last Comment
PMH4514

8/22/2022 - Mon
ASKER CERTIFIED SOLUTION
JohnGaby

Log in or sign up to see answer
Become an EE member today7-DAY FREE TRIAL
Members can start a 7-Day Free trial then enjoy unlimited access to the platform
Sign up - Free for 7 days
or
Learn why we charge membership fees
We get it - no one likes a content blocker. Take one extra minute and find out why we block content.
See how we're fighting big data
Not exactly the question you had in mind?
Sign up for an EE membership and get your own personalized solution. With an EE membership, you can ask unlimited troubleshooting, research, or opinion questions.
ask a question
ASKER
PMH4514

I'm sorry I was not clear in my first post.

I am in need of a way to take the buffer data that code snippet saves, and create an HBITMAP, doing so without the use of CreateDIBSection or SetBitmapBits. I don't know if there is a way. This is part of narrowing down a weird problem in an imaging application I'm trying to debug (the details of which I'm sure are outside the scope of this thread I think.)

thanks!
JohnGaby

I guess I don't understand.  You want to create a HBITMAP from data, but are not willing to use the API functions that do that?  What is the problem with using those functions?  If you really MUST do it without them, you could use GDI+, but that would be a really round about way (and I suspect that ultimately GDI+ would be calling one of those two functions anyway).
ASKER
PMH4514

Ok I'll give more high level detail of the problem. I have an imaging app - hooked to a camera. The camera DLL exposes an  interface to set the size of the frame buffer it will use (I believe they are using a circular buffer as that is how it behaves)  It also exposes a  method to retrieve the index into their buffer of the last completed frame.  Now, on my side, the "CameraThread" simply loops asking for the index of the last completed frame. It then sequentially retrieves into a locally scoped array,  the pixel data  (8bit grayscale) starting at the address of the index.  Using a for loop, the data for that frame is copied from the ring buffer in hardware to a local array of pixel data. This array is what I can successfully pass as unsigned char* image to the write8BitBmpFile(..) method in the snippet of my first post.  When I put a call to this write method (for testing only of course) inside the camera thread, immediately after I've copied data out of hardware's buffer, I end up with a folder full of bitmap files and all of them look "good" thus verifying the integrity of the data as it came off hardware.

Now, in my software code, immediately after I successfully copy the buffer from hardware, I instantiate what I call a "CFramePacket" which is essentially an HBITMAP wrapped with data and methods to allow filtering, drawing surfaces etc.. prior to display. For the moment, all of that extra stuff has been turned off as part of the debug process. So  CFramePacket is instantiated with a pointer to the buffer that contains known good image data.

This is where I then use CreateDIBSection and SetDIBits (see snippet) to create an HBITMAP populated with the image data coming in to the frame packet's constructor.

The problem is, after that point in time (debugging has brought me to this part of the code with little doubt, ) the HBITMAP ends up containing a band across it, approximately 25% of the height and full width,  varying locations vertically but mostly in the upper half. This "band" is not black or white, or junk data, it is a band of actual image data from a previous frame (I believe it is a previous frame but have not verified this - as opposed to a "next frame" still in their ring buffer that i have yet to grab..)

It's very odd. I've tied everything into an oscilloscope to check timing against hardware and the code is processing everything well faster than frame-rate and is lined up properly.  Somehow, during the creation of HBITMAP, I am getting a band of pixel data from another image in memory.

That said, since the write8bitbmp() file in my first post seemed to successfully turn the data into a bitmap, I though perhaps there would be a way to create an HBITMAP without using those two API calls so as to have another test in trying to fix this problem.

I have included further notes in comments in the attached snippet.

UINT8 image[1000][1000];
UINT16 *ImagePtr;
ImagePtr=(UINT16*)FrameBuffers[(Idx)]; // hardware's ring buffer
 
Idx=WaitForNextFrame();
ImagePtr=(UINT16*)FrameBuffers[(Idx)];
 
UINT8 *optr = (UINT8*)image;
for(int h=0;h<1000;h++)
   for(int w=0;w<1000;w++)
   {
      // we use only 8bits of the 10bit capture
      *optr ++=(UINT8)( *ImagePtr++>>2); 
   } 
 
 
// note: a call to write8BitBmpFile() right here 
// with 'image' as the buffer saves good images consistently.
 
// packet is instantiated prior. removed for clarity
pPacket->InitializeDrawingSurface((BYTE*)image);
 
//******************
 
void CFramePacket::InitializeDrawingSurface(BYTE* pData)
{
   void* buffer = NULL; 
 
   // create a DIBSection.. (m_hbmFrame is a member HBITMAP)
   m_hbmFrame = ::CreateDIBSection(0, m_pbmInfo, DIB_RGB_COLORS, &buffer, NULL, 0); 
 
   int iSetCount = ::SetBitmapBits(m_hbmFrame, BufferSize(),(void*)a_pBuffer);
 
   // NOTE:
   // I have another tested SaveBitmap method 
   // that takes an HBITMAP rather than the pointer 
   // to a buffer. If I call it here as I did testing
   // immediately after my buffer copy, many of my frames
   // are just fine, but at least 1 out of every 9 frames
   // (9fps is the framerate) has this band of data from 
   // a subsequent image. I can tell this because each subsequent
   // image is captured with a different hardware filter, so while
   // I'm capturing images of the same object, each looks different   
   // which is what makes the problem obvious when it's displayed. 
   
}

Open in new window

All of life is about relationships, and EE has made a viirtual community a real community. It lifts everyone's boat
William Peck
ASKER
PMH4514

sorry there is an extra:
ImagePtr=(UINT16*)FrameBuffers[(Idx)]; // hardware's ring buffer

in the top of my snippet that is not really there in the app.
JohnGaby

Let me see if I understand what you are saying.  You retrieve the bitmap data from the device and copy it into your own buffer.  If you then take that buffer and save it as a .bmp file (using the code your first posted), and then look at the resulting file, the image is correct.

However, if you take the SAME data and use CreateDIBSection, and then display the resulting HBITMAP, you see the vertical bar you described?

Is this correct?  If so, then it must mean that you are not creating the HBITMAP correctly.

When you use CreateDIBSection, you do not need to use the SetBitmapBits to set the data.  The CreateDIBSection returns a pointer to where the actual bitmap data is stored, and you can copy your data directly to that buffer.

When you copy the data, however,  you must ensure that each scan line is padded with zeros so that it ends on a LONG data-type boundary.  Perhaps that is your problem?
ASKER
PMH4514

>>However, if you take the SAME data and use CreateDIBSection, and then display the
>>resulting HBITMAP, >>you see the vertical bar you described?
>>Is this correct?  If so, then it must mean that you are not creating the HBITMAP correctly.

Yes, that is exactly correct.

>>When you copy the data, however,  you must ensure that each scan line is
>>padded with zeros so that it ends on a LONG data-type boundary.
>> Perhaps that is your problem?

Quite possibly. Thus my original post. I noticed that code was dealing with padding scan lines (an area I am unfortunately not near an expert in).

Can you show me an example of how I would CreateDIBSection as you suggested without calling SetBitmapBits?   Would it be more or less like the copy of into the buffer in the 2nd set of snippets?

Get an unlimited membership to EE for less than $4 a week.
Unlimited question asking, solutions, articles and more.
ASKER
PMH4514

caveat to "yes that is exactly correct" -  remember that not every image is bad - the banding only occurs every 3-9 frames or so (it is not a repeatable pattern).. if CreateDIBSection were being used improperly, wouldn't all of my images look bad?
ASKER
PMH4514

oh one more caveat (sorry) you wrote:

>>you see the vertical bar you described?

it's not a vertical bar, it's a horizontal "band" of actual data from a previous frame.
JohnGaby

If you did not have the scan lines aligned properly, then yes, all of the images would be incorrect (assuming that they were all the same width).  So it doesn't sound like that is your problem.  Are you absolutely sure that the frames that are bad when converted to a HBITMAP are correct when you write them to a file.   Have you tried saving the raw data to a file, so you can analyze it after the fact?
Experts Exchange is like having an extremely knowledgeable team sitting and waiting for your call. Couldn't do my job half as well as I do without it!
James Murphy
ASKER
PMH4514

Actually.. I take this all back. We just ran another set of better controlled tests and in fact the band is present in the data immediately following our copying it out of the buffer.

the odd part though is that we have, literally, everything else in the application turned off and the code we are using to pull the frames off exactly matches that in a sample the hardware vendor provided. theirs shows good results, ours shows the incremental banding.

so.. not sure where that leaves us as far as this Experts Exchange thread, because no, I am no longer convinced as I was that the HBITMAP creation played any role.  

I'll try to follow up soon.
ASKER
PMH4514

this problem went away through some judicious refactoring of the code. accepting your first answer, as, well CreateDibSection is the answer to the question as I asked it :)