The BMP File Format, Part 2
Reading and interpreting the bits
David Charlap
David is a software engineer with Visix Software, makers of the Galaxy Application Framework, a toolkit for cross-platform, object-oriented, distributed software development. He can be contacted via the Internet at [email protected].
The bitmap (BMP) file format is the standard way of storing bitmap images in Windows applications. In last month's installment of this two-part article, I presented four portable data structures that describe all versions of bitmap files on all platforms, including non-Windows environments. This month, I'll explain how to use those structures to read and interpret bits, then examine how the structures fit together.
Reading the Bits
Once the headers BITMAPFILEHEADER, BITMAPARRAYHEADER, BITMAPHEADER, and RGB are read, the only thing left to do is read the pixel data. The offsetToBits field in the BITMAPFILEHEADER structure points to the start of this data. The interpretation of the bits is a function of the image's bit depth and the compressionScheme field of the BITMAPHEADER structure.
For all images with bit depths less than 24 that do not use BITFIELDS encoding, the pixel values are array offsets into the image's color table. For images with bit depths of 24 or those that do use BITFIELDS encoding the pixel values are decoded directly into the color's red, green, and blue values.
Interpreting the Bits: No Compression
Uncompressed bit data is easy to interpret: It is an array of rows of pixels. Each row ends with 0--3 extra bytes, so that each row contains a multiple of four bytes. Each row is a packed array of pixel values. Each pixel's bit width is the image's bit depth.
For a bit depth of 1, each byte represents eight pixels; the most significant bits within a byte are the left-most pixels. For a bit depth of 4, each byte represents two pixels, the most significant four bits representing the left-side pixel, and the least significant four bits, the right-side pixel. For a bit depth of 8, each byte represents one pixel.
For a bit depth of 16, each pixel is represented by two bytes. The bytes must be read as a Big-endian word; that is, the first byte read is the most significant. While this seems backwards (all other structures in BMP files are Little-endian), it makes sense in this context as a logical side effect of a packed array with 16-bit elements. It should be noted that a 16-bit color-table-based bitmap is nonstandard--Windows 16-bit bitmaps must use the BITFIELDS encoding, and OS/2 does not have a standard 16-bit-deep bitmap format.
For a bit depth of 24, each pixel is represented by three bytes, which are interpreted as an RGB value. The first byte is the blue component; the second, green; and the third, red.
Interpreting the Bits: RLE Compression
There are two main forms of compression that bitmaps may use: run-length encoding (RLE) and modified Huffman encoding. Unfortunately, IBM chose not to document the modified Huffman encoding scheme in the OS/2 2.1 toolkit. However, compressed bitmaps are very rare, and most are RLE encoded, since RLE is well documented and Windows does not support Huffman-encoded bitmaps.
I have yet to come across a compressed bitmap in all the time I've been writing Windows and OS/2 programs. This leads me to believe that finding valid test cases for bitmap-decompressing code may be very difficult; therefore, the sample programs will not read compressed bitmaps. The description of RLE that follows is based entirely on the Windows SDK and OS/2 toolkit documentation.
RLE compression comes in three varieties: 4-, 8-, and 24-bit. Windows supports the 4- and 8-bit varieties. OS/2 supports all three. The three varieties are encoded with only slight differences.
RLE-encoded bitmaps are stored as a series of records, of which there are five kinds: RLE data, unencoded data, delta records, end-of-line records, and end-of-RLE records.
An RLE data record consists of a count of pixels followed by a pixel value. The value is repeated for the entire count of pixels. The first byte of an RLE data record is the number of pixels into which the record decodes. This value must be greater than 0. The next byte (or next three bytes for 24-bit RLE) is the value to be repeated; see Table 6. [Editor's Note: Tables 1 through 5 were presented last month.]
An unencoded data record consists of a flag value, a count, and then a string of data. The first byte is always 0, which differentiates it from an RLE data record. The second byte is a count (of bytes for 24-bit RLE, of pixels for 8-bit and 4-bit RLE). The rest of the record is pixel data to be inserted into the bitmap without processing; see Table 7.
A delta record consists of two flag values and two delta values. The first byte is always 0, and the second is always 2. The third and fourth bytes are unsigned byte values representing horizontal and vertical deltas, respectively. A delta record indicates that the next current pixel offset should be moved by the deltas indicated before decoding the next record.
An end-of-line record consists of two flag values. The first and second bytes are both 0. The end-of-line record indicates that the current line is complete and the next record will begin at the start of the next row.
An end-of-RLE record also consists of two flag values, the first of which is always 0 and the second, always 1. This record indicates the end of all RLE data.
Table 8 shows the decoding of the 8-bit RLE data stream 03 04 05 06 00 03 45 56 67 00 02 78 00 02 05 01 02 78 00 00 09 1E 00 01 (hexadecimal values). Table 9 shows the decoding of the 4-bit RLE data stream 03 04 05 06 00 06 45 56 67 00 04 78 00 02 05 01 04 78 00 00 09 1E 00 01 (hexadecimal values).
Interpreting the Bits: BITFIELDS Encoding
In addition to RLE and modified Huffman encoding, there is one more possible value for the compressionScheme field of a BITMAPHEADER: COMPRESSION_BITFIELDS. In reality, BITFIELDS is not a compression scheme, but an encoding scheme, since data is not compressed. BITFIELDS encoding is only used for images whose bit depth is 16 or 32. Only Windows NT supports BITFIELDS encoding--Windows 3.x and OS/2 do not.
In BITFIELDS encoding, each pixel is represented by 16 or 32 bits, depending on the image's bit depth. Each pixel represents a specific RGB value. The three 32-bit integers immediately following the BITMAPHEADER structure (where a color table normally resides) are used to describe how a pixel's value decodes into red, green, and blue values. The integers are masks applied to a pixel value to isolate its components.
The best way to explain this is with an example. A 32-bit image might be encoded using ten bits of red, ten bits of green, and ten bits of blue per pixel, with two unused bits; see Figure 5. [Editor's Note: Figures 1 through 4 appeared in last month's installment.] Table 10 shows the three integers in the color table for bits encoded this way. An example of a 16-bit BITFIELDS encoding might be a 5-6-5 encoding (commonly used on 16-bit display adapters) like that in Figure 6. Table 11 shows the three integers in the color table for bits encoded this way.
How All These Structures Fit Together
Now I'll examine how the structures combine together to form a bitmap file. The functions readSingleImageBMP, readSingleImageICOPTR, readSingleImageColorICOPTR, and readMultipleImage in readbmp.h and readbmp.c (available electronically; see "Availability," page 3) demonstrate how to use the structure-reading functions to decode a bitmap file.
The simplest structure is the single-image BMP file. All BMP files created on Windows systems are of this kind; likewise, many OS/2 BMP files. The structure of a single-image BMP file is:
BITMAPFILEHEADER BITMAPHEADER color table ... bits
The file begins with a BITMAPFILEHEADER structure whose type field contains TYPE_BMP. This is immediately followed by a BITMAPHEADER structure and then a color table. Elsewhere in the file is a block of data that contains the image's bits. A pointer to this location is stored in the BITMAPFILEHEADER structure.
Slightly more complex is the OS/2 monochrome icon/pointer format. Here, too, a single BITMAPFILEHEADER is followed by a BITMAPHEADER, a color table, and a block of bit data. The type field of the BITMAPFILEHEADER contains either TYPE_ICO or TYPE_PTR, indicating either an icon or pointer type image. The structure of the file is the same, but the bits are interpreted differently. In such an image, the bit depth is always 1 (monochrome) and the color table is ignored. Additionally, the height of the image is double the height that will be displayed. Decoding the bits will result in an image whose top half is an XOR mask and whose bottom half is an AND mask. To display the icon/pointer, the bottom half is combined with screen pixels using the AND operator. After applying the AND mask, the top half of the image is combined with screen pixels using the XOR operator. These two masks allow four "colors" to be applied--background, foreground, transparent, and inverse; see Table 12.
The OS/2 color icon/pointer format is a bit more complicated. A color icon is actually the composite of two images: a monochrome icon (containing the mask data) and a bitmap (containing the color data). Color icons and color pointers have the structure shown in Figure 7. For a color icon, both BITMAPFILEHEADER structures have TYPE_ICO_COLOR in the type field. For a color pointer, both BITMAPFILEHEADER structures have TYPE_PTR_COLOR in the type field.
Reading a color icon is rather simple once code for reading bitmaps and monochrome icons/pointers is in place. The first image is read using the same code used for a monochrome icon/pointer. The second image is read using the same code used for a bitmap. This results in two masks and a color bitmap, which are combined with screen pixels; see Table 13.
Once code is in place to read all three types of images, reading an array of images is simple. When the file begins with a BITMAPARRAYHEADER (indicated by a type field of TYPE_ARRAY), you simply traverse the linked list, reading each image using the existing code on each one.
The structure of a multiple-image bitmap file is shown in Figure 8. Each BITMAPARRAYHEADER in the list contains a pointer to the next BITMAPARRAYHEADER, establishing the list. Immediately following each BITMAPARRAYHEADER is the start of a bitmap image (bitmap, icon, or pointer), which is read exactly as it would be read in a single-image file.
Putting it All Together
Once routines are in place to read all four types of files (bitmap, icon/pointer, color icon/pointer, and array), writing a program to read any arbitrary bitmap file is easy. Test.c (Listing Three) shows one way to do this. [Editor's Note: Listings One and Two appeared in last month's installment.]
Test.c opens a file and reads the first two bytes to determine the type of bitmap file being read. The appropriate image-reading function is then called, and the image(s) in the file are read in. The test program then dumps information about the image to an output file; an actual application would probably do something else.
Conclusion
The bitmap file format is large and complex, with many variations. OS/2 and Windows document only their own variants, and almost no documentation is available for other systems (such as workstations). With the information presented here, however, you should have no problem reading any bitmap file you come across--no matter what platform you're developing for.
The sample code provided returns data in a format that is easy for this particular test program to use, but modifying the sample code to produce a different format would not be difficult.
References
Microsoft Corp. Microsoft Win32 Programmer's Reference, vol. 5. Redmond, WA: Microsoft Press, 1993.
Microsoft Corp. Microsoft Windows Programmer's Reference, version 3, vol. 2. Redmond, WA: Microsoft Press, 1990.
IBM Corp. OS/2 2.0 Programmer's Toolkit: Presentation Manager Reference (online manual).
Table 6: RLE data-record encoding.
1st byte Count of pixels. 2nd--4th bytes(24-bit RLE only) RGB value to be repeated. 2nd byte(8-bit RLE only) Color index to be repeated. 2nd byte(4-bit RLE only) Two color indexes to be alternated for entire count of pixels.
Table 7: Unencoded data record.
1st byte Must be zero. 2nd byte (24-bit) Indicates length (in bytes) of the rest of the record; must be a multiple of 3. 2nd byte (8-bit) Indicates length (in bytes) of the rest of the record; must be greater than 2. 2nd byte (4-bit) Indicates length (in pixels) of the rest of the record; must be greater than 2. Remaining bytes (24-bit) Every three bytes is another pixel's RGB value; if the total count of pixels is odd, an extra byte is appended for an even length overall. Remaining bytes (8-bit) Every byte is another pixel's color index; if the total count of pixels is odd, an extra byte is appended for an even length overall. Remaining bytes (4-bit) Every byte represents two pixels' color indexes, the most-significant four bits being the first pixel's value and the least significant four bits, the next pixel's value. If the count of pixels is odd, the last four bits will contain 0s; if the count of bytes is odd, an extra byte is appended for an even length overall.
Table 8: Example of 8-bit RLE encoding.
Bytes Meaning Bytes output 03 04 Three bytes of value 04. 04 04 04 05 06 Five bytes of value 06. 06 06 06 06 06 00 03 45 56 67 00 Three bytes of unencoded data. 45 56 67 02 78 Two bytes of value 78. 78 78 00 02 05 01 Delta record; move cursor forward none five pixels and up one row. 02 78 Two bytes of value 78. 78 78 00 00 End of row; next record begins on none the next line, at column 0. 09 1E Nine bytes of value 1E. 1E 1E 1E 1E 1E 1E 1E 1E 1E 00 01 End of RLE data. none
Table 9: Four-bit RLE encoding.
Bytes Meaning Pixel-value Output 03 04 Three pixels alternating 0 and 4. 0 4 0 05 06 Five pixels alternating 0 and 6. 0 6 0 6 0 00 06 45 56 67 00 Six pixels of unencoded data. 4 5 5 6 6 7 04 78 Four pixels alternating 7 and 8. 7 8 7 8 00 02 05 01 Delta record; move cursor forward five none pixels and up one row. 04 78 Four pixels alternating 7 and 8. 7 8 7 8 00 00 End of row; next record begins on the none next line, at column 0. 09 1E Nine pixels alternating 1 and E. 1 E 1 E 1 E 1 E 1 00 01 End of RLE data. none
Table 10: BITFIELDS description of a 10-10-10 encoding.
Position Value (binary) Value (hex) 1 (Red) 11111111110000000000000000000000 FFC00000 2 (Green) 00000000001111111111000000000000 003FF000 3 (Blue) 00000000000000000000111111111100 00000FFC
Table 11: BITFIELDS description of a 5-6-5 encoding.
Position Value Value (binary) (hex) 1 (Red) 1111100000000000 F800 2 (Green) 0000011111100000 07E0 3 (Blue) 0000000000011111 001F
Table 12: Mask operations.
Screen AND XOR Result pixel mask mask x 0 0 0 (background) x 0 1 1 (foreground) x 1 0 x (transparent) x 1 1 ~x (inverse)
Table 13: Color/mask operations.
Screen pixel AND mask XOR mask Color pixel Result x 0 0 c c (color) x 0 1 c c (color) x 1 0 c x (transparent) x 1 1 c ~x (inverse)
Figure 5 Layout of a 32-bit image might be encoded using ten bits of red, ten bits of green, and ten bits of blue per pixel, with two unused bits. Figure 6 16-bit BITFIELDS encoding.
Figure 7: Structure of color icons and color pointers.
BITMAPFILEHEADER (for the monochrome icon part) BITMAPHEADER (for the monochrome icon part) color table (for the monochrome icon part) BITMAPFILEHEADER (for the bitmap part) BITMAPHEADER (for the bitmap part) color table (for the bitmap part) bits (for the monochrome icon part) ... bits (for the bitmap part) ...
Figure 8: Structure of a multiple-image bitmap file.
BITMAPARRAYHEADER (for first image) BITMAPFILEHEADER BITMAPHEADER color table BITMAPFILEHEADER (if this image is a color icon or a color pointer) BITMAPHEADER (if this image is a color icon or a color pointer) color table (if this image is a color icon or a color pointer) ... BITMAPARRAYHEADER (for the second image) BITMAPFILEHEADER (for the second image) BITMAPHEADER (for the second image) color table (for the second image) ... ... bits (for the first image) ... bits (for the second image) ... ...
Listing Three (Listings One and Two appeared last month.)
/* Test program for reading bitmap files. It accepts an input file and an * output file on the command line. It will read and process the input file * and dump an ASCII representation of the contents to the output file. The * dump will consist of the color image and two masks. Missing parts will be * indicated as such (BMP files have no masks and nonochrome ICO/PTR files * have no color data. In the color image, the dump will be a series of RGB * values (in hexadecimal). In the masks, the dump will be represented by "." * symbols representing zeros and "@" symbols representing ones. */ #include <stdio.h> #include <stdlib.h> #include "bmptypes.h" #include "endian.h" #include "readbmp.h" int main (int argc, char *argv[]) { FILE *fp; RGB **argbs; char **xorMasks, **andMasks; UINT32 *heights, *widths, row, col; UINT16 fileType; long filePos; int numImages, i; int rc; if (argc < 3) { printf ("usage: test <infile> <outfile>\n"); return 1; } fp = fopen(argv[1], "rb"); if (fp == NULL) { perror ("Error opening source file"); return 2; } /* Read the first two bytes as little-endian to determine the file type. * Preserve the file position. */ filePos = ftell(fp); rc = readUINT16little(fp, &fileType); if (rc != 0) { perror("Error getting file type"); return 3; } fseek(fp, filePos, SEEK_SET); /* Read the images. */ switch (fileType) { case TYPE_ARRAY: /* If this is an array of images, read them. All the arrays we need * will be allocated by the reader function. */ rc = readMultipleImage(fp, &argbs, &xorMasks, &andMasks, &heights, &widths, &numImages); break; case TYPE_BMP: case TYPE_ICO: case TYPE_ICO_COLOR: case TYPE_PTR: case TYPE_PTR_COLOR: /* If this is a single-image file, we've a little more work. In order * to make the output part of this test program easy to write, we're * going to allocate dummy arrays that represent what readMultipleImage * would have allocated. We'll read the data into those arrays. */ argbs = (RGB **)calloc(1, sizeof(RGB *)); if (argbs == NULL) { rc = 1005; break; } xorMasks = (char **)calloc(1, sizeof(char *)); if (xorMasks == NULL) { free(argbs); rc = 1005; break; } andMasks = (char **)calloc(1, sizeof(char *)); if (andMasks == NULL) { free(argbs); free(xorMasks); rc = 1005; break; } heights = (UINT32 *)calloc(1, sizeof(UINT32)); if (heights == NULL) { free(argbs); free(xorMasks); free(andMasks); rc = 1005; break; } widths = (UINT32 *)calloc(1, sizeof(UINT32)); if (widths == NULL) { free(argbs); free(xorMasks); free(andMasks); free(heights); rc = 1005; break; } numImages = 1; /* Now that we have our arrays allocted, read the image into them. */ switch (fileType) { case TYPE_BMP: rc = readSingleImageBMP(fp, argbs, widths, heights); break; case TYPE_ICO: case TYPE_PTR: rc = readSingleImageICOPTR(fp, xorMasks, andMasks, widths, heights); break; case TYPE_ICO_COLOR: case TYPE_PTR_COLOR: rc = readSingleImageColorICOPTR(fp, argbs, xorMasks, andMasks, widths, heights); break; } break; default: rc = 1000; } /* At this point, everything's been read. Display status messages based * on the return values. */ switch (rc) { case 1000: case 1006: printf ("File is not a valid bitmap file\n"); break; case 1001: printf ("Illegal information in an image\n"); break; case 1002: printf ("Legal information that I can't handle yet in an image\n"); break; case 1003: case 1004: case 1005: printf ("Ran out of memory\n"); break; case 0: printf ("Got good data from file, writing results\n"); break; default: printf ("Error reading file rc=%d\n", rc); perror ("Errno:"); break; } /* If the return value wasn't 0, something went wrong. */ if (rc != 0) { if (rc != 1000 && rc != 1005) { for (i=0; i<numImages; i++) { if (argbs[i] != NULL) free(argbs[i]); if (andMasks[i] != NULL) free(andMasks[i]); if (xorMasks[i] != NULL) free(xorMasks[i]); } free(argbs); free(andMasks); free(xorMasks); free(widths); free(heights); } return rc; } fclose(fp); fp = fopen(argv[2], "wt"); if (fp == NULL) { perror ("Error opening target file"); return 3; } /* Dump the images. */ fprintf (fp, "There are %d images in the file\n", numImages); for (i=0; i<numImages; i++) { /* Loop through all the images that were returned. */ fprintf (fp, "Doing image number %d\n\n", i+1); fprintf (fp, "Image dimensions: (%ld,%ld)\n", widths[i], heights[i]); if (argbs[i] != NULL) { /* If the image has colors, dump them (BMP, color ICO and color * PTR files */ fprintf(fp, "Colors"); for (row = 0; row < heights[i]; row++) { fprintf (fp, "\n\nRow %ld pixels (R,G,B), hex values:\n", row); for (col = 0; col < widths[i]; col++) { fprintf (fp, "(%2.2x,%2.2x,%2.2x)", argbs[i][row * widths[i] + col].red, argbs[i][row * widths[i] + col].green, argbs[i][row * widths[i] + col].blue); } } } else { /* If image has no colors, say so. (monochrome ICO and PTR files) */ fprintf (fp, "No color image\n"); } if (xorMasks[i] != NULL) { /* If the image has an xor mask, dump it. (ICO and PTR files) */ fprintf (fp, "\nXOR mask\n"); for (row = 0; row < heights[i]; row++) { for (col = 0; col < widths[i]; col++) { fprintf (fp, "%c", xorMasks[i][row * widths[i] + col] ? '@' : '.'); } fprintf (fp, "\n"); } } else { /* If the image has no xor mask, say so. (BMP files). */ fprintf (fp, "No xor mask\n"); } if (andMasks[i] != NULL) { /* If the image has an and mask, dump it. (ICO and PTR files) */ fprintf (fp, "\nAND mask\n"); for (row = 0; row < heights[i]; row++) { for (col = 0; col < widths[i]; col++) { fprintf (fp, "%c", andMasks[i][row * widths[i] + col] ? '@' : '.'); } fprintf (fp, "\n"); } } else { /* If the image has noand mask, say so. (BMP files) */ fprintf (fp, "No and mask\n"); } if (i != numImages-1) fprintf (fp, "\n------------------------------------------\n\n"); } fclose(fp); /* Dumping is complete. Free all the arrays and quit */ for (i=0; i<numImages; i++) { if (argbs[i] != NULL) free(argbs[i]); if (andMasks[i] != NULL) free(andMasks[i]); if (xorMasks[i] != NULL) free(xorMasks[i]); } free(argbs); free(andMasks); free(xorMasks); free(widths); free(heights); return 0; } /* Formatting information for emacs in c-mode * Local Variables: * c-indent-level:4 * c-continued-statement-offset:4 * c-brace-offset:-4 * c-brace-imaginary-offset:0 * c-argdecl-indent:4 * c-label-offset:-4 * End: */
Copyright © 1995, Dr. Dobb's Journal