BMP Image Rendering: Rendering a image with more than one color
h
MocaCDeveloper

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

@DynamicSquid, @Highwayman

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!

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:

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!

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.

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!

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):

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
  • The padded width.

This will be quite easy, believe it or not:

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:

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:

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!

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:

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

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!

You are viewing a single comment. View All
MocaCDeveloper

@DynamicSquid

How did you become a moderator?