C++: How to Write a Bitmap Image from Scratch.

Preview

The approach used in this tutorial is only compatible with systems that process data in little-endian byte format (used in most Intel and AMD machines).

Prerequisites:

I. Introduction to Binary File Formats

A binary file is a collection of bits i.e. 1s and 0s, stored in a file. In most binary file formats information is stored byte by byte, byte is a collection of 8 bits. The bytes are arranged in an orderly fashion to store information. Most binary files have two major portions:

Header in a binary file contains meta data (additional information about the data, for example width and height of an image).

The body of a binary file contains actual data. In case of a bitmap file this is the information about each pixel in the image.

II. What is a Bitmap File?

A bitmap file basically consists of a header of variable size, however most commonly used header is of 54 bytes. In this tutorial we'll be writing a 24 bit Bitmap image. The body of a 24 bit bitmap image contains value for each pixel. Each pixel consists of 3 color channels in BGR (blue, green and red) sequence, where each channel is of 1 byte. Therefore each pixel in a 24 bit bitmap file is of 3 bytes (or 24 bits).
(For detailed specifications of the bitmap file format check: Bitmap-Specifications)

III. Code

(For Complete Example Code: Click Here)

#include  // for specific size integers #include  // for file handling using namespace std; 
Enter fullscreen mode

Exit fullscreen mode

The Bitmap file has two headers for meta data. First header (bitmap file header) tells that this binary file is a bitmap file. The next header (bitmap info header) contains additional meta-data about the image.
For bitmap file header (14 bytes) we need to create the following data structure:

struct BmpHeader  char bitmapSignatureBytes[2] = 'B', 'M'>; uint32_t sizeOfBitmapFile = 54 + 786432; uint32_t reservedBytes = 0; uint32_t pixelDataOffset = 54; > bmpHeader; 
Enter fullscreen mode

Exit fullscreen mode

The first two bytes 'B' and 'M' are unique to the bitmap file format (there still are different versions of it check bitmap format specification). The next four bytes contain total size of the bitmap file in bytes. As we are creating a 512 by 512 bitmap image, total size is the sum of size of meta data (14 + 40 = 54 bytes) and size of pixels (512 * 512 * 3 = 7863432, each pixel is of 3 bytes in 24 bit bitmap file). The last field of bitmap file header contains the offset to the pixel data of the image from the start of the file. In our case the offset is 54 bytes, as our pixel data is right after the bitmap file header and info header (14 + 40 = 54).

The bitmap file has various types of info header, but the most common one is the 40 byte Bitmap Info Header, so we need to write it down in the following data structure:

struct BmpInfoHeader  uint32_t sizeOfThisHeader = 40; int32_t width = 512; // in pixels int32_t height = 512; // in pixels uint16_t numberOfColorPlanes = 1; // must be 1 uint16_t colorDepth = 24; uint32_t compressionMethod = 0; uint32_t rawBitmapDataSize = 0; // generally ignored int32_t horizontalResolution = 3780; // in pixel per meter int32_t verticalResolution = 3780; // in pixel per meter uint32_t colorTableEntries = 0; uint32_t importantColors = 0; > bmpInfoHeader; 
Enter fullscreen mode

Exit fullscreen mode

The height of bitmap file can also be negative (you may have noticed the signed integers). When height is negative the first pixel in file is drawn at top left of the image. However the standard for bitmap files is to use positive height and the first pixel in file is drawn at the bottom left of the image followed by other pixels.

The bitmap file may also contain a color table but it is not mandatory in a 24 bit bitmap file, so we will not be creating one in this example.

As I'll be creating a mono color bitmap image (all pixels have same value), I'm creating an additional struct for the pixel:

struct Pixel  uint8_t blue = 255; uint8_t green = 255; uint8_t red = 0; > pixel; 
Enter fullscreen mode

Exit fullscreen mode

Each channel in a pixel must be an unsigned integer of 1 byte for a 24 bit bitmap file. Where 255 specifies maximum color intensity and 0 specifies absence of the color.

int main(int argc, char **argv)  ofstream fout("output.bmp", ios::binary); fout.write((char *) &bmpHeader, 14); fout.write((char *) &bmpInfoHeader, 40); // writing pixel data size_t numberOfPixels = bmpInfoHeader.width * bmpInfoHeader.height; for (int i = 0; i  numberOfPixels; i++)  fout.write((char *) &pixel, 3); > fout.close(); return 0; > 
Enter fullscreen mode

Exit fullscreen mode

A struct may have padding (additional bytes) added by the compiler for optimizing performance. Hence sizeof() is not going to return accurate value. So I have manually specified the size of each struct in ofstream::write() method.

IV. Conclusion

So if you've succeeded in writing your first bitmap file give yourself a pat on the shoulder. Next you may be interested in writing different types of bitmap files, for that check bitmap specifications. Kudos and keep learning!