Load BMP Files with the C Standard Library
This tutorial is also available as a video. In this tutorial I’ll show you how to load and display 32-bit bitmap image files with transparency with the C standard library. Note: I'm doing this inside a basic Win32 program, linking to gdi32, but no extra linking is required for the bitmap code itself. Note: I’m compiling this code with GCC. Note: Click any of the hyperlinked words to visit the documentation page for them. Note: I've dimmed all the code not relevant to the tutorial. main.c#define UNICODE
#define _UNICODE
#include <windows.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#define PRINT_ERROR(a, args...) printf("ERROR %s() %s Line %d: " a "\n", __FUNCTION__, __FILE__, __LINE__, ##args);
bool quit = false;
HWND window_handle;
HDC device_context;
HBITMAP bitmap;
BITMAPINFO bitmap_info;
typedef struct {
union { int width, w; };
union { int height, h; };
union { uint32_t *pixels, *p; };
} sprite_t;
sprite_t frame;
LRESULT CALLBACK WindowProcessMessage(HWND window_handle, UINT message, WPARAM wParam, LPARAM lParam);
bool LoadSprite(sprite_t *sprite, const char *filename);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {
const wchar_t window_class_name[] = L"Window Class";
static WNDCLASS window_class = { 0 };
window_class.lpfnWndProc = WindowProcessMessage;
window_class.hInstance = hInstance;
window_class.lpszClassName = window_class_name;
RegisterClass(&window_class);
bitmap_info.bmiHeader.biSize = sizeof(bitmap_info.bmiHeader);
bitmap_info.bmiHeader.biPlanes = 1;
bitmap_info.bmiHeader.biBitCount = 32;
bitmap_info.bmiHeader.biCompression = BI_RGB;
device_context = CreateCompatibleDC(0);
window_handle = CreateWindow(window_class_name, L"Learn to Program Windows", WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
if(window_handle == NULL) {
PRINT_ERROR("CreateWindow failed");
return -1;
}
static sprite_t sprite;
if(!LoadSprite(&sprite, "colorwheel.bmp")) {
PRINT_ERROR("Failed to load sprite: \"colorwheel.bmp\"");
return -1;
}
while(!quit) {
static MSG message = { 0 };
while(PeekMessage(&message, NULL, 0, 0, PM_REMOVE)) {
DispatchMessage(&message);
}
static uint32_t *p;
p = frame.pixels;
for(int n = 0; n < frame.w * frame.h; ++n) {
*(p++) = (float)n / (float)(frame.w * frame.h) * (uint32_t)(-1);
}
#if 1
static uint32_t *frame_pointer, *sprite_pointer;
static int sprite_byte_width;
frame_pointer = frame.pixels;
sprite_pointer = sprite.pixels;
sprite_byte_width = sprite.w * 4;
for(int y = 0; y < sprite.h; ++y) {
memcpy(frame_pointer, sprite_pointer, sprite_byte_width);
frame_pointer += frame.w; sprite_pointer += sprite.w;
}
#else
for(int y = 0; y < sprite.h; ++y) {
for(int x = 0; x < sprite.w; ++x) {
static uint32_t source_index, target_index;
static float alpha, anti_alpha;
static uint32_t sr, sg, sb; // Source
static uint32_t tr, tg, tb; // Target
source_index = x + y*sprite.w;
target_index = x + y*frame.w;
alpha = (float) ((sprite.p[source_index] & 0xff000000) >> 24) / 255.f;
anti_alpha = 1.f - alpha;
sr = (uint32_t)(((sprite.p[source_index] & 0x00ff0000) >> 16) * alpha) << 16;
sg = (uint32_t)(((sprite.p[source_index] & 0x0000ff00) >> 8) * alpha) << 8;
sb = (sprite.p[source_index] & 0x000000ff ) * alpha;
tr = (uint32_t)((( frame.p[target_index] & 0x00ff0000) >> 16) * anti_alpha) << 16;
tg = (uint32_t)((( frame.p[target_index] & 0x0000ff00) >> 8) * anti_alpha) << 8;
tb = ( frame.p[target_index] & 0x000000ff ) * anti_alpha;
frame.pixels[target_index] = sb + tb + sg + tg + sr + tr;
}
}
#endif
InvalidateRect(window_handle, NULL, FALSE);
UpdateWindow(window_handle);
}
return 0;
}
LRESULT CALLBACK WindowProcessMessage(HWND window_handle, UINT message, WPARAM wParam, LPARAM lParam) {
switch(message) {
case WM_DESTROY: {
PostQuitMessage(0);
quit = true;
} break;
case WM_PAINT: {
static PAINTSTRUCT paint;
static HDC paint_device_context;
paint_device_context = BeginPaint(window_handle, &paint);
BitBlt(paint_device_context, paint.rcPaint.left, paint.rcPaint.top, paint.rcPaint.right - paint.rcPaint.left, paint.rcPaint.bottom - paint.rcPaint.top, device_context, paint.rcPaint.left, paint.rcPaint.top, SRCCOPY);
EndPaint(window_handle, &paint);
} break;
case WM_SIZE: {
frame.width = bitmap_info.bmiHeader.biWidth = LOWORD(lParam);
frame.height = bitmap_info.bmiHeader.biHeight = HIWORD(lParam);
if(bitmap) DeleteObject(bitmap);
bitmap = CreateDIBSection(NULL, &bitmap_info, DIB_RGB_COLORS, (void**)&frame.pixels, 0, 0);
SelectObject(device_context, bitmap);
} break;
case WM_KEYDOWN: {
if(wParam == VK_ESCAPE) {
PostQuitMessage(0);
quit = true;
}
} break;
default: return DefWindowProc(window_handle, message, wParam, lParam);
}
return 0;
}
/* Bitmap file format
*
* SECTION
* Address:Bytes Name
*
* HEADER:
* 0: 2 "BM" magic number
* 2: 4 file size
* 6: 4 junk
* 10: 4 Starting address of image data
* BITMAP HEADER:
* 14: 4 header size
* 18: 4 width (signed)
* 22: 4 height (signed)
* 26: 2 Number of color planes
* 28: 2 Bits per pixel
* [...]
* [OPTIONAL COLOR PALETTE, NOT PRESENT IN 32 BIT BITMAPS]
* BITMAP DATA:
* DATA: X Pixels
*/
bool LoadSprite(sprite_t *sprite, const char *filename) {
bool return_value = true;
uint32_t image_data_address;
int32_t width;
int32_t height;
uint32_t pixel_count;
uint16_t bit_depth;
uint8_t byte_depth;
uint32_t *pixels;
printf("Loading bitmap file: %s\n", filename);
FILE *file;
file = fopen(filename, "rb");
if(file) {
if(fgetc(file) == 'B' && fgetc(file) == 'M') {
printf("BM read; bitmap file confirmed.\n");
fseek(file, 8, SEEK_CUR);
fread(&image_data_address, 4, 1, file);
fseek(file, 4, SEEK_CUR);
fread(&width, 4, 1, file);
fread(&height, 4, 1, file);
fseek(file, 2, SEEK_CUR);
fread(&bit_depth, 2, 1, file);
if(bit_depth != 32) {
PRINT_ERROR("(%s) Bit depth expected %d is %d", filename, 32, bit_depth);
return_value = false;
}
else { // Image metadata correct
printf("image data address:\t%d\nwidth:\t\t\t%d pix\nheight:\t\t\t%d pix\nbit depth:\t\t%d bpp\n", image_data_address, width, height, bit_depth);
pixel_count = width * height;
byte_depth = bit_depth / 8;
pixels = malloc(pixel_count * byte_depth);
if(pixels) {
fseek(file, image_data_address, SEEK_SET);
int pixels_read = fread(pixels, byte_depth, pixel_count, file);
printf("Read %d pixels\n", pixels_read);
if(pixels_read == pixel_count) {
sprite->w = width;
sprite->h = height;
sprite->p = pixels;
}
else {
PRINT_ERROR("(%s) Read pixel count incorrect. Is %d expected %d", filename, pixels_read, pixel_count);
free(pixels);
return_value = false;
}
}
else {
printf("(%s) Failed to allocate %d pixels.\n", filename, pixel_count);
return_value = false;
}
} // Done loading sprite
}
else {
PRINT_ERROR("(%s) First two bytes of file are not \"BM\"", filename);
return_value = false;
}
fclose(file);
}
else {
PRINT_ERROR("(%s) Failed to open file", filename);
return_value = false;
}
return return_value;
}
build.bat
gcc main.c -lgdi32
And here's the bitmap image loaded in the demo code: colorwheel.bmp
This image was generated using GIMP. If you have trouble creating a similar transparent bmp image, be sure to add transparency to the layer in GIMP and in the bmp export options, select "Advanced Options->A8 R8 G8 B8".
Code Walkthrough
#include <stdio.h>
stdio.h contains all the file operation functions we need to load any file. We could use the operating system's native libraries to load files but unless you have specific needs to meet, the portable C standard library is an easy way to get the job done.
typedef struct {
union { int width, w; };
union { int height, h; };
union { uint32_t *pixels, *p; };
} sprite_t;
This “sprite” structure holds the width, height and pixels of our bitmap image. We use uint32s for the pixels since we’re only going to be loading 32-bit bitmaps with alpha, red, green and blue 8-bit sub-pixels.
static sprite_t sprite;
if(!LoadSprite(&sprite, "colorwheel.bmp")) {
PRINT_ERROR("Failed to load sprite: \"colorwheel.bmp\"");
return -1;
}
In the main program we create the sprite variable and load it with this “LoadSprite” function, sending a pointer to the sprite to be filled with data and the filename to load. Within LoadSprite is the bulk of the program.
/* Bitmap file format
*
* SECTION
* Address:Bytes Name
*
* HEADER:
* 0: 2 "BM" magic number
* 2: 4 file size
* 6: 4 junk
* 10: 4 Starting address of image data
* BITMAP HEADER:
* 14: 4 header size
* 18: 4 width (signed)
* 22: 4 height (signed)
* 26: 2 Number of color planes
* 28: 2 Bits per pixel
* [...]
* [OPTIONAL COLOR PALETTE, NOT PRESENT IN 32 BIT BITMAPS]
* BITMAP DATA:
* DATA: X Pixels
*/
I found the bitmap file format information online and wrote it here in a convenient reference format. There are actually 4 versions of the bitmap format but I’ve written this code to only work with version 4 which has been standard since Windows 95. There are also many variations of bitmap formats such as 8-bit, using a colour palette and so on, but I’ve written code only to load 32-bit bitmaps with transparency.
Bitmap files contain a file header identifying it, a bitmap header with information about the image and the bitmap pixel data.
bool LoadSprite(sprite_t *sprite, const char *filename) {
bool return_value = true;
uint32_t image_data_address;
int32_t width;
int32_t height;
uint32_t pixel_count;
uint16_t bit_depth;
uint8_t byte_depth;
uint32_t *pixels;
printf("Loading bitmap file: %s\n", filename);
Here I’ve declared all the variables we care to load from the file. Some of the data we don’t care about so we’ll just skip it. I’ve interspersed several printfs to print out the data as we read it just to make debugging easier.
FILE *file;
file = fopen(filename, "rb");
if(file) {
We create a FILE pointer and open the file in “read binary” mode with fopen. If the file was opened successfully fopen returns the pointer to the file, otherwise it returns NULL so we can check it was successful with a simple if statement.
if(fgetc(file) == 'B' && fgetc(file) == 'M') {
printf("BM read; bitmap file confirmed.\n");
Using fgetc we read the first two bytes, checking that they’re the letters “BM” which identify a bitmap file. Note that these if blocks each have an else block to handle errors further down the code.
fseek(file, 8, SEEK_CUR);
fread(&image_data_address, 4, 1, file);
fseek(file, 4, SEEK_CUR);
fread(&width, 4, 1, file);
fread(&height, 4, 1, file);
fseek(file, 2, SEEK_CUR);
fread(&bit_depth, 2, 1, file);
if(bit_depth != 32) {
PRINT_ERROR("(%s) Bit depth expected %d is %d", filename, 32, bit_depth);
return_value = false;
}
Next we read the variables we care about with fread, which takes a pointer to be filled, the number of bytes per element, the number of elements to be read and the file pointer. We read: image data address; width; height; and bit depth, with a couple of file seek operations to skip unneeded data. fseek takes the file pointer, amount of bytes by which to offset the pointer and position from which to offset, which can be SEEK_SET (beginning of file), SEEK_CUR (current position) or SEEK_END (end of file). We verify the bit depth is correct before continuing.
else { // Image metadata correct
printf("image data address:\t%d\nwidth:\t\t\t%d pix\nheight:\t\t\t%d pix\nbit depth:\t\t%d bpp\n", image_data_address, width, height, bit_depth);
pixel_count = width * height;
byte_depth = bit_depth / 8;
pixels = malloc(pixel_count * byte_depth);
if(pixels) {
fseek(file, image_data_address, SEEK_SET);
int pixels_read = fread(pixels, byte_depth, pixel_count, file);
printf("Read %d pixels\n", pixels_read);
if(pixels_read == pixel_count) {
sprite->w = width;
sprite->h = height;
sprite->p = pixels;
}
If the data so far is correct, we calculate our number of pixels and bytes per pixel, and allocate the pixel buffer. We seek to the image data address and read the pixels. fread returns the number of elements read so we check that matches our calculated number of pixels before finally setting our sprite’s width, height and pointer to the pixels.
else {
PRINT_ERROR("(%s) Read pixel count incorrect. Is %d expected %d", filename, pixels_read, pixel_count);
free(pixels);
return_value = false;
}
}
else {
printf("(%s) Failed to allocate %d pixels.\n", filename, pixel_count);
return_value = false;
}
} // Done loading sprite
}
else {
PRINT_ERROR("(%s) First two bytes of file are not \"BM\"", filename);
return_value = false;
}
fclose(file);
}
else {
PRINT_ERROR("(%s) Failed to open file", filename);
return_value = false;
}
return return_value;
}
After the cascading error handling code, we close the file and return.
p = frame.pixels;
for(int n = 0; n < frame.w * frame.h; ++n) {
*(p++) = (float)n / (float)(frame.w * frame.h) * (uint32_t)(-1);
}
Back in our main program, we proceed to the main program loop where we fill the frame buffer with a background, then we have two different methods of copying the pixels to the screen:
#if 1
static uint32_t *frame_pointer, *sprite_pointer;
static int sprite_byte_width;
frame_pointer = frame.pixels;
sprite_pointer = sprite.pixels;
sprite_byte_width = sprite.w * 4;
for(int y = 0; y < sprite.h; ++y) {
memcpy(frame_pointer, sprite_pointer, sprite_byte_width);
frame_pointer += frame.w; sprite_pointer += sprite.w;
}
First is a relatively fast loop through each row of the sprite, using advancing pointers to copy each row at a time to the frame with memcpy. This doesn’t process any of the pixels individually and so won’t do alpha blending.
#else
for(int y = 0; y < sprite.h; ++y) {
for(int x = 0; x < sprite.w; ++x) {
static uint32_t source_index, target_index;
static float alpha, anti_alpha;
static uint32_t sr, sg, sb; // Source
static uint32_t tr, tg, tb; // Target
source_index = x + y*sprite.w;
target_index = x + y*frame.w;
alpha = (float) ((sprite.p[source_index] & 0xff000000) >> 24) / 255.f;
anti_alpha = 1.f - alpha;
sr = (uint32_t)(((sprite.p[source_index] & 0x00ff0000) >> 16) * alpha) << 16;
sg = (uint32_t)(((sprite.p[source_index] & 0x0000ff00) >> 8) * alpha) << 8;
sb = (sprite.p[source_index] & 0x000000ff ) * alpha;
tr = (uint32_t)((( frame.p[target_index] & 0x00ff0000) >> 16) * anti_alpha) << 16;
tg = (uint32_t)((( frame.p[target_index] & 0x0000ff00) >> 8) * anti_alpha) << 8;
tb = ( frame.p[target_index] & 0x000000ff ) * anti_alpha;
frame.pixels[target_index] = sb + tb + sg + tg + sr + tr;
}
}
#endif
Alternatively we can loop through every pixel in the sprite and blend it with the background. We extract the alpha value and convert it to a floating point between 0 and 1. Then we extract the red, green and blue values, shift them into the lower 8 bits, multiply by alpha and shift back up to their correct byte. We do the same for the background, only using one minus alpha instead. Finally we add the sprite and background colour values together to get the blended pixel value. Note that this is very slow code and is written to be understandable, not fast. If you’re rendering numerous transparent sprites in a game you’ll want to create far more optimized structures and algorithms to draw them.
That’s how to load and display bitmaps in C. In my next tutorials I’ll show you how to load sound files, do precise timing and soon I’ll be showing you how to make a complete small game in C.
If you've got questions about any of the code feel free to e-mail me or comment on the youtube video. I'll try to answer them, or someone else might come along and help you out. If you've got any extra tips about how this code can be better or just more useful info about the code, let me know so I can update the tutorial. Thanks to Froggie717 for criticisms and correcting errors in this tutorial. Cheers.