BmpToRaw: Step-by-Step Guide for Lossless BMP → RAW Conversion

BmpToRaw: Step-by-Step Guide for Lossless BMP → RAW ConversionConverting BMP (Bitmap) images to RAW pixel data is a common task in embedded systems, graphics pipelines, and custom image-processing workflows. This guide walks through the entire process — from understanding BMP internals to producing a lossless RAW file suitable for low-level hardware, custom renderers, or further processing. It includes examples, code, and troubleshooting tips.


What “BMP” and “RAW” mean here

  • BMP: a widely used raster image format (Microsoft Windows Bitmap) that stores pixel data with optional color tables and padding, plus header metadata describing dimensions, bit depth, and compression.
  • RAW: in this guide, a simple uncompressed stream of pixel values with no header — just pixel samples in a specified order (for example, RGB24 as R G B bytes per pixel, or GRAY8 as one byte per pixel). This differs from camera raw formats (which often contain sensor metadata and specialized encodings).

Why convert BMP to RAW?

  • Direct feeding into embedded displays or GPUs that expect raw pixel streams.
  • Faster loading and lower parsing overhead for performance-critical systems.
  • Custom formats for machine learning preprocessing or computer vision pipelines.
  • Avoiding image decoding libraries in constrained environments.

Overview of the conversion steps

  1. Read and parse the BMP header(s) to get image width, height, bit depth, compression, palette info, and pixel offset.
  2. Validate that BMP is uncompressed (BI_RGB) or handle supported compressions (e.g., RLE for ⁄8 bpp).
  3. Read pixel data, accounting for row padding (BMP rows are aligned to 4-byte boundaries).
  4. If BMP uses a palette (indexed color), expand indices to RGB values.
  5. Convert pixel order and channel layout to the desired RAW format (e.g., BGR → RGB, or interleaved → planar).
  6. Optionally flip vertical orientation (BMP stores pixels bottom-up by default).
  7. Write out raw bytes with no header.

BMP internals: headers and pixel layout

BMP files start with a 14-byte BITMAPFILEHEADER, immediately followed by a DIB header (commonly 40 bytes — BITMAPINFOHEADER). Key fields you’ll need:

  • bfOffBits (offset to pixel array)
  • biWidth, biHeight
  • biBitCount (1, 4, 8, 16, 24, 32)
  • biCompression (0 = BI_RGB uncompressed)
  • biSizeImage (size of raw bitmap data; may be 0 for BI_RGB)

Pixel layout notes:

  • For 24-bit BMP: each pixel is stored as B, G, R bytes. Rows are padded to multiples of 4 bytes.
  • For 32-bit BMP: usually B, G, R, A or B, G, R, X (X unused).
  • For 8-bit and lower: pixel values are indices into a color table (palette) of RGBQUAD entries.
  • BMP height can be negative: negative means top-down row order.

Example conversion goals

We’ll show examples for converting:

  • 24-bit BMP → RGB24 RAW (R G B per pixel)
  • 24-bit BMP → GRAY8 RAW (luminance per pixel)
  • 8-bit paletted BMP → RGB24 RAW

All examples use C for clarity and portability; similar logic applies in Python, Rust, or other languages.


Example: 24-bit BMP → RGB24 RAW ©

Below is a minimal, robust C example that:

  • Reads headers
  • Handles bottom-up or top-down orientation
  • Removes row padding
  • Converts BGR → RGB
  • Writes RAW bytes (R G B per pixel)
#include <stdio.h> #include <stdlib.h> #include <stdint.h> #pragma pack(push,1) typedef struct {     uint16_t bfType;     uint32_t bfSize;     uint16_t bfReserved1, bfReserved2;     uint32_t bfOffBits; } BITMAPFILEHEADER; typedef struct {     uint32_t biSize;     int32_t  biWidth;     int32_t  biHeight;     uint16_t biPlanes;     uint16_t biBitCount;     uint32_t biCompression;     uint32_t biSizeImage;     int32_t  biXPelsPerMeter;     int32_t  biYPelsPerMeter;     uint32_t biClrUsed;     uint32_t biClrImportant; } BITMAPINFOHEADER; #pragma pack(pop) int main(int argc, char **argv){     if(argc!=3){ fprintf(stderr,"Usage: %s input.bmp output.raw ",argv[0]); return 1; }     FILE *f = fopen(argv[1],"rb"); if(!f){ perror("open input"); return 1; }     BITMAPFILEHEADER fh; if(fread(&fh,sizeof(fh),1,f)!=1){ perror("read fh"); return 1; }     if(fh.bfType!=0x4D42){ fprintf(stderr,"Not BMP "); return 1; }     BITMAPINFOHEADER ih; if(fread(&ih,sizeof(ih),1,f)!=1){ perror("read ih"); return 1; }     if(ih.biBitCount!=24 || ih.biCompression!=0){ fprintf(stderr,"Only uncompressed 24-bit BMP supported "); return 1; }     int w = ih.biWidth; int h = abs(ih.biHeight);     int row_in = ((w*3 + 3) / 4) * 4; // padded row size     uint8_t *row = malloc(row_in);     FILE *out = fopen(argv[2],"wb"); if(!out){ perror("open out"); return 1; }     int bottom_up = (ih.biHeight > 0);     for(int y = 0; y < h; y++){         int read_row = bottom_up ? (h - 1 - y) : y;         if(fseek(f, fh.bfOffBits + (long)read_row * row_in, SEEK_SET) != 0){ perror("seek"); return 1; }         if(fread(row, 1, row_in, f) != (size_t)row_in){ perror("read row"); return 1; }         for(int x = 0; x < w; x++){             uint8_t b = row[x*3 + 0];             uint8_t g = row[x*3 + 1];             uint8_t r = row[x*3 + 2];             fputc(r, out); fputc(g, out); fputc(b, out);         }     }     free(row); fclose(f); fclose(out);     return 0; } 

Notes:

  • This example is intentionally minimal; add error checks and handle large files carefully.
  • For performance, use larger buffered reads/writes instead of per-byte fputc.

Example: 24-bit BMP → GRAY8 RAW ©

Convert RGB to luminance using standard Rec. 601 coefficients:

L = 0.299*R + 0.587*G + 0.114*B

/* Same headers as previous example */ ... for(each pixel){     uint8_t r,g,b;     // read b,g,r     uint8_t gray = (uint8_t)((299*r + 587*g + 114*b + 500) / 1000); // integer approx     fputc(gray, out); } ... 

Example: 8-bit paletted BMP → RGB24 RAW ©

If biBitCount <= 8, read the palette immediately after headers (biClrUsed entries or default 2^n). Each palette entry is an RGBQUAD (B,G,R,A).

Procedure:

  • Read palette (palette_size * 4 bytes).
  • For each pixel index, look up RGB in palette and write R,G,B.

Handling 16-bit and 32-bit BMPs

  • 16-bit: pixel formats vary (5-5-5, 5-6-5). Check biCompression and masks in BITFIELDS.
  • 32-bit: usually straightforward B G R A per pixel; decide whether to keep alpha or discard.

Dealing with compressed BMPs (RLE)

BMP supports RLE8 and RLE4 for 8- and 4-bit images. Implementing RLE decompression is more involved — if you expect such images, use a library (stb_image, libgd, FreeImage) or implement the RLE decoding per the BMP spec.


Common pitfalls and troubleshooting

  • Row padding: forgetting 4-byte alignment causes shifted pixels.
  • Top-down vs bottom-up: image appears upside down if height sign ignored.
  • Palettes: 8-bit BMPs need palette expansion.
  • Endianness: BMP is little-endian; on big-endian systems, swap fields accordingly.
  • biSizeImage may be zero for BI_RGB; compute from dimensions.

Performance tips

  • Read/write in large blocks (e.g., fread/fwrite buffers, mmap when available).
  • If converting many images, reuse buffers and avoid per-pixel function overhead.
  • Use SIMD (SSE/NEON) for color conversion at scale.

Using existing tools and libraries

  • command-line: ImageMagick (convert input.bmp -depth 8 rgb:output.raw) or Netpbm (bmptoppm then pnmtoimage).
  • libraries: stb_image/stb_image_write, libpng + libjpeg for other formats, FreeImage for many bitmap variants.

Example ImageMagick command for RGB24 raw: convert input.bmp -depth 8 rgb:output.raw


Example: Python quick script (Pillow)

from PIL import Image im = Image.open("input.bmp") im = im.convert("RGB")  # ensures RGB order w,h = im.size with open("output.raw","wb") as f:     f.write(im.tobytes())  # writes R,G,B per pixel in row-major top-down order 

Note: Pillow returns top-down order; if you need bottom-up, flip vertically first: im = im.transpose(Image.FLIP_TOP_BOTTOM)


Choosing RAW ordering and documenting it

Decide and document:

  • Channel order (RGB or BGR)
  • Channel size (8-bit, 16-bit)
  • Pixel packing (interleaved RGBRGB… vs planar RRR…GGG…BBB…)
  • Row order (top-down or bottom-up) Include a small README or header when distributing raw files.

Summary checklist before writing RAW

  • [ ] Parsed headers correctly (width, height, bit depth, compression)
  • [ ] Handled palette if present
  • [ ] Resolved padding and orientation
  • [ ] Converted channel order and bit depth correctly
  • [ ] Verified output size: width * height * channels (matches expectation)
  • [ ] Tested by re-importing RAW into an image tool or display

Converting BMP to RAW is straightforward once you account for padding, palettes, and orientation. Use the provided code snippets as a starting point and adapt them to your target RAW layout and performance needs.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *