Why

I found myself needing to be able to import Truevision Targa (or TGA) files as bitmaps into my game library. To my surprise a quick Google of the usual places didn't turn up a simple library I could wrap. So armed with a copy of the 2.0 specification linked from the previous Wikipedia page I decided to roll my own importer.

Requirements

I wanted to build something flexible but reasonably fast. After scanning through the specification I decided that the main challenge would be to convert pixels from one format to another. The colour formats in the TGA file appear to be relatively arbitrary and flexibility and I wanted to preserve that flexibility in my solution. After all a one size fits all colour encoding converter and swizzler sounds like a good idea to me. The problem writing generic code for that kind of thing is that you often end up with tight inner loops with lots branching which was something I wanted to avoid.

Colour Channels

So the solution I came up with was to firstly have a simple colour channel description class and also another class that deals with the job of converting a colour from one description to another. This way all of the branching can be done ahead of time - the job of copying and converting the actual colours becomes a trivial operation.

However as well as dealing with bit/byte offsets (and endian swizzles) I also need to deal with the actual conversion of the colour channel from one colour to another. This is simple with integer maths when converting from a higher precision to a lower precision as it's just a simple shift. However when going the other way shifting isn't good enough. Take for example a normalised channel with a value of one, if it's encoded with 5 bits you get 11111 (binary) or 31. If you're converting that to an 8 bit value then by bit shifting you get 11111000 (binary) or 240. Of course this is no longer the normalised value for one. That would be 11111111 (binary) or 255.

One solution to this problem would be to convert each value to it's normalised floating point representation and then back. However that's a lot of float to int conversions for an image (4 x Width x Height). So I decided to create a simple lookup table for every colour channel that gets converted. This makes the conversion process simpler too.

It would also have been possible to do some kind of fast (perhaps approximate) integer only version. Perhaps duplicating the upper bits after shifting to the lower bits. That way we'd preserve both black and white. It would also save on having to allocate and create the lookup tables but would cost us as once the value was converted we'd have to shift it back to the right again. Given some time it's an approach that I may pursue for a second version of the library. It would be possible to combine this with reading into a small input buffer on the stack to remove all the requirements for memory allocation.

Anyway onto some code. Firstly I've presented a small wrapper that allows me to use this code in my library or anyone to easily wrap it to use anywhere:

Open seperate instance of code

That's not so interesting, however the colour channel converter header file is next:

Open seperate instance of code

With the implementation here:

Open seperate instance of code

TGA reading

Given this I have a solution to convert integer colour channels from one format to another at will. The only thing remains is to read and parse the TGA file. However there are a couple of issues there as well. TGAs can be encoded in a couple of different ways, regardless of the way the individual colours are encoded. The options are: However I want to use the same code for decompressing all of them. The difference between a colour and grey-scale source image is an easy one to compensate for. The colour channel converters can simply be built to use the same source for the red, green and blue channels and we get grey-scale output. However indexed images are more tricky. For those I decided to initially convert the whole palette into the target colour space and then look up. Finally the run length encoded version requires all the same tools as the normal bitmaps with a bit of extra code to duplicate runs of the same colour.

As I'm lazy though I only really wanted to write the code once. Being picky I want the code to be fast, I don't want to be branching for every pixel to decide if it's indexed or not and if I need to check for a run length packet.

Luckily I stumbled on a technique a while ago that lets me get away with this. It's all relying on the compiler to be smart and compile away constant conditional branches. Furthermore to create these constant conditionals I use templates with integer parameters. So it's requiring the compiler to be smart, whilst dealing with templates - a tall order (or at least it was a while back).

The only thing that's a little odd about this is that when you call the template function the parameters must be constant, so you end up having to do a little branching initialisation block, which I'll point out later. Firstly my TGA import class header:

Open seperate instance of code

Notice that the TReadImage template function takes two integer template parameters which I'm treating as booleans. This way that boolean logic inside the tight loops can be compiled away. Here's the implementation:

Open seperate instance of code

I've actually split the functionality a little further with the TScanLine template class to deal with actually copying each scan-line. This was simply a convenience as it made the code a little more simple for me to write. Also as I mentioned above note the CTGAImage::ReadImage(...) function simply wraps the CTGAImage::TReadImage(...) function and makes the explicit template function calls.

Download and License

I've applied a BSD license to this code, meaning you can use it for whatever you like so long as my license stays in place. All that's required to compile are the 5 files on this page, for convenience (and to preserve the tabs) I've zipped them up here. If you do use it then I'd love to know about it so drop comment please.

Comments

I'd welcome any comments, problems with this method and whatever. If you have any, please add them to the blog post related to this article.