Parsing Colors in a TGA File

May 02 2010
Having just written a parser for TGA image files, I had the pleasure of learning that the storage method for color data was not clearly defined by the documentation and that the few pieces of example code I could find all contained bugs. In an attempt to rectify the issue, let's walk through the different ways color data can be packed into a TGA file and how to read it back. 

 

References

Because I'm going to focus on the color data specifically and you might be interested in more (e.g. the overall file format itself), here are a few useful links:

TGA File Format - This looks to be the official documentation for TGA files.

Wikipedia - Wikipedia has information about some fo the more common parts of the file.

TGA Format Examples - This text file has example breakdowns of common TGA formats, but be aware that when it says the "Image Descriptor Byte" should be zero, it is wrong. I discuss how the image desciptor is used to orient the image below.

 

Endianness

One important thing to keep in mind is that TGA files are stored with little-endian data. This means that any piece of data spanning multiple bytes (e.g. a 4-byte integer) will be sorted so that bytes representing the lower digits (the little end) will be sorted earlier in memory. For example, the hexadecimal short integer 0xFFAA contains two bytes: 0xFF and 0xAA. The 0xFF bite represents higher digits than the 0xAA byte. Thus on a little-endian system, memory will have 0xAA first and 0xFF second. On a big-endian system, 0xFF will be first and 0xAA will be second.

 

Packed Color Data

Color data in TGA files is stored as a single value. Depending on the color bit depth, the color value bits are split among three or four channels. Colors always store red, green, and blue channels with optional attribute channel. The attribute channel is generally used as an opacity value so the code below will make this assumption. For a more proper handling of the attribute channel, see the bottom of the article. The channels are bit packed into an ARGB (alpha, red, green, blue) format to build the final color.

 

The color precision is defined as a total number of bits used per color. The documentation lists the bits per color for image data as "Pixel Depth" (field 5.5 of the file header). While the number of bits does not need to align with a byte boundary, the color value is always stored as an integral number of bytes. For example, the color value for a 15-bit pixel depth will consist of 2 bytes (16 bits) where the lower 15 bits have useful data and the highest bit should be ignored.

 

The red, green and blue color channels always use an equal number of bits equivalent to one third of the pixel depth rounded down to the nearest integer and never greater than 8 bits. A 16-bit pixel depth uses  five bits per channel (16 / 3 = 5.33 which is rounded down to 5). A 32-bit pixel depth uses eight bits per channel (32 / 3 = 10.66 which is clamped to the maximum of 8).

 

The attribute (or alpha) channel uses however many bits are remaining after the color channel bits are distributed. A 16-bit pixel depth only uses fifteen bits of color data so there is one bit of attribute data. A 32-bit pixel depth only uses twenty four bits of color data so there are eight bits of attribute data. A 15-bit pixel depth uses all fifteen bits for color data so there are no bits of attribute data.

 

While the TGA format supports numerous pixel depth values, only 15, 16, 24, and 32 are commonly used. This means that in practice, you are probably fine just supporting these few values instead of writing a more general color parser. Let's break down the composition of these common pixel depths piece by piece (it was very tempting to write "bit by bit" there). Recall that values are composited in ARGB order, but the memory is stored as little-endian data.

 

Legend:
 x - unsed bit
 A - alpha channel bit
 R - red channel bit
 G - green channel bit
 B - blue channel bit
 
15-bit pixel depth
 color value: xRRRRRGG GGGBBBBB
 byte[0] = GGGBBBBB; 
 byte[1] = xRRRRRGG;
 
16-bit pixel depth
 color value: ARRRRRGG GGGBBBBB
 byte[0] = GGGBBBBB; 
 byte[1] = ARRRRRGG;
 
24-bit pixel depth
 color value: RRRRRRRR GGGGGGGG BBBBBBBB
 byte[0] = BBBBBBBB; 
 byte[1] = GGGGGGGG;
 byte[2] = RRRRRRRR;
 
32-bit pixel depth
 color value: AAAAAAAA RRRRRRRR GGGGGGGG BBBBBBBB
 byte[0] = BBBBBBBB; 
 byte[1] = GGGGGGGG;
 byte[2] = RRRRRRRR;
 byte[3] = AAAAAAAA;

 

 

Unpacking Color Data

Now that we understand how a color is stored in memory, we can write a function to unpack the data into a 32-bit color. Unpacking 24-bit and 32-bit pixel depths is trivial. Unpacking 15-bit and 16-bit pixel depths is a bit more complicated for two reasons: the green channel crosses two bytes, and we are converting a 5-bit color channel to an 8-bit color channel.

 

The naive approach for converting a 5-bit value to an 8-bit value is left shifting by 3 bits, but this will create 32-bit image that is darker than intended. The maximum value that can be stored in five bits is 31. The maximum value that can be stored in eight bits is 255. If we left shift 31 by 3, we end up with 248 (slightly less than 255). Thus a 15-bit white will turn into a 32-bit light gray.

 

Our left shift of 3 is multiplying the 5-bit value by 8, but we really want to multiply by about 8.2258 in order to scale properly. Before you go and start converting to floating point numbers, we can actually still get the proper result using a few more bit operations. In binary, out left shift adds three new zeros to the tail of our 5-bit number (xxxxx becomes xxxxx000). Instead of three zeros, we want digits that will evenly distribute the 5-bit input across the full 8-bit range. Let's look at some example desired conversions:

 

     5-bit              8-bit
decimal, binary -> decimal, binary
0,       00000  -> 0,       00000000
6,       00110  -> 49,      00110001
12,      01100  -> 99,      01100011
19,      10011  -> 156,     10011100
25,      11001  -> 206,     11001110
31,      11111  -> 255,     11111111

 

What has happened is that the least significant (rightmost) three bits of the 8-bit value are set to a 3-bit fraction of far we should be in the 8-bit range. For example, when we are 0% into the range (source value of 0), we left shift in the bits 000 and when we are 100% into the range (source value 31) we left shift in the bits 111. It turns out that our 3-bit percentage between 0 and 31 is equivalent to the most significant (leftmost) three bits of the initial 5-bit value. We can just toss out the lower two bits (right shift by 2) and the remaining three bits is the binary percentage.

 

We can now write a function to convert a packed TGA color to an unpacked 32-bit color.

 

// This structure represents a 32-bit color
struct tColorRGBA
{
    unsigned char r;
    unsigned char g;
    unsigned char b;
    unsigned char a;
};
 
// Given a pointer to packed TGA color data, and the number of bits used
// per color return an unpacked 32-bit color.
tColorRGBA ParseTGAColor(const unsigned char * pColorData, unsigned int bitsPerColor )
{
    tColorRGBA result;
    switch( bitsPerColor )
    {
    case 15:
        // MSB           LSB
        // xRRRRRGG GGGBBBBB 
        result.a = 255;
        result.r = (pColorData[1] >> 2) & 0x1F;
        result.g = ((pColorData[1] << 3) & 0x1C) | ((pColorData[0] >> 5) & 0x07);
        result.b = (pColorData[0] & 0x1F);
 
        // Convert 5-bit channels to 8-bit channels by left shifting by three
        // and repeating the first three bits to cover the range [0,255] with
        // even spacing. For example:
        //   channel input bits:        4 3 2 1 0
        //   channel output bits: 4 3 2 1 0 4 3 2
        result.r = (result.r << 3) | (result.r >> 2);
        result.g = (result.g << 3) | (result.g >> 2);
        result.b = (result.b << 3) | (result.b >> 2);
        break;
 
    case 16:
        // MSB           LSB
        // ARRRRRGG GGGBBBBB
        result.a = 255 * ((pColorData[1] & 0x80) >> 7);
        result.r = (pColorData[1] >> 2) & 0x1F;
        result.g = ((pColorData[1] << 3) & 0x1C) | ((pColorData[0] >> 5) & 0x07);
        result.b = (pColorData[0] & 0x1F);
 
        // Convert 5-bit channels to 8-bit channels by left shifting by three
        // and repeating the first three bits to cover the range [0,255] with
        // even spacing. For example:
        //   channel input bits:        4 3 2 1 0
        //   channel output bits: 4 3 2 1 0 4 3 2
        result.r = (result.r << 3) | (result.r >> 2);
        result.g = (result.g << 3) | (result.g >> 2);
        result.b = (result.b << 3) | (result.b >> 2);
        break;
 
    case 24:
        // MSB                    LSB
        // RRRRRRRR GGGGGGGG BBBBBBBB 
        result.a = 255;
        result.r = pColorData[2];
        result.g = pColorData[1];
        result.b = pColorData[0];
        break;
 
    case 32:
        // MSB                             LSB
        // AAAAAAAA RRRRRRRR GGGGGGGG BBBBBBBB 
        result.a = pColorData[3];
        result.r = pColorData[2];
        result.g = pColorData[1];
        result.b = pColorData[0];
        break;
    }
 
    return result;
}

 

 Parsing the Image Data

The image data is stored as a large array of color values. Colors are stored row by row, but the order of the rows and the order of the colors within the row are determined by the "image descriptor" (field 5.6 of the file header in the file format documentation). The image descriptor is a single byte value with the bits are numbered as follows:

 

most significant bit       least significant bit
                7 6 5 4 3 2 1 0  

 

If bit 5 of the image descriptor is zero, the rows of the image start with the bottom row and end with the top row. Otherwise the rows start with the top row and end with the bottom row.

 

If bit 4 of the image descriptor is zero, the colors within a row start with the left side and end with the right side. Otherwise the colors within a row start with the right side and end with the left side.

 

Attribute Types

If you are writing something along the lines of a commercial image processing tool (in comparison to a game engine) you may need more robust support for less common TGA files. The attribute bits don't have to be used for opacity data. If they are used for opacity data, they can even be pre-multiplied into the other channels. This can be checked in the Attribut Type field of the the TGA File Format's extension area (byte offset 494).