Resource-constrained embedded systems, microcontrollers (STM32, ESP32), and Linux framebuffer displays often lack the memory footprint required to link heavy image decoding libraries like libpng or libjpeg. Instead, these systems display graphics by reading uncompressed raw 16-bit RGB565 binary data directly into frame buffers.
In this article, we construct a standalone C program in Linux that parses standard 24-bit Windows Bitmap (.bmp) file headers, strips row padding, converts BGR pixel byte streams into 16-bit RGB565 word arrays, and outputs a raw binary file ready for direct DMA transmission to TFT LCD displays.
Quick Compilation and Execution Sequence
Compile the C converter and transform a 24-bit BMP image into raw RGB565 binary format:
# 1. Compile the C conversion utility
gcc -O2 bmp2raw.c -o bmp2raw
# 2. Run converter on input BMP file
./bmp2raw input_image.bmp output_16bit.raw
# 3. Inspect converted raw 16-bit binary output
hexdump -C output_16bit.raw | head -n 16Anatomy of the Windows BMP File Header
A standard Windows BMP file begins with a 14-byte File Header (BITMAPFILEHEADER) followed by a 40-byte Info Header (BITMAPINFOHEADER):
Magic Identifier (`bfType`): 2 bytes containing ASCII string
"BM"(0x4D42).Data Offset (`bfOffBits`): 4 bytes indicating byte offset where actual pixel arrays begin.
Dimensions (`biWidth`, `biHeight`): 4 bytes each. Positive height indicates bottom-up pixel ordering.
Bits Per Pixel (`biBitCount`): 2 bytes (24 bits = 8 bits each for Red, Green, Blue).
Row Padding Alignment: BMP scanlines are padded with zero bytes to align to 4-byte boundaries:
row_stride = (width * 3 + 3) & ~3.
Complete C Converter Source Code (bmp2raw.c)
This C implementation parses BMP headers, handles 4-byte row padding, performs RGB565 bit-shifting, and reverses scanlines to correct bottom-up orientation:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#pragma pack(push, 1)
typedef struct {
uint16_t bfType; // File type magic ("BM")
uint32_t bfSize; // File size in bytes
uint16_t bfReserved1; // Reserved
uint16_t bfReserved2; // Reserved
uint32_t bfOffBits; // Offset to pixel data
} BITMAPFILEHEADER;
typedef struct {
uint32_t biSize; // Header size (40 bytes)
int32_t biWidth; // Image width in pixels
int32_t biHeight; // Image height in pixels
uint16_t biPlanes; // Color planes (must be 1)
uint16_t biBitCount; // Color depth (24 bit)
uint32_t biCompression; // Compression method
uint32_t biSizeImage; // Image size
int32_t biXPelsPerMeter; // Horizontal resolution
int32_t biYPelsPerMeter; // Vertical resolution
uint32_t biClrUsed; // Colors used
uint32_t biClrImportant; // Important colors
} BITMAPINFOHEADER;
#pragma pack(pop)
// Convert 8-bit BGR to 16-bit RGB565
static inline uint16_t bgr888_to_rgb565(uint8_t r, uint8_t g, uint8_t b) {
return (uint16_t)(((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3));
}
int main(int argc, char *argv[]) {
if (argc < 3) {
printf("Usage: %s <input.bmp> <output.raw>
", argv[0]);
return 1;
}
FILE *fin = fopen(argv[1], "rb");
if (!fin) {
perror("Failed to open input BMP");
return 1;
}
BITMAPFILEHEADER fileHeader;
BITMAPINFOHEADER infoHeader;
fread(&fileHeader, sizeof(BITMAPFILEHEADER), 1, fin);
fread(&infoHeader, sizeof(BITMAPINFOHEADER), 1, fin);
if (fileHeader.bfType != 0x4D42 || infoHeader.biBitCount != 24) {
fprintf(stderr, "Error: Only uncompressed 24-bit BMP images supported.
");
fclose(fin);
return 1;
}
int width = infoHeader.biWidth;
int height = infoHeader.biHeight;
int is_bottom_up = (height > 0);
if (height < 0) height = -height; // Top-down BMP
int row_padding = (4 - ((width * 3) % 4)) % 4;
int raw_row_bytes = width * 3 + row_padding;
uint8_t *bmp_buffer = (uint8_t *)malloc(raw_row_bytes * height);
uint16_t *raw565_buffer = (uint16_t *)malloc(width * height * sizeof(uint16_t));
fseek(fin, fileHeader.bfOffBits, SEEK_SET);
fread(bmp_buffer, raw_row_bytes * height, 1, fin);
fclose(fin);
// Convert pixel scanlines
for (int y = 0; y < height; y++) {
int src_y = is_bottom_up ? (height - 1 - y) : y;
uint8_t *row_src = bmp_buffer + (src_y * raw_row_bytes);
uint16_t *row_dst = raw565_buffer + (y * width);
for (int x = 0; x < width; x++) {
uint8_t b = row_src[x * 3 + 0];
uint8_t g = row_src[x * 3 + 1];
uint8_t r = row_src[x * 3 + 2];
row_dst[x] = bgr888_to_rgb565(r, g, b);
}
}
FILE *fout = fopen(argv[2], "wb");
fwrite(raw565_buffer, sizeof(uint16_t), width * height, fout);
fclose(fout);
free(bmp_buffer);
free(raw565_buffer);
printf("Successfully converted %dx%d BMP to raw 16-bit RGB565!
", width, height);
return 0;
}RGB565 Bit-Shifting Breakdown
Red Component (5 bits): Take top 5 bits of Red (
r & 0xF8) and shift left by 8 bits (<< 8).Green Component (6 bits): Take top 6 bits of Green (
g & 0xFC) and shift left by 3 bits (<< 3).Blue Component (5 bits): Take top 5 bits of Blue (
b & 0xF8) and shift right by 3 bits (>> 3).Bitwise OR: Combine all components into a single
uint16_tword:(R << 8) | (G << 3) | (B >> 3).
Comments and corrections