Bitmap Binary Principles

BMP File Structure Explained from the Binary Level Up

Why BMP Is the Best Format for Learning Binary

In my experience teaching image file formats, BMP (Windows Bitmap) is the ideal starting point because it stores pixel data with almost no compression and minimal abstraction. Unlike PNG or JPEG, when you open a BMP file in a hex editor, you can directly trace how every byte maps to a pixel on screen. There is no DEFLATE decompression, no DCT transform, no Huffman decoding — just headers followed by the raw pixel array. I recommend BMP as the starting format for anyone learning binary image structures. The file structure consists of exactly three parts: the BITMAPFILEHEADER (14 bytes), the DIB header (typically 40 bytes), and the pixel data.

Fun fact: The "BM" magic number in the first 2 bytes of every BMP file stands for "BitMap." In binary, 0x42 0x4D is literally the ASCII characters "B" and "M".

BITMAPFILEHEADER: The 14-Byte File Header

The BITMAPFILEHEADER is exactly 14 bytes long. All multibyte fields are stored in little-endian order (least significant byte first). Here is the binary layout I always reference when reading BMP files:

  • Bytes 0-1: Signature 42 4D ("BM" in ASCII). In binary: 01000010 01001101.
  • Bytes 2-5: File size as a 32-bit integer. For a 100 × 100 24-bit image, this would be approximately 70,054 bytes (14 + 40 + 100 × 100 × 3 + padding).
  • Bytes 6-9: Reserved (always zero in standard BMP files).
  • Bytes 10-13: Pixel data offset. The byte position where pixel data starts. For a standard 24-bit BMP with BITMAPINFOHEADER, this is typically 54 (14 + 40). For an 8-bit BMP with a palette, this is 1078 (14 + 40 + 1024).

The offset field is critical. I once spent an hour debugging a BMP reader because I assumed pixel data always started at byte 54, but the file used a 108-byte BITMAPV4HEADER instead of the standard 40-byte one. Always read the offset field rather than assuming.

BITMAPINFOHEADER: The 40-Byte DIB Header

The DIB header contains all essential image metadata. The v3 BITMAPINFOHEADER is 40 bytes and includes these fields in order:

  • Bytes 14-17: Header size (always 40 for this version)
  • Bytes 18-21: Image width (32-bit signed integer, in pixels)
  • Bytes 22-25: Image height (32-bit signed integer; positive = bottom-up origin, negative = top-down)
  • Bytes 26-27: Color planes (16-bit unsigned integer, always 1)
  • Bytes 28-29: Bits per pixel (16-bit: 1, 4, 8, 16, 24, or 32)
  • Bytes 30-33: Compression method (0 = BI_RGB uncompressed, 1 = BI_RLE8, 3 = BI_BITFIELDS)
  • Bytes 34-37: Image data size (0 is valid for uncompressed RGB)
  • Bytes 38-41: Horizontal resolution (pixels per meter)
  • Bytes 42-45: Vertical resolution (pixels per meter)
  • Bytes 46-49: Colors in palette (0 = use default for bit depth)
  • Bytes 50-53: Important colors (0 = all important)

The bits-per-pixel field determines everything that follows. A value of 24 means direct RGB pixels. A value of 8 means indexed color with a 256-entry palette. The height field sign tells you whether rows are stored bottom-up (positive) or top-down (negative).

Color Palette: 8-Bit and Lower BMP Files

For BMP files with 8 bits per pixel or fewer, a color palette (color table) follows the DIB header. Each palette entry is exactly 4 bytes in BGRX order: 1 byte Blue, 1 byte Green, 1 byte Red, 1 byte reserved (usually zero). For an 8-bit BMP, the palette has 256 entries, taking up 1024 bytes. The palette defines which actual colors the pixel indices refer to.

In binary, the palette is a sequence of 4-byte BGRA-like values. Entry 0 defines color index 0, entry 1 defines color index 1, and so on. If a pixel value is 01101001 (105 in decimal), the display looks up palette entry 105 for the actual color. This indirection means you can change every pixel of a specific color instantly by modifying just one palette entry. I've used this trick to create color-swapped versions of images without touching the pixel data at all.

24-Bit Pixel Data: BGR Order and Row Padding

The pixel array in a 24-bit BMP stores each pixel as 3 bytes in BGR order (Blue first, then Green, then Red). A red pixel in binary is 00000000 00000000 11111111 — B=0, G=0, R=255. Rows are stored bottom-up by default: the first row of pixel data corresponds to the bottom row of the image. Each row must be aligned to a 4-byte boundary. If the raw pixel data for a row is not a multiple of 4 bytes, zero padding bytes are appended.

The padding calculation is: padding = (4 - (width × 3) % 4) % 4. For a 5-pixel-wide row (15 bytes of pixel data), the next multiple of 4 is 16, so 1 padding byte is needed. For a 4-pixel-wide row (12 bytes), no padding is needed. The padding bytes are ignored by display software but must be present for the file to be valid. Skipping this padding is the most common bug in custom BMP readers — I have made this mistake more than once, and it causes every row after the first to be shifted by the missing padding bytes.

Remember: BGR not RGB — the byte order in BMP pixel data is Blue, Green, Red. PNG and JPEG use RGB order. Always check the format specification before interpreting raw pixel data.

8-Bit vs 24-Bit BMP: Binary Size Comparison

The same image saved as 8-bit indexed BMP vs 24-bit BMP shows dramatically different binary sizes. For a 100 × 100 image:

  • 24-bit BMP: 14 (file header) + 40 (DIB header) + 100 × 100 × 3 (pixel data) + padding = approximately 30,054 bytes. Each pixel stores its own color in 3 bytes. Simple but memory-intensive.
  • 8-bit BMP: 14 + 40 + 1024 (256-entry palette) + 100 × 100 × 1 (pixel indices) + padding = approximately 11,078 bytes. Each pixel stores 1 byte as an index into the palette, and the palette defines the actual 256 colors.

The 8-bit version is roughly one-third the size but limited to 256 colors. If the source image has more than 256 unique colors, the conversion software uses color quantization and dithering to map the full range down to 256. This trade-off between color count and file size is the fundamental challenge of indexed color formats like 8-bit BMP and GIF.

BI_BITFIELDS: 16-Bit and 32-Bit BMP Compression

Beyond standard uncompressed BMP, the BI_BITFIELDS compression type (value 3 in the compression field) supports 16-bit and 32-bit pixel formats with custom color channel masks. Instead of fixed BGR byte layout, BI_BITFIELDS uses three 4-byte DWORD masks that define which bits correspond to Red, Green, and Blue. These masks appear immediately after the DIB header, before the pixel data.

A common 16-bit BI_BITFIELDS format uses masks: Red = F800 (bits 15-11), Green = 07E0 (bits 10-5), Blue = 001F (bits 4-0). This is the 5-6-5 High Color format. The binary masks tell the reader which bits to extract for each channel. In binary, the red mask is 1111100000000000, the green mask is 0000011111100000, and the blue mask is 0000000000011111. Each bit set to 1 in the mask belongs to that color channel. I've used BI_BITFIELDS extensively when working with raw framebuffer data from embedded displays and GPU memory dumps.

Reading a Complete BMP File: Binary Walkthrough

Let me walk through reading a tiny 2 × 2 pixel 24-bit BMP file at the binary level:

  • Offset 0x00: 42 4D — "BM" signature
  • Offset 0x02: 46 00 00 00 — file size = 70 bytes (little-endian)
  • Offset 0x06: 00 00 00 00 — reserved
  • Offset 0x0A: 36 00 00 00 — pixel data offset = 54
  • Offset 0x0E: 28 00 00 00 — DIB header size = 40
  • Offset 0x12: 02 00 00 00 — width = 2 pixels
  • Offset 0x16: 02 00 00 00 — height = 2 pixels (bottom-up)
  • Offset 0x1A: 01 00 — planes = 1
  • Offset 0x1C: 18 00 — bits per pixel = 24 (decimal 24 = hex 0x18)
  • Offset 0x1E-0x35: zeros for compression, image size, resolution, colors
  • Offset 0x36: pixel data begins with the bottom row first

Each row has 6 bytes (2 pixels × 3 bytes), which is aligned to 4 bytes, so no padding is needed. The pixel data section is 12 bytes total. This tiny example demonstrates every major BMP concept in just 70 bytes of data.

Try It Yourself

Open the Binary Code Decoder in a new tab and enter some binary patterns to see the results instantly. All conversions happen in your browser — no data is sent to any server.