Learn to Code via Tutorials on Repl.it!

← Back to all posts
BMP Image Rendering in C!
h
MocaCDeveloper (713)

Hi all!

It's C GOD coming back at you with another tutorial!
(I don't know why I am acting like I am having a intro to a video or something, lol)

Anyway :D

Lets get into this!

First off, what is a BMP image?

Well, I am not gonna waste your time with an explanation. So, here is the best explanation I found of a BMP image:

The BMP file format, also known as bitmap image file, device independent bitmap file format and bitmap, is a raster graphics image file format used to store bitmap digital images, independently of the display device, especially on Microsoft Windows and OS/2 operating systems

So. Now that you know what a BMP image is(and even if you don't, it shouldn't matter), lets dive into how to render a BMP image!

First step, the Info Header

The Info Header tells the .bmp file, "Hey, I identify as a BMP".

So. How do we do that? Well, this is quite simple actually.
Take a look at the code below!

#include <stdio.h>
#include <stdlib.h> // might not be used in the tutorial

int main()
{
    char BMP_INFO_HEADER[ 2 ] = { 'B', 'M' };
}

The BMP Info Header consists of the letters 'B' and 'M'. This is required for any and all images rendered into a .bmp file.

This is raw data taken from a .bmp file:

42 4d 3a 00 00 00 00 00 00 00 36 00 00 00 28 00
00 00 01 00 00 00 01 00 00 00 01 00 18 00 00 00
00 00 04 00 00 00 23 2e 00 00 23 2e 00 00 00 00
00 00 00 00 00 00 35 41 ef 00  

Note: Just focus on the first 2 numbers. Everything else will be explained later on!

If you will notice, the first two numbers are 42(B) and 4d(M).
This is the Info Header of the file, as I had once said:

tells the .bmp file, "Hey, I identify as a BMP".

The header

Lets get a bit dirty now, shall we?

Lets take another look at the raw data from the .bmp file:

42 4d 3a 00 00 00 00 00 00 00 36 00 00 00

We already went over 42 and 4d, lets take a look at the 3a.
What could this stand for? Well, 3a would be the file size.
Now, when rendering a bmp image, you do not need to know the file size right off the bat. In fact, there is no way to know it right off the bat.

Lets take a more advanced look at this data:

  • 4 bytes for the file size: 3a 00 00 00
  • 4 bytes reserved: 00 00 00 00
  • 4 bytes pixel offset: 36 00 00 00

Note: These are all integers, so this will be a integer array in C.

Lets see how we'd do this in our C code!

#include <stdio.h>
#include <stdlib.h> // might not be used in the tutorial

int main()
{
    char BMP_INFO_HEADER[ 2 ] = { 'B', 'M' };
    int BMP_HEADER[ ] = {
       0, // file_size. We don't know this yet, set it to 0
       0, // reserved
       0x36 // pixel offset
    };
}

We now have the file size, the reserved 4 bytes and the pixel offset in the BMP_HEADER array.

Now, lets move on to the rest!

28 00 00 00

The next set of raw data is the header size.
Lets implement this into our C code now!

#include <stdio.h>
#include <stdlib.h> // might not be used in the tutorial

int main()
{
    char BMP_INFO_HEADER[ 2 ] = { 'B', 'M' };
    int BMP_HEADER[ ] = {
       0, 0, // File size, reserved
       0x36, 0x28 // Pixel offset, header size
    };
}

We simply just add 0x28 to our BMP_HEADER array to define the header size.

Note: BMP_HEADER[2] & BMP_HEADER[3] will always be 0x36 and 0x28.

Now, onto the next section!

The width and height of the image

Now, in the example image we're using, the image width and height are 1x1(1 pixel). But, don't worry, this too can change just like the file size!

But, disregarding the fact this example image is a 1x1 image, we'll work with what we got!

Here is the raw data to define the image width and height:

01 00 00 00 ; width
01 00 00 00 ; height

They're both the same. As I said, it's a 1x1 image.
Lets add this to our BMP_HEADER array, shall we?

#include <stdio.h>
#include <stdlib.h> // might not be used in the tutorial

int main()
{
    char BMP_INFO_HEADER[ 2 ] = { 'B', 'M' };
    int BMP_HEADER[ ] = {
       0, 0, // File size, reserved
       0x36, 0x28, // Pixel offset, header size
       1, 1 // width, height
    };
}

We have now successfully added in the width AND the height to the BMP_HEADER array.

What if we don't know the width and the height and want to assign it later?

not to worry. Just set the index value of 4 and 5 to zero by default, and then reset the values at those indexes later on when you know the image width and height.

Number of color planes/Bits per pixel

Lets take a look at the next section of raw data:

01 00 18 00

01 00 is the number of color planes(which would result in 1).
18 00 is the bits per pixel in each plane.

We combine these two numbers into a single number for efficiency. Each count as 2 bytes, however, we will be writing 4 bytes at once, since they both interact with each other.

So, lets add this to our BMP_HEADER array!

#include <stdio.h>
#include <stdlib.h> // might not be used in the tutorial

int main()
{
    char BMP_INFO_HEADER[ 2 ] = { 'B', 'M' };
    int BMP_HEADER[ ] = {
       0, 0, // File size, reserved
       0x36, 0x28, // Pixel offset, header size
       1, 1, // width, height
       0x180001, // number of color planes with 24 bits/pixel
    };
}

As simple as that was, I think you might slowly be realizing this is all just a matter of memorizing what each index should be what value, and what indexes should not be touched whatsoever.

Anywho, back to the code!

We now have successfully added in the number of color planes and bits per pixel(in one number) to the BMP_HEADER array.

The next few steps are quite simple. You will most likely not catch yourself changing these values(because if you do, the whole image will change).

Compression and pixel data size

Lets take a look at these sections of the raw data:

00 00 00 00 ; compression
04 00 00 00 ; pixel data size

Now, since the compression is zero, the pixel data size, too, will be zero. So, as I said:

The next few steps are quite simple

Lets add this to our array!

#include <stdio.h>
#include <stdlib.h> // might not be used in the tutorial

int main()
{
    char BMP_INFO_HEADER[ 2 ] = { 'B', 'M' };
    int BMP_HEADER[ ] = {
       0, 0, // File size, reserved
       0x36, 0x28, // Pixel offset, header size
       1, 1, // width, height
       0x180001, // number of color planes with 24 bits/pixel
       0, 0 // compression - zero, pixel data size - zero 
    };
}

Now, you can play around with the compression and the pixel data size to see how exactly it changes your image. But, if you want your image to render normally and not look funky, I'd highly suggest keeping these two numbers as zeroes!

Horizontal and Vertical resolution of the image

This simply sets the resolution for the image. Lets look at the raw data:

23 2e 00 00
23 2e 00 00

They're both the same(because why would you have on with a higher resolution and the other with a lower? LOL)

All jokes aside(cough), lets add this to our code, shall we?

#include <stdio.h>
#include <stdlib.h> // might not be used in the tutorial

int main()
{
    char BMP_INFO_HEADER[ 2 ] = { 'B', 'M' };
    int BMP_HEADER[ ] = {
       0, 0, // File size, reserved
       0x36, 0x28, // Pixel offset, header size
       1, 1, // width, height
       0x180001, // number of color planes with 24 bits/pixel
       0, 0, // compression - zero, pixel data size - zero 
       0x002e23, 0x002e23 // horizontal and vertical resolution
    };
}

Pretty simple. As I stated before:

I think you might slowly be realizing this is all just a matter of memorizing what each index should be what value, and what indexes should not be touched whatsoever.

Onto the last step!

Number of color palettes and Number of important colors

Both of these are zero. Pretty straight forward.

You can change these values, but unless you want your image to look funky, I'd suggest keeping them as zeroes.

Here is the raw data for both(I mean, I think you could probably guess what the raw data is gonna be):

00 00 00 00
00 00 00 00

Note: Since Number of important colors is zero, that means all colors are important.

Lets add this to our BMP_HEADER array now!

#include <stdio.h>
#include <stdlib.h> // might not be used in the tutorial

int main()
{
    char BMP_INFO_HEADER[ 2 ] = { 'B', 'M' };
    int BMP_HEADER[ ] = {
       0, 0, // File size, reserved
       0x36, 0x28, // Pixel offset, header size
       1, 1, // width, height
       0x180001, // number of color planes with 24 bits/pixel
       0, 0, // compression - zero, pixel data size - zero 
       0x002e23, 0x002e23, // horizontal and vertical resolution
       0, 0 // no color palette.
    };
}

Great! We got the basic BMP_INFO_HEADER array that includes the letters 'B' and 'M' to identify to the .bmp file that it's a BMP.
And we have now successfully setup the BMP_HEADER array which gives the BMP image the information it needs:

  • file size
  • pixel offset and the header size
  • width & height
  • number of color planes, and the bits-per-pixel
  • resolution
  • color palette(if any at all, default = zero)

Lets now render the image!

Rendering the image

First, before all, we have to write the info header to the file!
We are going to be going off of the raw data we have been working with. In this tutorial, I have set the file size to zero. But, below, I am setting it to 0x3a just so we can follow the raw data and not get confused. I will explain how you can set the file size(BMP_HEADER[0]) at the end of the tutorial.

So, lets do this!

#include <stdio.h>
#include <stdlib.h> // might not be used in the tutorial

int main()
{
    FILE* file = fopen("img.bmp", "wb");
    char BMP_INFO_HEADER[ 2 ] = { 'B', 'M' };
    int BMP_HEADER[ ] = {
       0x31, 0, // File size, reserved
       0x36, 0x28, // Pixel offset, header size
       1, 1, // width, height
       0x180001, // number of color planes with 24 bits/pixel
       0, 0, // compression - zero, pixel data size - zero 
       0x002e23, 0x002e23, // horizontal and vertical resolution
       0, 0 // no color palette.
    };

    fwrite(BMP_INFO_HEADER, sizeof(BMP_INFO_HEADER), 1, file); // write the info header to the file to identify itself as a BMP
    fwrite(&BMP_HEADER, sizeof(BMP_HEADER), 1, file); // Write the image information consisting of its width, height, size, resolution etc.
    // To-Do: Render the actual image
    fclose(file); 
}

It's quite simple. You just write the data into the file.

Now, lets render an actual image using the data that was written to the file!

Creating a 1x1 image

It's quite simple now. We're going to be creating a char array with values that indicate a color in hex.

#include <stdio.h>
#include <stdlib.h> // might not be used in the tutorial

int main()
{
    FILE* file = fopen("img.bmp", "wb");
    char BMP_INFO_HEADER[ 2 ] = { 'B', 'M' };
    int BMP_HEADER[ ] = {
       0x31, 0, // File size, reserved
       0x36, 0x28, // Pixel offset, header size
       1, 1, // width, height
       0x180001, // number of color planes with 24 bits/pixel
       0, 0, // compression - zero, pixel data size - zero 
       0x002e23, 0x002e23, // horizontal and vertical resolution
       0, 0 // no color palette.
    };
    
    // storing as char array because we want each value to take up 1 byte.
    char bitmap[] = {
        0x35, // Blue
        0x41, // Green
        0xef, // Red - this will be the color of the image.
        0x00  // Padding
    };

    fwrite(BMP_INFO_HEADER, sizeof(BMP_INFO_HEADER), 1, file); // write the info header to the file to identify itself as a BMP
    fwrite(&BMP_HEADER, sizeof(BMP_HEADER), 1, file); // Write the image information consisting of its width, height, size, resolution etc.
    fwrite(bitmap, sizeof(bitmap), 1, file); // write the image
    fclose(file); 
}

Now, if you're following along and copying and pasting the code, you'll realize the image is just red.
And you're probably wondering: "Why is it just red when we have 2 other colors!?"

Explanation:
Each color is a byte. First it's blue, secondly it's green, then lastly the color red.
Since the image is a 1x1 image, the last byte, red, will be the image color. Now, if you make the image a bit bigger, you will see the other colors that were rendered. But we're simply just working with a 1x1 image in this tutorial.

How to set a custom file size

Lets revert back to the C code I had:

#include <stdio.h>
#include <stdlib.h> // might not be used in the tutorial

int main()
{
    char BMP_INFO_HEADER[ 2 ] = { 'B', 'M' };
    int height = 1;
    int width = 1;
    int BMP_HEADER[ ] = {
       0, 0, // File size, reserved
       0x36, 0x28, // Pixel offset, header size
       width, height, // width, height
       0x180001, // number of color planes with 24 bits/pixel
       0, 0, // compression - zero, pixel data size - zero 
       0x002e23, 0x002e23, // horizontal and vertical resolution
       0, 0 // no color palette.
    };
}

We can easily assign the file size by taking the size of the BMP_INFO_HEADER, the size of the BMP_HEADER and the image size.

Lets see an example of that!

#include <stdio.h>
#include <stdlib.h> // might not be used in the tutorial

int main()
{
    char BMP_INFO_HEADER[ 2 ] = { 'B', 'M' };
    int height = 1;
    int width = 2;
    int BMP_HEADER[ ] = {
       0, 0, // File size, reserved
       0x36, 0x28, // Pixel offset, header size
       width, height, // width, height
       0x180001, // number of color planes with 24 bits/pixel
       0, 0, // compression - zero, pixel data size - zero 
       0x002e23, 0x002e23, // horizontal and vertical resolution
       0, 0 // no color palette.
    };
    int image_size = 3 * height * (width - 1);
    BMP_HEADER[0] = sizeof(BMP_HEADER) + sizeof(BMP_INFO_HEADER)  + image_size;
}

Note: You most likely won't be dealing with a 1x1 image. So, the image height and width will be more than just 1. In the example above, I set width to 2 so we can get the image size.

There are 3 bits per pixel, so we multiply 3 into height * (width - 1).

It is as simple as that!

Summary

BMP image rendering is not easy. You have to know how to read the raw data, and how to render it correctly.
But, I hope throughout this tutorial you have learned the fundamental skills needed to render a basic 1x1 image in C!

Next tutorial will be about rendering larger BMP images via using C!

C GOD logging out!
(I really need to stop doing this lmao)

Comments
hotnewtop
Highwayman (1501)

I got really confused at the part where you start talking about the header. You say that we can't know the file size at the start, but then you say there is a space to enter the file size in the header...? What did I miss?

MocaCDeveloper (713)

@Highwayman

Ok. So. In this tutorial, we are working with raw-data that describes a 1x1 image.

So, we knew the size of the file right off the bat because we're literally reading the raw-data of the bmp image.

But, as you get more and more advanced, you will not know the file size. So, we set the file size(BMP_HEADER[0) to zero, and reassign it later on.

I know, it was quite confusing because I kept with the concept of not knowing what the file size is going to be, then I referenced the 1x1 file size at the end. I shouldn't of done that.

But, towards the end, I assign BMP_HEADER[0] to the default file size that was given inside the raw-data, instead of doing the more advanced way of figuring out the file size.

But, lets say you're working with a image that is rendered dependable on what the user wants. And we don't know the exact width and height. Then, we'd do:

int BMP_HEADER[] = {
    0, 0x00, // [0] - we don't know the file size
    0x36, 0x28,
    0, 0, // we don't know the height or width
    0x180001,
    0, 0,
    0x002e23, 0x002e23, 0, 0
};

Since we don't know the width or height, we assign BMP_HEADER[0], BMP_HEADER[4] and BMP_HEADER[5] to zero.

Then, when we do get the image width and height, we just assign the values to the indexes of 4 and 5:

BMP_HEADER[4] = width;
BMP_HEADER[5] = height;

Then, to configure the file size, we take the size of the BMP_HEADER, the size of the BMP_INFO_HEADER, and then the image size(which is 3 * height * (width - 1), which is just configuring 3 bits per pixel, then subtracting 1 from the width so the image is rendered without extra pixels just lying around on the edge).

That would then look like:

int size = 3 * height * (width - 1);
BMP_HEADER[0] = sizeof(BMP_HEADER) + sizeof(BMP_INFO_HEADER) + size;

And then, we have now configured the width and height, as well as the file size.

Highwayman (1501)

ahhh! I see now ok, thank you. @MocaCDeveloper

MocaCDeveloper (713)

@Highwayman

I will be posting a tutorial tonight over more advanced topics over rendering a bmp image dependable on a greater width and height. I will make sure to @ you in it!

DynamicSquid (5023)

@MocaCDeveloper Ooh, this is awesome! Can you also ping me? :D

MocaCDeveloper (713)

@DynamicSquid @Highwayman My WiFi had gone out last night. I will be posting the tutorial today

firefish (953)

shouldn't of done that

@MocaCDeveloper ah yes, grammar

MocaCDeveloper (713)

@firefish

When you get typing really fast you tend to look away from the grammar being written and more towards the words being written. Lol.

firefish (953)

but anyway, it's shouldn't have @MocaCDeveloper

MocaCDeveloper (713)

@firefish

Thank you autocorrect :) (lol).
It's been a hot minute since we've last talked.

firefish (953)

@MocaCDeveloper Also, about the tutorial, I've tried reconstructing 1x1 pngs in nodejs and well... I realised pngs are compressed ;-;

DynamicSquid (5023)

I tried your example on "Creating a 1x1 image", and I saw a small red dot, but then I tried your "How to set a custom file size", and it says it couldn't support the image.

Here's my code:

#include <stdio.h>
#include <stdlib.h> // might not be used in the tutorial

int main()
{
    FILE* file = fopen("img.bmp", "wb");
    char BMP_INFO_HEADER[ 2 ] = { 'B', 'M' };
    int height = 1;
    int width = 2;
    int BMP_HEADER[ ] = {
       0, 0, // File size, reserved
       0x36, 0x28, // Pixel offset, header size
       width, height, // width, height
       0x180001, // number of color planes with 24 bits/pixel
       0, 0, // compression - zero, pixel data size - zero 
       0x002e23, 0x002e23, // horizontal and vertical resolution
       0, 0 // no color palette.
    };
    int image_size = 3 * height * (width - 1);
    BMP_HEADER[0] = sizeof(BMP_HEADER) + sizeof(BMP_INFO_HEADER)  + image_size;
    
    // storing as char array because we want each value to take up 1 byte.
    char bitmap[] = {
        0x35, // Blue
        0x41, // Green
        0xef, // Red - this will be the color of the image.
        0x00  // Padding
    };

    fwrite(BMP_INFO_HEADER, sizeof(BMP_INFO_HEADER), 1, file); // write the info header to the file to identify itself as a BMP
    fwrite(&BMP_HEADER, sizeof(BMP_HEADER), 1, file); // Write the image information consisting of its width, height, size, resolution etc.
    fwrite(bitmap, sizeof(bitmap), 1, file); // write the image
    fclose(file); 
}

Am I just being dumb and forgetting something?

MocaCDeveloper (713)

@DynamicSquid

No no, lol, you're not being dumb.

So, when we set the file size manually, we're usually going to be working(well not usually, we WILL be working) with a unknown height and width.
And, since in this example, we are only creating one pixel, then we really don't need to set a manual size of 3 * height * (width - 1) because it's just one pixel.

So, it's erroring because of the fact the rendered image is extremely small(1x1) and the file size is far to large.
Now, when we get into larger images that aren't 1x1, which is probably going to be the norm because why would you create a 1x1 image, lol, we'll start practicing setting the file size manually via 3 * height * (width - 1).

MocaCDeveloper (713)

@DynamicSquid

Also, something else to point out.
When working with manual file sizes(since we won't know the height or width), if the height or width is greater than 1, you have to write a pixel for each bit of the image(via looping through the height and width, then looping through each bit and writing the appropriate pixel for each bit).
So, width is 2 in the example above(which was just an example of how to set a manual file size). And so, when you set the BMP image width to width(which is 2), that requires us to write to 6 bits, and we only write to 1 pixel(3 bits). So, that could also be the reason why it's erroring because the file size is not matching up with the correct bit-size of the image, and not all bits are covered and assigned to a pixel value.

I will most likely go more into depth with this concept in the tutorial I am posting today!

DynamicSquid (5023)

@MocaCDeveloper Ahh okay, that makes more sense. Can't wait for today's tutorial :D