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:
- A colour bitmap.
- A grey-scale bitmap.
- A indexed (palletised) bitmap.
- Run length encoded versions of all three of the above
- I've since read somewhere that sometimes the first three can be Huffman compressed.
- But as it wasn't in the document specification I read I've not supported it.
- I also suspect I'd end up using a 3rd party decompression suite (zlib?).
- For this kind of thing I really suspect a PNG is a much better idea.
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.