Jump to content
  • Advertisement
Sign in to follow this  
thedirt

Write bitmap file

This topic is 3939 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I am creating a program and need the ability to write bmp files. It works mostly, but the bmp it creates is a little off. output (magnified 400x) See that green pixel on bottom left? Well it's not supposed to be there. I think it starts reading the pixel data one or two bytes early. My code is here:
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <fstream>
#include <stdio.h>

int saveBitmap( BYTE *bits, LONG width, LONG height, WORD bpp, LPCTSTR filename )
{
	using namespace std;
	BITMAPINFOHEADER infoheader = {0};
	BITMAPFILEHEADER fileheader = {0};

	infoheader.biSize = sizeof( BITMAPINFOHEADER );
	infoheader.biBitCount = bpp;
	infoheader.biClrImportant = 0;
	infoheader.biClrUsed = 0;
	infoheader.biCompression = BI_RGB;
	infoheader.biWidth = width;
	infoheader.biHeight = height;
	infoheader.biPlanes = 1;
	infoheader.biSizeImage = width * height * ( bpp / 8 );

	fileheader.bfType = 0x4D42;
	fileheader.bfOffBits = sizeof( BITMAPINFOHEADER ) + sizeof( BITMAPFILEHEADER );
	
	fileheader.bfSize = fileheader.bfOffBits + infoheader.biSizeImage; //

	HANDLE output = CreateFile( filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
	if( !output )
		return -1;

	DWORD written = 0;

	WriteFile( output, &fileheader, sizeof( BITMAPFILEHEADER ), &written, NULL );
	WriteFile( output, &infoheader, sizeof( BITMAPINFOHEADER ), &written, NULL );
	WriteFile( output, &bits, infoheader.biSizeImage, &written, NULL );
	
	return 1;
	CloseHandle( output );
	

}

int main()
{
	BYTE data[192] = { 0,0,0,   0,0,0,   0,0,0,   0,0,0,   0,0,0,   0,0,0,   0,0,0,   0,0,0, 
					   0,0,0,   0,255,0,   0,255,0,   0,0,0,   0,0,0,   0,255,0,   0,255,0,   0,0,0, 
				       0,0,0,   0,255,0,   0,255,0,   0,0,0,   0,0,0,   0,255,0,   0,255,0,   0,0,0,   
					   0,0,0,   0,0,0,   0,0,0,   0,0,0,   0,0,0,   0,0,0,   0,0,0,   0,0,0,
					   0,0,0,   0,0,0,   0,0,0,   0,0,0,   0,0,0,   0,0,0,   0,0,0,   0,0,0, 
					   0,0,0,   0,0,0,   0,255,0,   0,255,0,   0,255,0,   0,255,0,   0,0,0,   0,0,0, 
				       0,0,0,   0,0,0,   0,0,0,   0,255,0,   0,255,0,   0,0,0,   0,0,0,   0,0,0,   
					   0,0,0,   0,0,0,   0,0,0,   0,0,0,   0,0,0,   0,0,0,   0,0,0,   0,0,0 };

	return saveBitmap( data, 8, 8, 24, L"output.bmp" );
}

I was hoping someone would be able to catch my stupid mistake that I spent over an hour on. Thank you.

Share this post


Link to post
Share on other sites
Advertisement
I am not using gdi+ for simplicity. All I need is Bmp output, and honestly, I don't consider that much code at all.

Share this post


Link to post
Share on other sites
I don't thing that's a problem Si, I mean, its not like BMP is a terribly complicated format(*1), and its surely a learning experience by the sounds of things. If all the OP needs is BMP support, then fixing his code is most likely the easier route. If it were something more complicated, or he were further from the goal than he is, I would agree, but I don't think that's warranted in this case.

So anyhow, lets get to the problem! First, observe three facts:
  1. The sample bitmap you linked displays upside-down.

  2. It is also shifted right by one pixel.

  3. It is red, rather than green, as your source data would indicate.


Now, also understand that bitmaps are commonly stored upside-down (because someone decided it should be that way many years ago, to the bane of everyone else.)

These four observations reveal that there is extra data being written prior to the image data. I suspect that there are two extra bytes being written, due to the shade of the blue-green pixel and the fact that windows stores color data in BGR order rather than RGB. If two extra bytes were written, it would have the effect of mis-aligning the color data such that the colors would be effected in a way which would happen to turn green pixels red (can you sense that I'm getting warm?)

Since I don't see anything wrong with the file-writing itself, and the rest of the logic seems sound this leads me to two possible conclusions: either your structures are not packed or one of the structure members is two bytes too big (ie an int that should be a short.) Both of these would cause two extra bytes to be written. My bet is on biPlanes or biBitCount being an int, since only the file header struct would need to be padded out, and any problems there would manifest in the bitmap header, rather than the image data.

I'm about 95% certain that biPlanes or biBitCount (probably the latter) is an int. Fix your bitmap info header, and the problem should be resolved.

(*1) -- The BMP format encompasses several variations for bit depth and also (IIRC) allows for RLE encoding of image data. However, so long as you need a well-defined subset of these features it shouldn't present any problem. Even supporting all variations is not a problem for an intermediate coder.

[Edited by - ravyne2001 on January 9, 2008 5:11:42 PM]

Share this post


Link to post
Share on other sites
That would be a far more compelling argument if you were also able to point out what the problem was in his bitmap saving code was. In comparison, here's the GDI+ version of that code:

#include <windows.h>
#include <gdiplus.h>

using namespace Gdiplus;

int main(void) {
GdiplusStartupInput startup;
ULONG_PTR token;
GdiplusStartup(&token, &startup, NULL);
{
BYTE data[192] = {
0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0,
0,0,0, 0,255,0, 0,255,0, 0,0,0, 0,0,0, 0,255,0, 0,255,0, 0,0,0,
0,0,0, 0,255,0, 0,255,0, 0,0,0, 0,0,0, 0,255,0, 0,255,0, 0,0,0,
0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0,
0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0,
0,0,0, 0,0,0, 0,255,0, 0,255,0, 0,255,0, 0,255,0, 0,0,0, 0,0,0,
0,0,0, 0,0,0, 0,0,0, 0,255,0, 0,255,0, 0,0,0, 0,0,0, 0,0,0,
0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0 };
Bitmap bitmap(8, 8, 24, PixelFormat24bppRGB, data);
static const CLSID clsid =
{ 0x557CF400, 0x1A04, 0x11D3, { 0x9A, 0x73, 0x00, 0x00, 0xF8, 0x1E, 0xF3, 0x2E } };
bitmap.Save(L"output.bmp", &clsid);
}
GdiplusShutdown(token);
return 0;
}


The hardest part about that was looking up the CLSID for the bitmap format. Given that you haven't been able to say what the problem with his original code was, I'm going to say that switching to GDI+ is easier than debugging his bitmap saving routine. You'll also note that the entire program, is actually fewer lines of code than just the bitmap saving routine in the original program.

Share this post


Link to post
Share on other sites
writing bmp files can be quite trivial, such that it can be too much pain
for little gain if you decide to do it yourself and don't get it write first time, not to mention the reduction of any kudos you may have had in doing so.

So lets swap notes ;):
There is not enough info be be sure why your's doesn't work, so listed below
is an (un-buggy code, as far as I know [so far]) example of very specifically saving( hence some nasty hard coding) an image as a 24 bit bmp on/for Windows 3.1x, 95, NT.....

What I can see in your code ,however, is that you seem to be 'not' rounding the
Bitmap Data Size to the next 4 byte boundary (as per specs). And in the case of BYTE data[192] ,
where 8*8*3 + 54 = 192 + 54(size of header stuff) = 246
and 246%4 = 2!
so need to round up file size to 248? (how does that look..concentration is waning).

By all means use any of the code below as you wish (NB: but at you own risk!)




bool WWriteBMP(char *filename, int width, int height, unsigned char *outImage)
{

int colorMode = 3;
unsigned char tempColors = 0;
FILE *pFile = 0;
char FileType[2] = {'B','M'};
int totalbytes = width * height * 3 + 54;
int FileSize = width * height * 3 + 54;
int mod = totalbytes%4;
// Rounded size to the next 4 byte boundary if needed.
if(mod != 0)
FileSize = totalbytes - mod + 4;
int Reserved = 0;
int DataOffset = 54;
int HeaderSize = 40;
int Width = width;
int Height = height;
short Planes = 1;
short BitsPerPixel = 24;
int Compression = 0; // 0 == BI_RGB
int ImageSize = 0;
int XPelsPerMeter = 0;
int YPelsPerMeter = 0;
int UsedColors = 0;
int ImportantColors = 0;

// Open file for output.
pFile = fopen(filename, "wb");

// Check if the file opened or not.
if(!pFile) { fclose(pFile); return false; }

fwrite(&FileType, 1, 2, pFile);
fwrite(&FileSize, 1, 4, pFile);
fwrite(&Reserved, 1, 4, pFile);
fwrite(&DataOffset, 1, 4, pFile);
fwrite(&HeaderSize, 1, 4, pFile);
fwrite(&Width, 1, 4, pFile);
fwrite(&Height, 1, 4, pFile);
fwrite(&Planes, 1, 2, pFile);
fwrite(&BitsPerPixel, 1, 2, pFile);
fwrite(&Compression, 1, 4, pFile);
fwrite(&ImageSize, 1, 4, pFile);
fwrite(&XPelsPerMeter, 1, 4, pFile);
fwrite(&YPelsPerMeter, 1, 4, pFile);
fwrite(&UsedColors, 1, 4, pFile);
fwrite(&ImportantColors, 1, 4, pFile);

// Now switch image from RGB to BGR.
for(int index = 0; index < width * height * colorMode; index += colorMode)
{
tempColors = outImage[index];
outImage[index] = outImage[index + 2];
outImage[index + 2] = tempColors;
}
// Finally write the image.
fwrite(outImage, width * height * 3, 1, pFile);

fclose(pFile);

return true;
}





[Edited by - steven katic on January 9, 2008 5:59:13 PM]

Share this post


Link to post
Share on other sites
So, for the record, I added my diagnosis *after* SiCrane responded to my response. Also for the record, I agree in principle that just using the library is the preferred approach, but disagree in this case because the OP clearly has a single, very small problem in existing code that seemed easily discoverable on its face. I also assumed that the OP had a reason for avoiding the library, whatever that is, and were he ready to give up on his solution in favor of a library, he would have asked for one, rather than help solving his problem.

Assuming my diagnosis is correct, the error turns out to be small, though rather nefarious and rather well hidden if you're not familiar with a number of intricacies involving the bmp format, and even then took me 15 minutes or so to track down (though, it would have been a lot shorter had the definition of the header structs been provided, and saved me a lot of deduction and inference.)


Also, another note for the OP:

Whether you are unaware or simply ignoring it for now, the code as is will not work for images in the general case because your row data is not padded to a multiple of 4 bytes and your existing code does not provide a means to do so. Your test case happens to work since 8 times 3 is 24, which just happens to be a multiple of 4. As it stands, for 24 bit images, your code will not work correctly for any bitmap with an odd number as the horizontal dimension and only every other even horizontal dimension. Basically, it only works for images which have dimensions that are a multiple of 4. If you wish to correct this, your solution will not be able to simply write the entire bits buffer out; instead, you will have to write <width> bits, then pad the ends until it is a multiple of 4. This also affects the calculation of your bfSize member.

Share this post


Link to post
Share on other sites
Steven, I'm pretty certain your understanding of the padding issue is mistaken. The format spec indicates that individual *rows* must be padded to multiples of 4, I'm not aware of anything which states that the file, as a whole, must be.

Share this post


Link to post
Share on other sites
Quote:

Steven, I'm pretty certain your understanding of the padding issue is mistaken. The format spec indicates that individual *rows* must be padded to multiples of 4, I'm not aware of anything which states that the file, as a whole, must be.


you have the specs there?

0022h Bitmap Data Size 1 dword Size of the bitmap data in bytes. This number must be rounded to the next 4 byte boundary.


But, does that not ultimately affect the actual file size?
(i.e. 0002h File Size 1 dword Complete file size in bytes.)

Unfortunately for me it is implied, where the specs are not explicit.
More importantly it does work (even though I am not happy about it).


And this is an example of exactly SiCane's point
(That is the best advice: i.e. avoid saving bmp yourself where possible?)

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!