I've literally been trying for three hours to apply a simple grayscale filter to a texture for a 2D engine I'm tinkering with. The problem? Those damn BMP files and the way C handles memory.
What started as a relaxed afternoon of coding and reading documentation has turned into an open war against byte alignment.
The Mud: Understanding the Two-Headed Monster
To manipulate a BMP file raw, without relying on heavy external libraries (because compiling SDL_image or libjpeg on Windows with Dev-C++ and MinGW is sometimes enough to make you want to shoot yourself), you have to understand what's in those first few bytes of the file.
The uncompressed BMP format has two main headers before getting to the meat (the pixels): 1. BITMAPFILEHEADER: Takes up exactly 14 bytes and tells us it's a valid BMP file (must start with 'B' and 'M') and where the image data begins. 2. BITMAPINFOHEADER: 40 bytes containing the width, height, and color depth (the typical 24 bits, one byte for red, green, and blue).
Seems easy, you open a FILE *, do an fread of the structure, and off you go. Well, no.
The Infamous Compiler Padding
This is where C stabs you in the back. C compilers (yes, looking at you, GCC 3.x) align variables in memory so the processor reads them faster, usually in 4-byte blocks. Since the BITMAPFILEHEADER takes up 14 bytes (which isn't a multiple of 4), the compiler silently sneaks two garbage bytes at the end of the structure to square it up to 16. When you do the fread, you read a shifted header, the data gets displaced, the image width gives you an absurd number like 16777216, and everything explodes.
To avoid this disaster and tinker comfortably, you have to force the compiler to pack memory byte by byte. Here is the code that saved my life tonight:
#include <stdio.h>
#include <stdlib.h>
/* Definimos nuestros propios tipos para asegurar el tamaño,
nada de fiarse de si int son 16 o 32 bits en esta máquina */
typedef unsigned short WORD; // 2 bytes
typedef unsigned int DWORD; // 4 bytes
/* Obligamos al compilador a alinear a 1 byte */
#pragma pack(push, 1)
typedef struct {
WORD bfType; // Las mágicas letras 'BM'
DWORD bfSize; // Tamaño total del archivo
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits; // Offset donde empiezan nuestros ansiados píxeles
} BITMAPFILEHEADER;
typedef struct {
DWORD biSize; // Tamaño de esta cabecera (40 bytes)
DWORD biWidth; // Ancho en píxeles
DWORD biHeight; // Alto en píxeles
WORD biPlanes; // Siempre 1
WORD biBitCount; // Bits por píxel (24 para RGB)
DWORD biCompression; // 0 = Sin comprimir
DWORD biSizeImage;
DWORD biXPelsPerMeter;
DWORD biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER;
#pragma pack(pop) /* Restauramos la alineación normal */
int main() {
FILE *f = fopen("textura.bmp", "rb");
if (!f) return 1;
BITMAPFILEHEADER fileHeader;
fread(&fileHeader, sizeof(BITMAPFILEHEADER), 1, f);
if (fileHeader.bfType != 0x4D42) {
printf("¡Esto no es un BMP, melón!\n");
return 1;
}
printf("Offset a los píxeles: %d bytes\n", fileHeader.bfOffBits);
fclose(f);
return 0;
}
That magical #pragma pack(push, 1) (or __attribute__((packed)) if you're playing on purist Linux) was the difference between going to sleep frustrated or being able to see in the console where the heck my pixels begin.
Conclusion
Having to get down in the mud to gut byte arrays with pointers is tough and sometimes maddening, but it gives you absolute control over the machine. You feel like you're truly programming and not just snapping prefabricated pieces together.
The future? I want to believe that in a few years, hardware will be such a beast that we won't have to worry about these clock cycles or absurd memory alignments, and there will be standard libraries in every language that just do a sad loadImage("photo.jpg") and return an array of pixels without you having to study the format specification and fight with the bits by brute force.
But for now, if you want to make fast, graphical things, it's time to get your hands dirty. I'm going to bed; the alarm clock rings in a few hours.