Learn to Code via Tutorials on Repl.it!

← Back to all posts
##### BMP Image Rendering: Rendering a image with more than one color

Welcome back to part 2 of Learning how to render a BMP image, in C!

In this tutorial we will be going over the fundamental skills required to write a image with a specific amount of pixels, and with a larger width and height.

So, lets start!

# Getting started

So, lets take a look back at the raw data again. Just for some reference!

``````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             ``````

If you remember:

• The first 2 numbers represent 'B' and 'M', which basically just tells the file "Hey, we are a BMP".
• The 3rd number is the file size. This will be set to zero in this tutorial due to the fact we're going to be working with a image that we don't know how much memory it needs(or, better off said, how many pixels it will contain).
• Then, we reach, `36 00 00 00`, which is the starting address of the BMP image.
• We then reach `28 00 00 00`, which is the header size.
• After that, we run into the width and height, or, basing it off of the raw data above, `01 00 00 00` and `01 00 00 00`(1x1 image).
• We then get the number of color planes as well as the number of bits per pixel, which are both 2 bytes, and we combine them to one variable(`0x180001`).
• Then, we figure out the compression. If the compression is zero, then the image size is zero(the image size is ignored for images without compression). So, that would result in `00 00 00 00` and `00 00 00 00`.
• Then, we set the horizontal and vertical resolution, and by default it is `0x002e23` for horizontal, and `0x002e23` for vertical.
• The last two numbers are both zero. There are 4 bytes reserved for the number of color palettes, which is zero by default(if you change this number, your rendered BMP image will look different), then the number of important colors is zero(meaning all colors are important).

The above will result in the following C code:

``````#include <stdio.h>
#include <stdlib.h> // we will be using this in this tutorial

int main()
{
/*
* Info Header telling the file, 'Hey, we identify
* as a BMP.
*/
char BMP_INFO_HEADER[ 2 ] = { 'B', 'M' };

/*
* Header, which gives the fundamental information required
* to render the BMP image. This stores the width, height,
* resolution etc.
*/
0, 0x00, // setting file size to zero, for this tut.
0x36, 0x28, // 0x28 is the header size, 0x36 is the starting address of the BMP image.
0, 0, // we don't know the image width or height.
0x180001, // 1 color plane, 24 bits/pixel.
0, 0, // no compression, pixel data size is then zero if compression is zero.
0x002e23, 0x002e23, // horizontal and vertical resolution
0, 0 // no color palette, all colors are important.
};
}``````

Now, lets get dirty!

# Rendering a BMP image with a larger width and height

So, we're going to create a function that will render the BMP image for us when we call it.

This function will take in a few arguments:

• Width
• Pixel array
• Pixel array length
• File to write to

We are also going to make the `BMP_INFO_HEADER` and `BMP_HEADER` variables global.

Lets do this!

``````#include <stdio.h>
#include <stdlib.h>

/*
* Info Header telling the file, 'Hey, we identify
* as a BMP.
*/
const char BMP_INFO_HEADER[ 2 ] = { 'B', 'M' };

/*
* Header, which gives the fundamental information required
* to render the BMP image. This stores the width, height,
* resolution etc.
*/
0, 0x00, // setting file size to zero, for this tut.
0x36, 0x28, // 0x28 is the header size, 0x36 is the starting address of the BMP image.
0, 0, // we don't know the image width or height.
0x180001, // 1 color plane, 24 bits/pixel.
0, 0, // no compression, pixel data size is then zero if compression is zero.
0x002e23, 0x002e23, // horizontal and vertical resolution
0, 0 // no color palette, all colors are important.
};

void create_image(FILE* file, int width, char *pixel_array, int length)
{
// code..
}

int main()
{
// code..
}``````

In the above code, we make the `BMP_INFO_HEADER` and `BMP_HEADER` global. We set the `BMP_INFO_HEADER` to const since the values it contains will not change.

We then implemented a function that takes in the arguments: `FILE* file`, `int width`, `char *pixel_array` and `int length`.

• `char *pixel_array` will be the pixel array used to set the pixels at the corresponding bit in the image.
• `int length` is going to be the length of `char *pixel_array`
• `FILE* file` will be the file we write to. Make sure to set the identifier to 'wb', since we are primarily writing bytes to the file.

Now, lets add some more code!
But wait Moca, we don't know the height!

# How to configure the height

This is a function I managed to come across while messing around with configuring the right height for an image.

``````int configure_height(int width, int len)
{
if((width * len) % 4 == 0)
{
return ((width * len) + (2 * (width * len))) / (width + len);
}
return (len * 2) / width;
}``````

In this, we check if the width multiplied by the length of the pixel array modulo of 4 is zero. And, if it is, we do the simple little equation `((width * len) + (2 * (width * len))) / (width + len)` which will return a height that seems to fit well with what the width is. Otherwise, it returns `(len * 2) / width`. Pretty straightfoward.

Lets implement it into the rest of our code!

``````#include <stdio.h>
#include <stdlib.h>

/*
* Info Header telling the file, 'Hey, we identify
* as a BMP.
*/
const char BMP_INFO_HEADER[ 2 ] = { 'B', 'M' };

/*
* Header, which gives the fundamental information required
* to render the BMP image. This stores the width, height,
* resolution etc.
*/
0, 0x00, // setting file size to zero, for this tut.
0x36, 0x28, // 0x28 is the header size, 0x36 is the starting address of the BMP image.
0, 0, // we don't know the image width or height.
0x180001, // 1 color plane, 24 bits/pixel.
0, 0, // no compression, pixel data size is then zero if compression is zero.
0x002e23, 0x002e23, // horizontal and vertical resolution
0, 0 // no color palette, all colors are important.
};

/*
* Configure the height dependable on the width and
* length of the pixel array.
*/
int configure_height(int width, int len)
{
if((width * len) % 4 == 0)
{
return ((width * len) + (2 * (width * len))) / (width + len);
}
return (len * 2) / width;
}

void create_image(FILE* file, int width, char *pixel_array, int length)
{
// code..
}

int main()
{
// code..
}``````

Perfect. Now, lets dive into the `create_image` function, shall we?

# Implementing the `create_image` function

So, we need another function for the padded width of each pixel. This just makes sure that pixels don't collide into one another.

Lets quickly add that(oopsie little ol Moca forgetting things):

``````#include <stdio.h>
#include <stdlib.h>

/*
* Info Header telling the file, 'Hey, we identify
* as a BMP.
*/
const char BMP_INFO_HEADER[ 2 ] = { 'B', 'M' };

/*
* Header, which gives the fundamental information required
* to render the BMP image. This stores the width, height,
* resolution etc.
*/
0, 0x00, // setting file size to zero, for this tut.
0x36, 0x28, // 0x28 is the header size, 0x36 is the starting address of the BMP image.
0, 0, // we don't know the image width or height.
0x180001, // 1 color plane, 24 bits/pixel.
0, 0, // no compression, pixel data size is then zero if compression is zero.
0x002e23, 0x002e23, // horizontal and vertical resolution
0, 0 // no color palette, all colors are important.
};

/*
* Configure the padded width of each pixel
* so no pixels overlap each other.
*/
int r4(int width)
{
return width % 4 == 0 ? width : width - width % 4 + 4;
}

/*
* Configure the height dependable on the width and
* length of the pixel array.
*/
int configure_height(int width, int len)
{
if((width * len) % 4 == 0)
{
return ((width * len) + (2 * (width * len))) / (width + len);
}
return (len * 2) / width;
}

/*
* This function will render the BMP image dependable on the width, height and pixel_array.
* The function also configures the padded width, the height, as well as what pixel
* goes to what index of the BMP image array.
*/
void create_image(FILE* file, int width, char *pixel_array, int length)
{
// code..
}

int main()
{
// code..
}``````

Ok, basically what the function `r4` does is set the padding to a even integer. If the width modulo four is zero, then we just return the width. Else, we return 4.(it was taken from documentation. I do not know why we do `width - width % 4 + 4` when we could just return 4).

Anywho, lets dive into the function `create_image`!

So, first we have to configure a few things:

• The image height

This will be quite easy, believe it or not:

``````void create_image(FILE* file, int width, char *pixel_array, int length)
{
// Configuring height.
int height = configure_height(width, length) - 10;

int paddedw = r4(width * 3); // 3 bits per pixel.
}``````

Quick Note: Subtracting 10 from the height just seemed to work. That was purely something I added in by myself since I created the function `configure_height` by myself. So, subtracting 10 seemed to work just fine and rendered the images perfectly.

Pretty simple. I want to go over the line `int paddedw = r4(width * 3);` however.

What is this doing? Well, we take the width multiplied by three to justify 3 bits per pixel across the width of the image. This is passed to the function r4 to make sure the number is even, and if not, it defaults to a padded width of 4.

Note: The padded width does not have any sort of offset to the pixels in the image. It will not shift them anywhere. It rather helps us get the correct pixel index of the image array and assign it the corresponding pixel the bit needs to be assigned to. This will make more sense later on

Now, lets allocate memory for the image array.
But, before we do that, we have to configure the actual size of the file(I like to call it the image size because the file size is just representing the size of the image).

So, I went over this real briefly in the last tutorial. We use the equation `3 * height * (width - 1)` to configure the image size.

### Why does this equation work?

There are 3 bits per pixel going against the height, and against the width. We subtract the width by 1 so there are no extra pixels lying around the edge of the image.

## Lets Implement This!

Here is the C code we should now have:

``````void create_image(FILE* file, int width, char *pixel_array, int length)
{
// Configuring height.
int height = configure_height(width, length);

int paddedw = r4(width * 3); // 3 bits per pixel.

// Getting the image size.
int image_size = 3 * height * (width - 1);

/*
* Allocating memory for the image array. The elements
* will be assigned depending on each section of the height,
* each section of the width, and to each bit of each pixel.
* Each element will have the size of a character(1 byte).
*/
char *bmp_image = (char*)calloc(image_size, sizeof(char));
}``````

Simple enough. We configured the image size, and then allocated the corresponding memory to the variable pointer `*bmp_image`, each element with the size of a character(1 byte).

Simple enough. Lets move on to the more dirty stuff!

## Lets make sure our code is working

So, with the code we have now, we should of successfully configured the height, the padded width, the image size and the memory allocation needed for the actual image.

Lets render a black image to see if the information will work:

``````void create_image(FILE* file, int width, char *pixel_array, int length)
{
// Configuring height.
int height = configure_height(width, length);

int paddedw = r4(width * 3); // 3 bits per pixel.

// Getting the image size.
int image_size = 3 * height * (width - 1);

/*
* Allocating memory for the image array. The elements
* will be assigned depending on each section of the height,
* each section of the width, and to each bit of each pixel.
* Each element will have the size of a character(1 byte).
*/
char *bmp_image = (char*)calloc(image_size, sizeof(char));

/*
* Now, we have to set the image size, the width and the height to the BMP_HEADER array.
*/

/*
* We will loop through the image_size and set each index
* of the bmp_image to `0x00`, which should render a black image.
*/
for(int i = 0; i < image_size; i++) bmp_image[i] = 0x00;

/*
* We will now write the information into the file.
*/
fwrite(bmp_image, image_size * sizeof(bmp_image), 1, file);
fclose(file);
}``````

Quick Note: Set the width to 20 for testing purposes, if the width and height are above 50 then rendering each pixel becomes more of a challenge(the pixel array has to match up with the height and width. If there is not enough pixel values in the pixel array, the image will fail to render due to lack of pixels).

Lets take a look at the line that writes the image in:
`fwrite(bmp_image, image_size * sizeof(bmp_image), 1, file);`

### What is this line doing?

So, we write the information stored in `bmp_image`. The size is going to be the `image_size` multiplied by the `sizeof` the image array(`bmp_image`).
This confirms that every bit of the image array is being written into the .bmp file.

Copy and paste the above code, and inside of the `.bmp` file, you should see a black image!

See the black image? Great! The code works correctly, as expected. Now, lets get into rendering each pixel to each bit corresponding to the height and width(via using for loops).

# Rendering the pixel array into the image

You're probably going to get a headache getting this down. It took me awhile to understand it.

There are a few steps to this:

• Looping through the height of the image
• Looping through the width of the image
• Looping through the 3 bits required for each pixel, then assigning each pixel to each bit.

So, lets not waste anytime here and dive right in!

``````void create_image(FILE* file, int width, char *pixel_array, int length)
{
// Configuring height.
int height = configure_height(width, length) - 10;

int paddedw = r4(width * 3); // 3 bits per pixel.

// Getting the image size.
int image_size = 3 * height * paddedw;

/*
* Allocating memory for the image array. The elements
* will be assigned depending on each section of the height,
* each section of the width, and to each bit of each pixel.
* Each element will have the size of a character(1 byte).
*/
char *bmp_image = (char*)calloc(image_size, sizeof(char));

for(int i = 0; i < image_size; i++) bmp_image[i] = 0x00;

/*
* Now, we have to set the image size, the width and the height to the BMP_HEADER array.
*/

/*
* Rendering each pixel to the corresponding row,
* column, and bit of each pixel.
*/
for(int row = 0; row < height; row++)
{
for(int col = 0; col < width; col++)
{
/*
* Rendering each pixel to each bit of
* each row and column.
*/
for(int b = 0; b < 3; b++)
{
/*
* This grabs the index at the specified row, by the padded width, then multiplied by
* 3(for 3 bits per pixel), added by the current bit
*/
int index = row * paddedw + col * 3 + b;

/*
* Assigning the current index to the corresponding pixel at the current row,
* according to the width and the current column.
*/
bmp_image[index] = pixel_array[3*(row * width + col) + (2 - b)];
}
}
}

/*
* We will now write the information into the file.
*/
fwrite(bmp_image, image_size * sizeof(bmp_image), 1, file);
fclose(file);

free(bmp_image);
}``````

Now, you're probably astounded by the amount of code we added. But, don't worry. I will walk you through what happens.

I think the comments are pretty explanatory. But, I will still explain what's going on.
So, first, we loop through each row(which would be the height). Then, we loop through each column(which would be the width). Then, for each element of the image, we write 3 bits per pixel. So, we then find the relative element dependable on the current row, by the padded width multiplied by 3(for the pixel size) by the current bit(via the for loop looping for each 3 bits of the current pixel).

It's quite confusing. But, hang in there with me! `int index` just finds the corresponding index for the current pixel it will be rendering.
Then, the next line, `pixel_array[3 * (row * width + col) + (2 - b)]` gets the pixel data at the index for the current col and row.

Now, you can change this. It does not have to be exactly as the example above shows. But, if you want the image to render normally without looking funky, I'd suggest getting the above code locked into your memory!

Then, last but not least, we want to free the memory so we don't leak any out. So we free the memory allocated to the `bmp_image` variable.

Now, lets see an example of a image we could render(examples taking from documentations covering BMP image rendering)

# Rendering the image!

So, the current C code is as so:

``````#include <stdio.h>
#include <stdlib.h>]

/*
* Info Header telling the file, 'Hey, we identify
* as a BMP.
*/
const char BMP_INFO_HEADER[ 2 ] = { 'B', 'M' };

/*
* Header, which gives the fundamental information required
* to render the BMP image. This stores the width, height,
* resolution etc.
*/
0, 0x00, // setting file size to zero, for this tut.
0x36, 0x28, // 0x28 is the header size, 0x36 is the starting address of the BMP image.
0, 0, // we don't know the image width or height.
0x180001, // 1 color plane, 24 bits/pixel.
0, 0, // no compression, pixel data size is then zero if compression is zero.
0x002e23, 0x002e23, // horizontal and vertical resolution
0, 0 // no color palette, all colors are important.
};

/*
* Configure the padded width of each pixel
* so no pixels overlap each other.
*/
int r4(int width)
{
return width % 4 == 0 ? width : width - width % 4 + 4;
}

/*
* Configure the height dependable on the width and
* length of the pixel array.
*/
int configure_height(int width, int len)
{
if((width * len) % 4 == 0)
{
return ((width * len) + (2 * (width * len))) / (width + len);
}
return (len * 2) / width;
}

/*
* This function will render the BMP image dependable on the width, height and pixel_array.
* The function also configures the padded width, the height, as well as what pixel
* goes to what index of the BMP image array.
*/
void create_image(FILE* file, int width, char *pixel_array, int length)
{
// Configuring height.
int height = configure_height(width, length) - 10;

int paddedw = r4(width * 3); // 3 bits per pixel.

// Getting the image size.
int image_size = 3 * height * paddedw;

/*
* Allocating memory for the image array. The elements
* will be assigned depending on each section of the height,
* each section of the width, and to each bit of each pixel.
* Each element will have the size of a character(1 byte).
*/
char *bmp_image = (char*)calloc(image_size, sizeof(char));

for(int i = 0; i < image_size; i++) bmp_image[i] = 0x00;

/*
* Now, we have to set the image size, the width and the height to the BMP_HEADER array.
*/

/*
* Rendering each pixel to the corresponding row,
* column, and bit of each pixel.
*/
for(int row = 0; row < height; row++)
{
for(int col = 0; col < width; col++)
{
/*
* Rendering each pixel to each bit of
* each row and column.
*/
for(int b = 0; b < 3; b++)
{
/*
* This grabs the index at the specified row, by the padded width, then multiplied by
* 3(for 3 bits per pixel), added by the current bit
*/
int index = row * paddedw + col * 3 + b;

/*
* Assigning the current index to the corresponding pixel at the current row,
* according to the width and the current column.
*/
bmp_image[index] = pixel_array[3*(row * width + col) + (2 - b)];
}
}
}

/*
* We will now write the information into the file.
*/
fwrite(bmp_image, image_size * sizeof(bmp_image), 1, file);
fclose(file);

free(bmp_image);
}

int main()
{
// Code here
}``````

Ok, that should be the code you have currently sitting in the .c file.

Now, lets take a look at the main function, and see what we need to add.

• The File
• The pixel array
• Call the `create_image` function dependable on a set width, the pixel array, and the pixel array length

Lets get to work!

This should be self explanatory.

``````int main()
{
/*
* Creating a file 'new.bmp' to hold this image.
*/
FILE* file = fopen("new.bmp", "wb");

/*
* This will be the pixel array that will be used to render the BMP image
*/
char fr[] = {
0, 85, 164,    // Bleu
255, 255, 255, // Blanc
239, 65, 53,   // Rouge
0, 85, 164,
255, 255, 255,
239, 65, 53,
};

/*
* calling the create_image function to create a image with a width of 3
*/
create_image(file, 3, fr, sizeof(fr) / sizeof(char));
}``````

Now, take a look at the .bmp file that was rendered. You should see something like: ## Keep Note:

If you have a image with a width or height greater than one, then you have to write a pixel for each bit. In the last tutorial, we worked with a 1x1 image, which would just require a single bit(or a single pixel). In this tutorial, we work with rendering a image with a greater width and height, therefore, we have to write a corresponding pixel to the corresponding index of the BMP image array(for each bit).
P.S: I'd highly suggest not rendering a 1x1 image. There is quite literally no point in it.

# Summary

You now have the fundamental skills needed to render a BMP image. Throughout this tutorial, you learned:

• How to configure the height
• How to configure the padded width
• What the padded width is
• How to declare the bmp image array(and allocate the right amount of memory)
• How to assign each bit a pixel value in the image

Now, you can create images like a pro! Getting the hang of the pixel array will be quite hard. You have to assign each index of the pixel array to a specific rgb value in order for your image to look the way you want. But, with time, you can achieve anything!

Next tutorial I will walk you through how to read a bmp image. We will go more in depth on more advanced things about a bmp image, how to read its data, and more on how to read the raw-data..as well as how to print out all the raw-data via the terminal!

# Sincerely

I hope you enjoyed this tutorial. You have one more tutorial before you're a BMP writing/BMP reading pro! Hang in there, you're almost done!

Until then. MocaCDeveloper, OUT!

hotnewtop
DynamicSquid (4816)

This is awesome! It's almost night time for me, so I'll take a better look at it tomorrow :D

MocaCDeveloper (603)

Let me know if you have any questions.

I will be more the willing to answer any questions you may have!

MocaCDeveloper (603)

How did you become a moderator?

DynamicSquid (4816)

@MocaCDeveloper I asked Drone if he needed another mod, and he said no at first, but then he kicked a couple of inactive mods, and then I became mod!