― Anne Revere
The code below is part of a C graphics processing library, which parses data in the venerable bitmap (BMP) file format. A bitmap file consists of a two parts: a header and the pixel data block. More specifically, a bitmap file is laid-out like this:
Offset | Size | Content |
---|---|---|
0 | 1 | Character ‘B’ |
1 | 1 | Character ‘M’ |
2 | 4 | Size of the bitmap file |
6 | 4 | Reserved |
10 | 4 | Offset to the first byte of the pixel data (ofs) |
14 | n | Info block |
ofs | m | Pixel data |
All multi-byte integer values (like the bitmap file size and the offset to the pixel data) are stored in little-endian format.
The function ‘bmp_pixel_data’ takes a pointer to a bitmap file data and returns a pointer to the bitmap’s pixel data area within the bitmap. The size of the pixel data area is returned via the ‘size’ out parameter. In case the provided bitmap file data is malformed, a NULL pointer is returned and the ‘size’ out parameter is set to zero.
As always, the code compiles cleanly without warnings (at ‘-W -Wall’), but when the function ‘bmp_pixel_data’ was put to use, it failed miserably. Where did the programmer goof?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
/* First magic byte. */ #define BMP_MAGIC_BYTE1 'B' /* Second magic byte. */ #define BMP_MAGIC_BYTE2 'M' /* Offset of first magic byte. */ #define BMP_MAGIC_BYTE1_OFS 0 /* Offset of second magic byte. */ #define BMP_MAGIC_BYTE2_OFS (BMP_MAGIC_BYTE1_OFS + sizeof(BMP_MAGIC_BYTE1)) /* Offset to 4-byte bitmap file size, little-endian. */ #define BMP_FILE_SIZE_OFS (BMP_MAGIC_BYTE2_OFS + sizeof(BMP_MAGIC_BYTE2)) /* Offset to 4-byte pixel data offset, little-endian. */ #define BMP_OFFSET_OFS (BMP_FILE_SIZE_OFS + sizeof(uint32_t) + sizeof(uint32_t)) /* Offset to bitmap info block. */ #define BMP_OFFSET_INFO_BLOCK (BMP_OFFSET_OFS + sizeof(uint32_t)) static inline uint32_t uint32_from_little_endian(const uint8_t* data) { assert(data != NULL); return ((data[3] << 24U) + (data[2] << 16U) + (data[1] << 8U) + data[0]); } const uint8_t* bmp_pixel_data(const uint8_t* bitmap, uint32_t* size) { assert(bitmap != NULL); assert(size != NULL); const uint8_t* p = NULL; if (bitmap[BMP_MAGIC_BYTE1_OFS] == BMP_MAGIC_BYTE1 && bitmap[BMP_MAGIC_BYTE2_OFS] == BMP_MAGIC_BYTE2) { uint32_t file_size = uint32_from_little_endian(&bitmap[BMP_FILE_SIZE_OFS]); uint32_t offset = uint32_from_little_endian(&bitmap[BMP_OFFSET_OFS]); if (offset <= file_size) { *size = file_size - offset; p = &bitmap[BMP_MAGIC_BYTE1_OFS + offset]; } } if (p == NULL) { *size = 0; } return p; } |