In this tutorial we will look at loading a bitmap from a .bmp file and drawing it to the screen.
Before we can draw a bitmap, we first need to load a bitmap. The bitmap file must be a 24-bit colour depth .bmp file, or else the code and techniques discussed in this tutorial will not work. The one I will be using is the 64x64 px astronaut sprite, called "test.bmp", shown below:
Before we can hope to load a .bmp from a file, we must first understand the .bmp file structure. Bitmap files are made up of four key parts: the file header, the info header, the colour table and the bitmap data.
The file header of a .bmp file includes file metadata, and is a BITMAPFILEHEADER structure. It is a fixed size, which we can get using "sizeof(BITMAPFILEHEADER)". It contains data such as the size of the file and the file type (although this latter property is always set to an ID indicating that it is a bitmap file).
The info header contains metadata related to the image itself, for example the width and height of the image in pixels. This is a BITMAPINFOHEADER structure, which is the same type of structure as renderBuffer.infoHeader.bmiHeader from earlier on in the series, when we were first setting up our render buffer.
The colour table is not important for us, as we are working with 24-bit bitmaps. 24-bit bitmaps do not have a colour table, as they can have three bytes to use for RGB values. Colour tables are mostly used for 8-bit and 16-bit images. Since there will not be one in our file, we do not have to worry about it (unless you intend to support different types of bitmaps).
The pixel data is stored as an array of pixels. Each pixel is stored as a blue byte, followed by a green byte and then a red byte. Since we have set our buffer up to store data in this format, we use the pixel array data as is, with little modification when drawing our bitmap.
Before we can load a bitmap, we will need to create our own bitmap structure to store our bitmap data in. It will be made of three properties. The first is a BITMAPFILEHEADER structure called "bitmapFileHeader". The second is a BITMAPINFOHEADER called "bitmapInfoHeader". The third is a pixel pointer called "pixels". This will be used to store our actual pixel array.
We should create our bitmap structure in the main.h file, with the rest of our structures. I will define it as its own type called "Bitmap".
We can now create a loadBMPFile function. It should take two arguments, the path file as a string and a pointer to the bitmap structure to fill in. We can do this with the line "int loadBMPFile(char filePath[], Bitmap * bitmap){".
The first thing to do inside our function is to attempt to open the file. We should open the file in read binary mode. I will call the returned file pointer "file". We can do this with the line 'FILE * file = fopen(filePath, "rb");'.
After this, we want to check if the file failed to open. We can check this with the if statement "if(!file){". Inside the if statement, we want to retrn 0.
If our file did open successfully, we want to retrieve the file size. To do this, we should first set the file position indicator to the end of the file using the line "fseek(file, 0, SEEK_END);". We can then retrieve the position of the file position indicator and store it to a variable called "fileSize", using the line "int fileSize = ftell(file);". Then we want to rewind our file position indicator back to the start of the file so we can read the actual data. This can be done with the line "rewind(file);".
Next, we need to check if the file size is too small to be reasonable. We know that the file is at least the size of a BITMAPFILEHEADER and a BITMAPINFOHEADER added together, so we should check if the file size is smaller than sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER). We can do this with the line: "if(fileSize < sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)){". Inside this if statement, we should close the file (with "fclose(file);") and return 0.
Now we have checked for errors, we can read the file header into our bitmap's fileHeader structure. We can do this using an fread call, like so: "fread(&(bitmap->fileHeader), sizeof(BITMAPFILEHEADER), 1, file);". Here, we are reading 1 structure of sizeof(BITMAPFILEHEADER) bytes from our file, and transferring it into the bitmap's fileHeader structure (by using a pointer to its address).
Next, we need to do the same for the bitmap's info header. We can do this with the line: "fread(&(bitmap->infoHeader), sizeof(BITMAPINFOHEADER), 1, file);".
Now we have read bitmap info header, we can read the width and height of the bitmap from it. We can use this to allocate enough memory for the pixel data. The amount of memory to allocate is equal to the size of a pixel structure * the width of the bitmap * the height of the bitmap. We should allocate this memory and set the "bitmap->pixels" pointer to point to it. This can be done with the line: "bitmap->pixels = (Pixel *) malloc(bitmap->infoHeader.biWidth * bitmap->infoHeader.biHeight * sizeof(Pixel));".
After this, we can read the bitmap pixel data straight into our bitmap->pixels array with another fread call (remember that there is no colour table in the bitmap we are using, so we can move straight onto reading pixel data). This can be done with the line "fread(bitmap->pixels, sizeof(Pixel), bitmap->infoHeader.biWidth * bitmap->infoHeader.biHeight, file);". To finish of our function, we should close our file (using "fclose(file);") and return 1 to indicate success.
That finishes our loadBMPFile function.
We will store our bitmaps as global variables to make them easy to use (you could use another way if you would rather). I will declare a test bitmap in the main.h file using "Bitmap bmp_test;".
We need to load our bitmaps before we can draw them, so we should create a function to load all our bitmap variables and run it inside the WinMain function, just before we start the main loop. I will name this function loadAllBitmaps. It will be a void function and have no parameters.
I will declare the loadAllBitmaps function in the main.h file, and I will define it just after our loadBMPFile function call in the render.c file. Inside the function, we will load our bitmap file (test.bmp) into our bitmap structure bmp_test. This can be done with the line 'loadBMPFile("test.bmp", &bmp_test);'. This completes our loadBitmaps function for now (we may wish to add other bitmaps later).
Now we need to call our loadAllBitmaps function just before our first mainLoop call.
Now we've loaded a bitmap, we need to look at drawing it. We will create a drawBitmap function to do this. It will be avoid function and will have three parameters. The first two should be integers and should be the x and y coordinates of the bottom left corner of the bitmap on the render buffer (I will call these "x" and "y"). The last should be a pointer to our bitmap structure (which I will call "bitmap"). This function will be written in render.c and should be declared in main.h.
Inside our drawBitmap function, we should first create a pixel pointer. This will always point to the pixel we want to render to. We will create our pixel pointer with the line "Pixel * pixel;". We then need to create two loop counters, i and j, which we can create with the following lines: "int i = 0;" and "int j = 0;".
After setting up our variables, we want to loop through each row of our bitmap. We can use the bitmap->infoHeader.biHeight property to get the height of our bitmap. We can loop through all the rows of our bitmap with the following line: "for(i = 0; i < bitmap->infoHeader.biHeight; i++){". At the top of this loop, we want to set the pixel pointer to the start of the row. This can be found by taking the renderBuffer.pixels array and adding ((y + i) * BUFFER_WIDTH + x) to it. (y + i) * BUFFER_WIDTH will take the pointer to the correct y-coordinate, and the "+ x" part moves it along to the correct x coordinate. We can write this in code with the following line: "pixel = renderBuffer.pixels + (y + i) * BUFFER_WIDTH + x;".
Now that our pixel pointer has been set to the correct row, we want to iterate through each pixel on said row. We can do this with the line: "for(j = 0; j < bitmap->infoHeader.biWidth; j++){". Within this loop, we need to check that the pixel we are trying to render is actually on screen, which can be done with the line "if(j >= 0 && j < BUFFER_WIDTH && y + i >= 0 && y + i < BUFFER_HEIGHT){".
If the current pixel is on screen, we want to retrieve the correct pixel from the source bitmap and draw it. We can retrieve the correct pixel by finding the pixel at bitmap->pixels + i * bitmap->infoHeader.biWidth + j. We can store this to a pixel pointer, which I will call srcPixel, with the line: "Pixel * srcPixel = bitmap->pixels + i * bitmap->infoHeader.biWidth + j;".
The next step is optional, but is something I find helpful. You may wish to set one specific colour to be a transparent colour. This means that when we come across a pixel of said colour in our bitmap image, we do not draw it, and any colours of said pixel are simply ignored. I will implement this, and will use a vibrant pink colour (that I am not using in my bitmap image) as the transparent colour.
Before drawing the pixel, we need to check that the srcPixel is not the transparent colour. How you check this depends on what you want your transparent colour to be, but I am checking for mine with the line: "if(!(srcPixel->red == 163 && srcPixel->green == 73 && srcPixel->blue == 164)){".
If our colour is not transparent, we should draw it to the screen. This can be done by setting the pixel value at our pixel pointer to the srcPixel's set of RGB values. We can do this with the lines "pixel->red = srcPixel->red;", "pixel->green = srcPixel->green;" and "pixel->blue = srcPixel->blue;".
Before we close off our horizontal loop (our j loop), we want to increment our pixel pointer to the next one along using the line "pixel++;".
This completes our drawBitmap function. Now we just have to call it in our render function. I will call it using the following line: "drawBitmap(100, 100, &bmp_test);", but you can, of course, change the arguments inputted.
Below is the code for all three of our files. I am using the MinGW compiler and will compile it with the command "gcc main.c -lgdi32 -o app", but you can use another compiler, so long as you are able to link the gdi32 library.
main.h
render.c
main.c
If you run your program, you should get an output similar to the image below:
That's all for this tutorial. In the next tutorial, we will be cutting a sprite out of a bitmap image. We will also look at scaling our sprite.