C++ - Bitmap Tutorial: Loading and Saving Bitmaps


1. Introduction

The windows bitmap file format (.bmp) is the most widely used image file format on windows (next to .jpg), and there are many occasions a program or game has to be able to load or save bitmaps ( raytracers and other non-realtime renderers should be able to save their output in .bmp format, games might have to load them as textures etc. ).
Unfortunately .bmp files are not as straightforward as for example .png image files and provide quite a problem for newbies since it's not that easy to figure out how to use them when without a library or API.

Two notes before we start:
1) In this tutorial we're dealing with 24 bit bmps only. But it shouldn't be hard to change the code to support other formats.
2) For clarity's sake i'm only showing the code important to the task at hand, with only minimal error checking. If you want to use this code in a real program, you should add some exception handling.




2. Loading a bitmap to a DC


I'm going to start this easy by showing you how to use the WinAPI to load a bitmap onto a device context.

First we declare the function that takes as input a device context and a filename:
bool LoadBMPIntoDC ( HDC hDC, LPCTSTR bmpfile )
{
Now we check if the supplied arguments are valid. If not we return false.
if ( ( NULL == hDC  ) || ( NULL == bmpfile ) )
  return false;
We then use the windows function LoadImage to load the bitmap into a Bitmap Handle:
HANDLE hBmp = LoadImage ( NULL, bmpfile, IMAGE_BITMAP, 0, 0,
  LR_LOADFROMFILE );
The first parameter is the instance that contains the image, but since we are loading it from a file and not from a resource, this is NULL. The second param is the filename. With the third one we tell the function to load a bitmap (it can also load icons and cursors). The next two params are desired width and height. 0 means that we want the actual bitmap size read from the file. Finally we have to tell the function that we want to load the bitmap from a file. To load an image from a resource file we would use the function like this:
HANDLE hBmp = LoadImage ( hInstance, MAKEINTRESOURCE(imageid),
 IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR );
where hInstance is the handle of the instance of the program (the HINSTANCE in WinMain), imageid is the identifier of the image in the resourcefile and LR_DEFAULTCOLOR is the standard do-nothing flag.
Now we do a quick check if the function returned a usable handle:
if ( NULL == hBmp )
  return false;  
At this point we have a valid image handle. Now we want to select it into a device context. Unfortunately it is not possible to select it directly into a visible DC, but only into a memory DC, so we have to create that first:
HDC dcmem = CreateCompatibleDC ( NULL );
This function created a memory device context that is compatible to the screen.
Now we can select the bitmap into it:
if ( NULL == SelectObject ( dcmem, hBmp ) )
 { // failed to load bitmap into device context
  DeleteDC ( dcmem ); 
  return false; 
 }
If the function fails it deletes the memory dc and returns from the function.
If it was successful we can now blit the image from the memory dc to the visible dc we supplied as the first parameter to our LoadBMPIntoDC function.
To blit the entire image we first have to find out it's size though, but the WinAPI supplies us with an easy way to get the width and height of a bitmap handle:
BITMAP bm;
 GetObject ( hBmp, sizeof(bm), &bm );
This function loads the information from the handle, which is not much more then a pointer, into a BITMAP structure that lets us access the width and height of the image, stored in bm.bmWidth and bm.bmHeight. With those values we can now blit the whole image to the visible DC:
// and blit it to the visible dc
 if ( BitBlt ( hDC, 0, 0, bm.bmWidth, bm.bmHeight, dcmem,
  0, 0, SRCCOPY ) == 0 )
 { // failed the blit
  DeleteDC ( dcmem ); 
  return false; 
 }
The function blits the entire image to the upper left of the visible DC and cleans up on failure.

On success we can now see the image on the screen.

Now we don't need the memory dc anymore and can finish the function:
DeleteDC ( dcmem );  // clear up the memory dc 
 return true;
}
To display an image, we can use the function like this (hWnd is the window handle):
HDC dc = GetDC ( hWnd );            // get device context
LoadBMPIntoDC ( dc, L"test.bmp"  ); // display test.bmp on it
ReleaseDC ( hWnd, dc );             // release dc
You can of course modify the code to suit your needs, for example you can change the BitBlt to only copy parts of the image or to copy it to a position different from (0,0) on the destination DC.


3. Loading a Bitmap


Most of the time we want to work with the bitmap data directly (e.g. use it as a texture), and not just display it on a window dc, so the next thing i'll show you is how to load a bitmap.
Before we attempt to load it we should take a short look at the structure of a .bmp file.
A 24 bit bmp consists of 3 parts:
1) The file header, that holds information about type, size and layout of the file
2) The info header, that holds information about dimension and color format of the image
3) The image data

A palettized .bmp ( 4 or 8 bit ) additionally holds a color palette between info header and image data, but we're only looking at the usual 24 bit bmp in this tutorial.
The WinAPI is again nice enough to supply us with the structs for the file and info header, but you can as well declare them yourself if you don't want to use any windows functions or want to load .bmps on an other OS. They look like this:
typedef struct tagBITMAPFILEHEADER 
{
   WORD    bfType;        // must be 'BM' 
   DWORD   bfSize;        // size of the whole .bmp file
   WORD    bfReserved1;   // must be 0
   WORD    bfReserved2;   // must be 0
   DWORD   bfOffBits;     
} BITMAPFILEHEADER; 
The bfOffBits member is a bit confusing due to it's name. Actually it is the distance to the beginning of the image data, in bytes, from the beginning of the file. This is due to the fact that the size of the info header is not fixed to a certain size, since on newer windows versions there might be an extended version of the header, thus it is not guaranteed that the imaga data starts at sizeof(BITMAPFILEHEADER) + sizeof (BITMAPINFOHEADER) (although you can always read in a BITMAPINFOHEADER since the expanded info headers have the same layout). So, to read the image data later on we will move the file pointer to bfOffBits and start reading there.
Now lets take a look at the info header:
typedef struct tagBITMAPINFOHEADER
{
   DWORD  biSize;            // size of the structure
   LONG   biWidth;           // image width
   LONG   biHeight;          // image height
   WORD   biPlanes;          // bitplanes
   WORD   biBitCount         // resolution 
   DWORD  biCompression;     // compression
   DWORD  biSizeImage;       // size of the image
   LONG   biXPelsPerMeter;   // pixels per meter X
   LONG   biYPelsPerMeter;   // pixels per meter Y
   DWORD  biClrUsed;         // colors used
   DWORD  biClrImportant;    // important colors
} BITMAPINFOHEADER;
A little note: if you don't use the struct declarations from windows.h but paste the above into your code, you need to make sure that your compiler aligns data structures on 2-byte boundaries, else the loading and saving will not work correctly since the compiler usually pads the structures to the next 4-byte boundary. Setting a 2-byte align is luckily very easy with VC++: just add #pragma pack(2) before the struct declaration. If you use another compiler you will have to consult it's documents on how to change the alignment.

Ok now we can write a function to load the bitmap ( i'm using windows file I/O here, but you can use fopen/fread etc. or STL functions as well..whatever you like.)
Lets declare the function and some variables:
BYTE* LoadBMP ( int* width, int* height, long* size, LPCTSTR bmpfile )
{
 BITMAPFILEHEADER bmpheader;
 BITMAPINFOHEADER bmpinfo;
 DWORD bytesread;
Note that we take three pointers as parameters for width, height and size, since we will return the image dimensions and size in these variables. bmpfile is of course the filename of the bitmap, and the return value of the function will be a pointer to the image data.
First lets try to open the file:
HANDLE file = CreateFile ( bmpfile , GENERIC_READ, FILE_SHARE_READ,
   NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL );
 if ( NULL == file )
  return NULL;
Just a quick note here: it's useful to write if ( NULL == file ) instead of if ( file == NULL ) to prevent bugs, since on accidently typing if ( file = NULL ) the compiler will not complain but assign NULL to the file handle. if ( NULL = file ) will spawn a compiler error, so you can prevent bugs easily this way.
Back to the topic: now we opened the file and can read the file header. On error we will close the file and return from the function.
if ( ReadFile ( file, &bmpheader, sizeof ( BITMAPFILEHEADER ), 
  &bytesread, NULL ) == false )
 {
  CloseHandle ( file );
  return NULL;
 }
Now we can read the info header:
if ( ReadFile ( file, &bmpinfo, sizeof ( BITMAPINFOHEADER ), 
  &bytesread, NULL ) == false )
 {
  CloseHandle ( file );
  return NULL;
 }
Since we are only going to load 24bit .bmps here we now do some checking of the header contents.
First check if the file is actually a bitmap:
if ( bmpheader.bfType != 'MB' )
 {
  CloseHandle ( file );
  return NULL;
 }
check if it's uncompressed
if ( bmpinfo.biCompression != BI_RGB )
 {
  CloseHandle ( file );
  return NULL;
 }
and check if it's 24bit
if ( bmpinfo.biBitCount != 24 )
 {
  CloseHandle ( file );
  return NULL;
 }
When we are here we actually have a 24 bit bmp, so lets get its size and dimensions. We'll store them in the supplied variables:
*width   = bmpinfo.biWidth;
 *height  = abs ( bmpinfo.biHeight );
 *size = bmpheader.bfSize - bmpheader.bfOffBits;
To be independent of the type of info header, we compute the imaga data size as the whole file size minus the distance from file origin to start of image data.
Now we create a buffer to hold the data
BYTE* Buffer = new BYTE[ *size ];
Again, to be independent of info header version, we set the file pointer to the start of image data as told by the bfOffBits:
SetFilePointer ( file, bmpheader.bfOffBits, NULL, FILE_BEGIN );
And now we can read in the data. We make sure that on error the Buffer gets deleted so we don't create memory leaks:
if ( ReadFile ( file, Buffer, *size, &bytesread, NULL ) == false )
 {
  delete [] Buffer;
  CloseHandle ( file );
  return NULL;
 }
and finish the function
CloseHandle ( file );
 return Buffer;
}
A quick example of how to use the function:
int x, y;
long size;
BYTE* Buffer = LoadBMP ( &x, &y, &size, L"test.bmp" );
Unfortunately we have a problem now. The data we read from the image and returned from the function isn't quite what one might expect it to be.
One would expect that we now have a buffer of width*height RGB triplets, but unfortunately bitmaps store their data a little bit un-straightforward:
Basically they store everything from back to front: the image is stored upside-down in the file, and not in RGB triplets, but in BGR triplets.
And another thing comes into play: scanlines in .bmps are DWORD aligned. This means that every scanline ( there are (height) scanlines in an image ) which is not a multiple of 4 bytes long ( DWORD == 4bytes ) is filled up with zeros to the next multiple of 4.
Thus if the image would be just one black pixel it is not stored as one RGB triplet ( FF, FF, FF ) in the file, but as a DWORD ( FF, FF, FF, 00 ). This means, that if we treat the data as an RGB array we get some wrong colors, since we must not interprete the added zeros as color values.
Luckily the graphics APIs can deal with the unusual structure of .bmp image data:
In OpenGL it is possible to assign the buffer returned by LoadBMP directly to a texture if we add the following code before calls to glTexImage2D:
glPixelStorei ( GL_UNPACK_ALIGNMENT,   4 );
glPixelStorei ( GL_UNPACK_ROW_LENGTH,  0 );
glPixelStorei ( GL_UNPACK_SKIP_ROWS,   0 );
glPixelStorei ( GL_UNPACK_SKIP_PIXELS, 0 );
I don't know how to do it with D3D, but it should be possible there too.

But this tutorial is also for people who don't use an API for it, so lets move on to the next section.


4. Converting Bitmap data to an RGB array


Now i will show you how to take the padded upside-down BGR image data and convert it into an RGB buffer.
The function will take as arguments the BYTE array, the width and the height returned from LoadBMP. The output will be an array of RGB triplets and thus have the size 3 * width * height.
This means if Buffer is the output of the function, the RGB color of the first pixel in the image will be (Buffer[0], Buffer[1], Buffer[2]), the second pixel's (Buffer[3], Buffer[4], Buffer[5]) etc.
It is possible, and perhaps more straightforward, to create a struct like this:
typedef struct tagRGBTriplet
{
 BYTE red;
 BYTE green;
 BYTE blue;
} RGBTriplet;
and then make an array of such structs and assign the output from the function to it like this:
RGBTriplet* buffer = (RGBTriplet*)ConvertBMPToRGBBuffer
 ( imagedata, width, height );
buffer will then consist of width*height RGBTriplet structures and the color of the first pixel in the image would be ( buffer[0].red, buffer[0].green, buffer[0].blue ).

Now lets look at the code:
First we declare the function and make sure we have no invalid params:
BYTE* ConvertBMPToRGBBuffer ( BYTE* Buffer, int width, int height )
{
 if ( ( NULL == Buffer ) || ( width == 0 ) || ( height == 0 ) )
  return NULL;
Now we have to find out the number of bytes every scanline is padded with
int padding = 0;
 int scanlinebytes = width * 3;
 while ( ( scanlinebytes + padding ) % 4 != 0 )
  padding++;
At the end of the while loop padding will hold the number of padding bytes.

Now we can get the length in bytes of a padded scanline:
int psw = scanlinebytes + padding;
And construct the buffer to hold the output
BYTE* newbuf = new BYTE[width*height*3];
The 3 stands for the number of bytes in one RGBTriplet of course.

Now comes the heart of the function:
long bufpos = 0;   
 long newpos = 0;
 for ( int y = 0; y < height; y++ )
  for ( int x = 0; x < 3 * width; x+=3 )
  {
   newpos = y * 3 * width + x;     
   bufpos = ( height - y - 1 ) * psw + x;

   newbuf[newpos] = Buffer[bufpos + 2];       
   newbuf[newpos + 1] = Buffer[bufpos+1]; 
   newbuf[newpos + 2] = Buffer[bufpos];     
  }
What exactly happens in this loop?
For clear code and some more speed we declare two variables that will hold the buffer indices.
The first for loop loops trough each scanline in the image data, the second loop hits every 3rd byte in a scanline, meaning the start of every RGB triplet(representing a pixel).
Then we compute the index the current pixel will have in the new RGB buffer as current scanline * imagewidth * numberofbytesperpixel + position of current pixel.
Next we compute the position we have to look at for the current pixel in the image data. The image was stored upside down in the .bmp, thus if we want to find a pixel color in the first line we have to look at the last scanline in the image data. Because we start indexing arrays with 0, the scanline to look for is imageheight - currentscanline (the y variable of the loop) - 1.
To get the exact pixel position, we have to multiply the scanline number by the amount of bytes per scanline in the buffer, which we already computed in psw. And finally we add the x position of the current pixel.
So now we have the position the pixel (x, y) will have in the new buffer in newpos, and the position the color values for this pixel are at in the image data is in bufpos.
Now we could just assign those values, but remember that the color values themselves are stored in BGR format in the image, and we want them in RGB format, so we have to swap the bytes at our position (red value) and the one at our poition+2 (blue value).
I hope that was halfway clear :)

Now we can finish the function:
return newbuf;
}
I have two more things to add:
1) As i stated in the introduction i'm only doing rudimentary error checking here. You can see that if the buffer passed to the function is smaller then width*height*3, the loop will try to access data that doesn't belong to the array which might crash the program.
To prevent this from happening it would be good to do some exception handling and wrap the function or at leat the loop in a __try __catch block and make sure you pass the exact output from LoadBMP to the function.
2) The function creates a new buffer which it then returns, thus the calling function has to make sure this buffer is [] deleted when not longer needed to prevent memory leaks.


5. Saving a Bitmap


Now that i covered how to load an image, the next topic is how to save an image as a bitmap.
I already described the structure of .bmp files in section 3, so look there again if anything is unclear.

Lets begin by declaring the function
bool SaveBMP ( BYTE* Buffer, int width, int height, long paddedsize, LPCTSTR bmpfile )
{
Buffer is an array that contains the image data, width and height are the dimensions of the image to save, and paddedsize is the size of Buffer in bytes. bmpfile is the filename to save to.
First we declare the header structs and clear them:
BITMAPFILEHEADER bmfh;
 BITMAPINFOHEADER info;
 memset ( &bmfh, 0, sizeof (BITMAPFILEHEADER ) );
 memset ( &info, 0, sizeof (BITMAPINFOHEADER ) );
Next we fill the file header with data:
bmfh.bfType = 0x4d42;       // 0x4d42 = 'BM'
 bmfh.bfReserved1 = 0;
 bmfh.bfReserved2 = 0;
 bmfh.bfSize = sizeof(BITMAPFILEHEADER) + 
  sizeof(BITMAPINFOHEADER) + paddedsize;
 bmfh.bfOffBits = 0x36;
and the info header:
info.biSize = sizeof(BITMAPINFOHEADER);
 info.biWidth = width;
 info.biHeight = height;
 info.biPlanes = 1; 
 info.biBitCount = 24;
 info.biCompression = BI_RGB; 
 info.biSizeImage = 0;
 info.biXPelsPerMeter = 0x0ec4;  
 info.biYPelsPerMeter = 0x0ec4;     
 info.biClrUsed = 0; 
 info.biClrImportant = 0; 
Some explanations: we want to save as a 24 bit RGB image, so we have to set biCompression to BI_RGB, biBitCount to 24 and biPlanes to 1.
In 24 bit images we can set the biSizeImage value to 0 since it is ignored.
For PelsPerMeter i simply use the values that Paint uses when saving bitmaps.
Since we have no palette, we set the biClrUsed to 0, and biClrImportant being zero means that all colors are important.

Now we can open a file to save to ( again i'm using windows functions but it doesnt matter what file I/O functions you use of course)
HANDLE file = CreateFile ( bmpfile , GENERIC_WRITE, FILE_SHARE_READ,
   NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
 if ( NULL == file )
 {
  CloseHandle ( file );
  return false;
 }
Now we write the file header and info header:
unsigned long bwritten;
 if ( WriteFile ( file, &bmfh, sizeof ( BITMAPFILEHEADER ), 
  &bwritten, NULL ) == false )
 { 
  CloseHandle ( file );
  return false;
 }

 if ( WriteFile ( file, &info, sizeof ( BITMAPINFOHEADER ), 
  &bwritten, NULL ) == false )
 { 
  CloseHandle ( file );
  return false;
 }
and finally the image data:
if ( WriteFile ( file, Buffer, paddedsize, &bwritten, NULL ) == false )
 { 
  CloseHandle ( file );
  return false;
 }
Now we can close our function with
CloseHandle ( file );
 return true;
}
To save a buffer with image data as a .bmp file you use the function like this: (Buffer holds the data, is s bytes in size and of dimension width*height)
SaveBMP ( Buffer, width, height, s, L"test.bmp" );
Unfortunately we now have the same problem as above: the weird format of the image data. To be opened and displayed in the right way by other programs we can't save an RGB buffer to a bmp file, but first have to convert it to BGR, flip it upside down and DWORD-align the scanlines.


6. Converting RGB data to saveable bmp data


Basically this function is the opposite of the ConvertBMPToRGBBuffer from above, so i'm not going to explain every detail again.

First declare the function and check params for validity:
BYTE* ConvertRGBToBMPBuffer ( BYTE* Buffer, int width, int height, 
 long* newsize )
{
 if ( ( NULL == Buffer ) || ( width == 0 ) || ( height == 0 ) )
  return NULL;
find the number of bytes the buffer has to be padded with and length of padded scanline:
int padding = 0;
 int scanlinebytes = width * 3;
 while ( ( scanlinebytes + padding ) % 4 != 0 ) 
  padding++;
 int psw = scanlinebytes + padding;
calculate the size of the padded buffer and create it:
*newsize = height * psw;
 BYTE* newbuf = new BYTE[*newsize];
Now we could of course copy the old buffer to the new one, flip it and change RGB to GRB and then fill every scanline with zeroes to the next DWORD boundary, but we are smart coders of course, so we don't bother with any actual padding at all, but just initialize the whole buffer with zeroes and then copy the color values to their new positions without touching those padding-bytes anymore :)
memset ( newbuf, 0, *newsize );

 long bufpos = 0;   
 long newpos = 0;
 for ( int y = 0; y < height; y++ )
  for ( int x = 0; x < 3 * width; x+=3 )
  {
   bufpos = y * 3 * width + x;     // position in original buffer
   newpos = ( height - y - 1 ) * psw + x; // position in padded buffer
   newbuf[newpos] = Buffer[bufpos+2];       // swap r and b
   newbuf[newpos + 1] = Buffer[bufpos + 1]; // g stays
   newbuf[newpos + 2] = Buffer[bufpos];     // swap b and r
  }
 return newbuf;
}
Remember to delete all buffers at the end of the program and consider adding some exception handling.


7. Conclusion


And that's all there is to .bmps. And that in less then 200 lines of code :)

To conclude this tutorial, here are some examples that show how to use the functions we've written above:

This function copies the input file to the output file with the SaveBMP and LoadBMP functions:
void TestBMPCopy (LPCTSTR input, LPCTSTR output)
{
 int x, y;
 long s;
 BYTE* b = LoadBMP ( &x, &y, &s, input );
 SaveBMP ( b, x, y, s, output );
 delete [] b;
}
This function does the same, but also tests the convert functions:
void TestBMPCopy2(LPCTSTR input, LPCTSTR output)
{
 int x, y;
 long s, s2;
 BYTE* a = LoadBMP ( &x, &y, &s, input );
 BYTE* b = ConvertBMPToRGBBuffer ( a, x, y );
 BYTE* c = ConvertRGBToBMPBuffer ( b, x, y, &s2 ); 
 SaveBMP ( c, x, y, s2, output );
 delete [] a;
 delete [] b;
 delete [] c;
}
If you have a program that rendered an image of dimension x * y into an RGB buffer, you can save it to a .bmp file like this:
long s;
 BYTE* b = ConvertRGBToBMPBuffer ( buffer, x, y, &s );
 SaveBMP ( b, x, y, s, L"image.bmp" );
 delete [] b;
and to load an image, e.g. as a texture, into an RGB buffer:
int x, y;
 long s;
 BYTE* a = LoadBMP ( &x, &y, &s, L"texture.bmp" );
 BYTE* TexBuf = ConvertBMPToRGBBuffer ( a, x, y );
 delete [] a;


8. Download the code


To make your life easier, so you don't have to copy/paste everything from this page, you can get one .cpp file with the whole code (documented) here:
BMP.cpp 

No comments:

Post a Comment