On Programming

A discussion of programming strategies and results

Write Your Own Bitmaps

| Comments

Bitmaps are quite possibly the simplest image format we use today. They are supported on every platform and in every browser. Bitmaps suffer from one major shortcoming: They are uncompressed. Thankfully harddrive space has never been cheaper, so if you want simple image writing in your program without having to link an image library like libwebp or libpng then writing your own bitmaps is the way to go.

In this post I will attempt to explain the bitmap format. I also have written all the code you need, which will can be downloaded here.

The Header

The bitmap header is so simple that you don’t need any structures to write it. Below is a breakdown of the bytes:

Byte RangeValueExplanation
00x42ASCII for ‘B’
10x4DASCII for ‘M’
2-5File SizeThis will be the full size of the file including the header, the pixel data, and all padding
6-90x00000000This data is reserved but can just be set to 0
10-13Pixel OffsetThis will be how many bytes are in the file before you get to the actual pixel data. In our header the value will be 54.
14-1740We’re going to be writing the BITMAPINFOHEADER header which has a size of 40 bytes
18-21Pixel WidthThe width of the image in pixels
22-25Pixel HeightThe height of the image in pixels
26-271The number of color planes, must be set to 1
28-2924The number of bits per pixel. For an RGB image with a single byte for each color channel the value would be 24
30-330Disable Compression
34-37Size of raw pixel dataThis will have the number of bytes in the pixel data section of the file (the part after the header). Since padding will be added to the rows you cannot simply multiple height * width * 24 and expect it to work
38-412835This is the horizontal resolution. Just leave it at 2835.
42-452835This is the vertical resolution. Just leave it at 2835.
46-490The number of colors, leave at 0 to default to all colors
50-530The important colors, leave at 0 to default to all colors

As you can see from the table, our header consists of 54 bytes (byte 0 to 53). Now all we have left is the actual pixel data.

The Pixel Data

The pixel data has to be written with some minor modifications. First, the bottom row of the image appears first in the data. If you forget this step your image will be vertically flipped. Second, the pixes are written in BGR (blue - green - red) format, which is the opposite of the normal RGB. Finally, at the end of each row you must pad it with bytes till it is a multiple of 4.

Lets look at an example. Lets say I wanted to write a 2x2 image with the colors listed below:

RedGreen
BluePurple

I’d start with an empty data array. I must then start with the bottom row (blue, purple). I write the blue pixel first in BGR format which ends up with:

1
2
3
4
  //                          Blue        
  //                       B     G     R  
  //                      |--------------|
  unsigned char data[] = {0xFF, 0x00, 0x00};
Then I’d write the purple pixel
1
2
3
4
  //                          Blue                Purple
  //                       B     G     R      B    G     R
  //                      |--------------|  |--------------|
  unsigned char data[] = {0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF};
Now, you’ll notice that I have 6 bytes in my pixel data, but I am at the end of the row. This means I need to add 2 more bytes to make it a multiple of 4. I will use 0x00 for padding, but any value will work.
1
2
3
4
  //                          Blue                Purple
  //                       B     G     R      B    G     R     Padding
  //                      |--------------|  |--------------|  |--------|
  unsigned char data[] = {0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00};
Now we add the next row (Red - Green)
1
2
3
4
5
6
7
8
9
10
  //                          Blue                Purple
  //                       B     G     R      B    G     R     Padding
  //                      |--------------|  |--------------|  |--------|
  unsigned char data[] = {0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00,

  /*                          Red                  Green*/
  /*                       B     G     R      B    G     R     Padding*/
  /*                      |--------------|  |--------------|  |--------|*/
                          0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00
  };
Finally, we take the bytes from the header and the bytes from the pixel data, throw them together, and write them to a file.

I’ve uploaded code to handle the encoding of bitmaps for you. This code exposes a single function

1
  size_t bitmap_encode_rgb(const uint8_t* rgb, int width, int height, uint8_t** output);
This function expects RGB data starting from the top of the image, and will output the full byte stream of a bitmap image. Here is an example using it to produce the same 4 pixels we just walked through:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  #include <cinttypes>
  #include <fstream>

  size_t bitmap_encode_rgb(const uint8_t* rgb, int width, int height, uint8_t** output);

  int main(int argc, char** argv)
  {
      //                      Red              Green
      //                |---------------| |--------------|
      uint8_t data[] = {0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00,
                        0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF};
      //                |--------------|  |--------------|
      //                     Blue              Purple
      uint8_t* output;
      size_t output_size = bitmap_encode_rgb(data, 2, 2, &output);

      std::ofstream file_output;
      file_output.open("output.bmp");
      file_output.write((const char*)output, output_size);
      file_output.close();

      delete [] output;
      return 0;
  }

Download

The source code can be downloaded at http://static.paphus.com/blog/bitmap.tar.bz2. It uses C++11 features so you can compile it with this command:

1
  $CXX -std=c++11 bitmap.cpp main.cpp

Comments