How to Load a Bitmap
by Mark Bernard


ADVERTISEMENT

Download the sample code for this article here.

Introduction

One of the most basic and most important components of games is graphics. There are a number of ways to get graphics into games, but one of the most common is to load them in from a file. This article will discuss how to load an image that has been saved as a device independent bitmap (DIB); more commonly referred to as a Windows bitmap. All code presented will be in C++.

File types

One of the most important aspects of reading information from a file is knowing what type it is. There are two basic types: text and binary. The difference between the two is text saves all information as strings of characters and binary saves the computer representation of any values.

For example, if you have an unsigned integer with the value 847385 and saved this number in text format to a file, you could open that file up in any text editor and you would see the number 847385 staring back at you. Assuming that an unsigned integer is 32bits long, this number would be stored in memory with the hexadecimal values 19 EE 0C 00. From looking at this you can see that it looks backwards, but that is how Intel based machines store values. The least significant byte is stored first, with the most significant being last. This is also how information is written into a binary file also. After the value 847385 is written to a binary file, if you were to examine the file with a text editor, you would just see a bunch of meaningless characters. However, if you were to examine the file with a hexadecimal editor, you would see the values 19 EE 0C 00.

Image files stored as a DIB are also saved in binary format. We will look at how to load these properly soon. First I will go over how a DIB file is formatted.

Bitmap file format

There are four sections that make up a bitmap file. They are the bitmap header, bitmap info, colour palette and the bitmap data. These sections will always appear in this order, but the colour palette will not always be present. For anyone programming on the Windows platform, there are already structures available to load this information. All you have to do is #include <windows.h> to get access to thee structures. For those not programming in Windows, you will have to define these structures yourself. Below you can see these structures as they are defined in windows.h.

//File information header
//provides general information about the file
typedef struct tagBITMAPFILEHEADER { 
  WORD    bfType; 
  DWORD   bfSize; 
  WORD    bfReserved1; 
  WORD    bfReserved2; 
  DWORD   bfOffBits; 
} BITMAPFILEHEADER, *PBITMAPFILEHEADER;

//Bitmap information header
//provides information specific to the image data
typedef struct tagBITMAPINFOHEADER{
  DWORD  biSize; 
  LONG   biWidth; 
  LONG   biHeight; 
  WORD   biPlanes; 
  WORD   biBitCount; 
  DWORD  biCompression; 
  DWORD  biSizeImage; 
  LONG   biXPelsPerMeter; 
  LONG   biYPelsPerMeter; 
  DWORD  biClrUsed; 
  DWORD  biClrImportant; 
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;

//Colour palette
typedef struct tagRGBQUAD {
  BYTE    rgbBlue; 
  BYTE    rgbGreen; 
  BYTE    rgbRed; 
  BYTE    rgbReserved; 
} RGBQUAD;

Note that there is no structure for the data. That is because the data is simple a run of bytes.

Opening a file

The first thing we need to do to read in a bitmap file is to open the file. The function we will use to open the file is:

FILE *fopen(const char *filename, const char *mode).

The first parameter is the name of the file and the second parameter is the mode to open with. Here is the code snippet you need to read from a file.

//include the header for file access#include <cstdio>

//function to load the bitmap
loadBMP(char *file) {
	//file handle used in all file operations
    FILE *in;

    //open the file for reading in binary mode    
    in=fopen(file,"rb");

The "rb" portion is very important in the fopen function. This is what allows us to read in binary format. If this is not set properly you will not be able to read the file correctly.

That's all there is. The file is now open and ready for reading.

BITMAPFILEHEADER

The first thing you need to read from the file is the file header. The function we will use for reading is:

size_t fread(void *ptr, size_t size, size_t nelem, FILE *stream);

The first parameter is a pointer to where you want the data stored. The second parameter is the size of each element that you want to read. The third is the number of elements of that size to read. The final parameter a pointer to the stream obtained from fopen.

Here's how to read in the file header:

BITMAPFILEHEADER bmfh;

fread(&bmfh,sizeof(BITMAPFILEHEADER),1,in);

As you can see, it only takes one read to fill all the information in the structure. Using sizeof() on BITMAPFILEHEADER will return a size of 16 bytes. This is also the exact size of the file header. Since there is only one header, the third element is set to one. And of course, the stream we are using is the last element.

Now that we have loaded the file header, let's examine each element for their meaning.

WORD    bfType; 

This will tell you if the file is a bitmap type or not. This number is always 19778. If it is not, then the file is not a bitmap file.

DWORD   bfSize; 

This is the total size of the file, including all the headers.

WORD    bfReserved1; WORD    bfReserved2; 

These two are reserved and should contain all zeros.

DWORD   bfOffBits; 

This is the offset to the image data from the start of the file. This will vary depending on whether or not there is a colour palette.

BITMAPINFOHEADER

Here you use the same method that was used to load BITMAPFILEHEADER:

BITMAPINFOHEADER bmih;

fread(&bmih,sizeof(BITMAPINFOHEADER),1,in);

The sizeof() should return 40 bytes on this read. Since there is only one BITMAPFILEHEADER, the number to read in is 1.

Now let's look at the elements of BITMAPFILEHEADER:

DWORD  biSize; 

This is the size of BITMAPFILEHEADER. It should be 40.

LONG   biWidth; 

This is the width of the image in pixels.

LONG   biHeight; 

This is the height of the image in pixels.

WORD   biPlanes; 

This is the number of bit planes in the image. It should always be 1.

WORD   biBitCount; 

This is the number of bits per pixel for colours. It will be 1,4,8 or 24.

DWORD  biCompression; 

This is the compression, if any, used. It will be set to 0 for no compression.

DWORD  biSizeImage; 

This should be set to the size of the image data. Sometimes it may be set to 0.

LONG   biXPelsPerMeter;
LONG   biYPelsPerMeter; 

These set the horizontal and vertical resolution in pixels per meter.

DWORD  biClrUsed;

This specifies the number of colours used from the colour palette. If the image is 24 bits per pixel, then this is usually 0.

DWORD  biClrImportant;

This is the number of colour indexes that are important. If it is set to 0, all indexes are important.

RGBQUAD

This information is only read in from the file if there is a colour table. There is only a colour table if the image is less than 24 bits per pixel. The code below will load the colour table:

//set the number of colours
numColours=1 << bmih.biBitCount;

//load the palette for 8 bits per pixel
if(bmih.biBitCount == 8) {
    colours=new RGBQUAD[numColours];
    fread(colours,sizeof(RGBQUAD),numColours,in);
}

In this article, I will only be dealing with 8bit or 24bit bitmaps, so there will only be a colour table for the 8bit variety. With the code above, it can be easily expanded to accommodate 4bit and 1bit colour tables as well.

The sizeof() here will return 4. The variable numColours will be 256 for an 8bit image. So this will load in a 256 colour palette. On a Windows PC, most colour palettes are stored in RGB format. In a bitmap file, this is reversed. There is also a reserved byte at the end of each colour. The palette loaded will look like BGRr. Where the 'r' is the reserved byte.

Image data

Now we come to the most important part of the file. This is where all the pixels that make up the image will be stored. To find out the size of this section, you could look at bmih.biSizeImage. However, this value will sometimes be 0. To make sure you always have a valid value, you can use the following code:

DWORD size;

size=bmfh.bfSize-bmfh.bfOffBits;

This takes the size of the file and subtracts the size of all the header information. This will always give you an accurate result. Next create a temporary variable to store image data in so you can work with it and read the image data from the file:

BYTE *tempPixelData;

tempPixelData=new BYTE[size];

if(tempPixelData==NULL) {
    fclose(in);
    return false;
}
fread(tempPixelData,sizeof(BYTE),size,in);

And that's it. One read reads in the entire bitmap, no matter how large it is. The only consideration is if you have enough memory to hold the image.

Once you have the data loaded, you might think you are ready to use it. However, there are a couple of things to consider about how the data is formatted.

First, each line of data must end on a DWORD(4 byte) boundary. If the width of your image does not fall on a DWORD boundary, then it will be padded with zeros to fill it out. For example, if you have an image that is 254x254, the DWORD boundary for this image is 256. The image data loaded from this file will be 256x254. Before the data can be used, the padding must be removed. Also note that some image editors will also make sure the file ends on a DWORD boundary. This needs to be taken into account in the code. There are comments to show where this needs to be considered.

Second, the data can be stored either in forward order or reverse order. The way to tell is to check the bmih.biHeight value. If this value is negative, the data is stored in forward order. If it is positive, reverse order is used.

Now well set up a couple of variables to hold the widths:

//byteWidth is the width of the actual image in bytes
//padWidth is the width of the image plus the extra padding
LONG byteWidth,padWidth;

//initially set both to the width of the image
byteWidth=padWidth=(LONG)((float)width*(float)bpp/8.0);

//add any extra space to bring each line to a DWORD boundary
while(padWidth%4!=0) {
   padWidth++;
}

I will just throw all the code out and then go through it:

DWORD diff;
int offset;
LONG height;

height=bmih.biHeight;
//set diff to the actual image size(no padding)
diff=height*byteWidth;
//allocate memory for the image
pixelData=new BYTE[diff];
if(pixelData==NULL) {
    fclose(in);
    return false;
}
//bitmap is inverted, so the padding needs to be removed
//and the image reversed
//Here you can start from the back of the file or the front,
//after the header.  The only problem is that some programs
//will pad not only the data, but also the file size to
//be divisible by 4 bytes.
if(height>0) {
    int j==size-3;
    offset=padWidth-byteWidth;
    for(int i=0;i<size;i+=3) {
        if((i+1)%padWidth==0) {
            i+=offset;
        }
        *(pixelData+j+2)=*(tempPixelData+i);
        *(pixelData+j+1)=*(tempPixelData+i+1);
        *(pixelData+j)=*(tempPixelData+i+2);
        j++;
    }
}
//the image is not reversed.  Only the padding needs to be removed.
else {
    height=height*-1;
    offset=0;
    do {
        memcpy((pixelData+(offset*byteWidth)),
               (tempPixelData+(offset*padWidth)),
                byteWidth);
        offset++;
    } while(offset<height);
}

First the actual size of the image is determined and memory is allocated for it. Then the height is checked to see if the image is reversed or not. If the image is reversed, the data needs to be copied byte by byte into the final storage area. If the image is not reversed, then we can make use of memcpy() to quickly move the data. This is not a big deal as image loading should not occur in time critical code anyway.

An example

Now that we have our bitmap loaded, I will show you how you can use this in an OpenGL program to load texture data from a bitmap image. First I will post the header file for my class to make things easier to follow:

#ifndef _BITMAP_H
#define _BITMAP_H

#include <cstdio>
#include <windows.h>

class Bitmap {
public:
    //variables
    RGBQUAD *colours;
    BYTE *pixelData;
    bool loaded;
    LONG width,height;
    WORD bpp;
    //methods
    Bitmap(void);
    Bitmap(char *);
    ~Bitmap();
    bool loadBMP(char *);
private:
    //variables
    BITMAPFILEHEADER bmfh;
    BITMAPINFOHEADER bmih;
    //methods
    void reset(void);
};

#endif //_BITMAP_H

As you can see, I left the data that needs to be accessed public to make it easier to use.

Now for an example of loading a texture into an OpenGL program:

bool loadTexture() {
	Bitmap *image; 

	image=new Bitmap();

	if(image==NULL) {
		return false;
	}

	if (image->loadBMP("mytexture.bmp")) {
		glGenTextures(1, &texture[0]); 

		glBindTexture(GL_TEXTURE_2D, texture[0]);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
		glTexImage2D(GL_TEXTURE_2D, 0, 3, image->width, image->height, 0, 
					GL_RGB, GL_UNSIGNED_BYTE, image->pixelData);
	}
	else {
		return false;
	}

	if (image) {
		delete image;
	}

	return true;
}

As you can see, all you have to do is allocate space for the Bitmap object, tell it to load the bitmap and send it to OpenGL.

Conclusion

I didn't cover all the aspects of bitmap files, but the ones that I left out are rarely used. It's pretty straightforward to load a bitmap, once you have the specification. You can easily add to this class by adding a loadXXX for each type of file (TGA,PCX,JPG…) you want to load.

Postscript

Some minor changes have been made to the code since the writing of this article. But essentially it is the same.

Discuss this article in the forums


Date this article was posted to GameDev.net: 7/23/2003
(Note that this date does not necessarily correspond to the date the article was written)

See Also:
General
Sweet Snippets

© 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
Comments? Questions? Feedback? Click here!