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