An image that is not a PNG is stored as an
ICONIMAGE, a device independent bitmap without the file header a
BMP has. It holds, in this order, a header, a colour
table, the XOR bitmap, which is the colours, and the AND mask.
0BITMAPINFOHEADERicHeader, with the height doubled40 bytes40Colour tableicColors, for depths up to 8 bits4 bytes a colour…XOR bitmapicXOR, the colours, at the image’s depthrows of the depth…AND maskicAND, 1 bit a pixelrows of 1 bit
BITMAPINFOHEADER
The same structure as a BMP’s DIB header, but a BMP’s rules do not all apply. Microsoft says only the six fields marked used below count, and all the others must be 0.
| Offset | Size | Type | Name | In an icon |
|---|---|---|---|---|
| 0 | 4 | u32 | biSize | Used. The size of this header: 40. |
| 4 | 4 | i32 | biWidth | Used. The picture’s width in pixels. |
| 8 | 4 | i32 | biHeight | Used. The height of the XOR bitmap and the AND mask together: twice the picture’s height, so a 16 × 16 image has 32 here. Divide by two before using it for either bitmap. |
| 12 | 2 | u16 | biPlanes | Used. 1. |
| 14 | 2 | u16 | biBitCount | Used. Bits per pixel of the XOR bitmap: see depths. |
| 16 | 4 | u32 | biCompression | BI_RGB (0). Raymond Chen’s account also allows BI_BITFIELDS (3) for 16-bit and 32-bit images. |
| 20 | 4 | u32 | biSizeImage | Used. The size of the pixel data in bytes. Microsoft does not say whether it includes the AND mask; files give the XOR bitmap alone, both bitmaps together, or 0. |
| 24 | 4 | i32 | biXPelsPerMeter | Must be 0. |
| 28 | 4 | i32 | biYPelsPerMeter | Must be 0. |
| 32 | 4 | u32 | biClrUsed | Must be 0, so the colour table is full size. Many real files set it anyway, and a reader then finds that many colours. |
| 36 | 4 | u32 | biClrImportant | Must be 0. |
Microsoft’s layout declares a BITMAPINFOHEADER, the 40-byte header, and does not
mention any other. Wikipedia says Windows also accepts the 12-byte
BITMAPCOREHEADER and does not accept the 108-byte and 124-byte headers, but marks
that claim as needing a citation, so treat it as unconfirmed. Nothing describes the 52-byte
and 56-byte headers in icons. Compression other than the values above, including
run-length encoding and BI_ALPHABITFIELDS, is not part of the layout
either.
Depths
biBitCount | Colour table | Each pixel in the XOR bitmap | Notes |
|---|---|---|---|
1 | 2 colours | 1 bit, the highest bit is leftmost | A monochrome icon has black first and white second. See the four results. |
4 | 16 colours | 4 bits, the high nibble leftmost | |
8 | 256 colours | 1 byte, an index | |
16 | none | A 16-bit value: 5 bits each of red, green and blue, unless bit masks say otherwise | With BI_BITFIELDS, three masks follow the header. |
24 | none | 3 bytes: blue, green, red | Windows loads it, though Microsoft’s account says it is not supported and should be converted to 32 bits. |
32 | none | 4 bytes: blue, green, red and alpha or an unused byte | See 32-bit alpha. |
One icon file can mix depths and sizes, so each image has its own header.
Colour table
Directly after the header, for depths of 8 bits or fewer: an array of
RGBQUAD entries, one for each colour the depth allows.
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0 | 1 | u8 | rgbBlue | Blue, 0 to 255. |
| 1 | 1 | u8 | rgbGreen | Green, 0 to 255. |
| 2 | 1 | u8 | rgbRed | Red, 0 to 255. |
| 3 | 1 | u8 | rgbReserved | Reserved, 0. |
XOR bitmap
The colours of the picture, one row after another at the image’s depth. Rows are stored bottom row first, and each row is padded to a multiple of four bytes:
row size in bytes = ((width × bits + 31) ÷ 32, rounded down) × 4
For a 16 × 16 image at 4 bits a pixel, each row is 8 bytes, so the XOR bitmap is 128 bytes.
AND mask
Directly after the XOR bitmap: one bit for each pixel of the same width and height, again bottom row first and padded to four bytes a row, whatever the image’s depth. Its bit order is the same as a 1-bit bitmap’s: the highest bit is the leftmost pixel.
For a 16 × 16 image, each row is 4 bytes, so the mask is 64 bytes. With the 40-byte header, 64 bytes of colour table and the 128-byte XOR bitmap above, that image is 296 bytes in all. What each bit does is on the AND and XOR masks page.
Because a bitmap is stored from the bottom row up, the XOR bitmap comes first in the file even though it appears as the bottom half of the double-height picture the header describes.