Truevision Targa

About the code

The code in this article was written using Code::Blocks and SDL 2.
You can read here a guide to install this sofware.
Although it is based on SDL, I don't use its functions directly. I have written a small library with a few basic
functions to ease the understanding and the portability to another language.
You can read more about this lib here.

Description of the format

Truevision Targa is one of the simplest image format.
It handles paletted or truecolor images of 8, 15, 24 or 32 bits and supports RLE compression.
There are 2 versions of this format 1.0 and 2.0. Here I will describe the 1.0 version.
Version 2.0 only adds additionnal datas at the end of the file, mostly to add a description or the author's name.

The image file contains 4 main parts.

I will describe the loading of all the supported formats of these files.
We will store the image in an array of "Color" objects. "Color" being the class I wrote for my SDL library.
I will also describe the saving of one of these Color array. We will save it in 32 bits truecolor, as my library
don't really use color palettes at the moment.

File functions

Before we dive into the loading functions, to make thing clearer I want to avoid the error handling as much as
possible.
So a wrote a bunch of file utility functions that handle all the file errors.
They are not very complex. They only call the standard C file functions (fopen, fclose, fread, ...) and if an error
occurs they print a message and exit the program.
Here is the header file for these functions:
		#ifndef CFILE_H
		#define CFILE_H

		#include <stdio.h>
		#include <SDL.h>

		class CFile
		{
		public:
			struct SFile
			{
				FILE*   file;
				char*   fileName;
			};

			SFile*      open(const char* fileName, const char* mode);
			void        close(SFile* f);
			void        seek(SFile* f, long int offset, int origin);
			long int    tell(SFile* f);

			void    read(SFile* f, void* dest, int nbBytes);
			Uint8   readU8(SFile* f);
			Sint8   readS8(SFile* f);
			Uint16  readU16(SFile* f);
			Sint16  readS16(SFile* f);
			Uint32  readU32(SFile* f);
			Sint32  readS32(SFile* f);
			Uint64  readU64(SFile* f);
			Sint64  readS64(SFile* f);
			float   readFloat(SFile* f);
			double  readDouble(SFile* f);

			void    write(SFile* f, const void* src, int nbBytes);
			void    writeU8(SFile* f, const Uint8 value);
			void    writeS8(SFile* f, const Sint8 value);
			void    writeU16(SFile* f, const Uint16 value);
			void    writeS16(SFile* f, const Sint16 value);
			void    writeU32(SFile* f, const Uint32 value);
			void    writeS32(SFile* f, const Sint32 value);
			void    writeU64(SFile* f, const Uint64 value);
			void    writeS64(SFile* f, const Sint64 value);
			void    writeFloat(SFile* f, const float value);
			void    writeDouble(SFile* f, const double value);

		private:
			void    printError(const char* error, const char* fileName);
			void    printErrorAndClose(const char* error, SFile* f);
		};

		extern CFile file;

		#endif // CFILE_H
				
If you want to take a deeper look at them you can download the files here.

Loading: the header

The header contains the following fields:

Size in bits Name Description
8 IDLength Number of bytes of the file description area that we will skip.
8 colorMapType 0 if there is no palette.
1 if there is a palette.
8 imageType 0: No image. We won't handle this case.
1: Indexed. Means the image datas contains indexes to the palette colors.
2: Truecolor. The image datas contains the direct RGB(A) values of the pixels.
3: Grayscale. No palette. The image datas contains a gray value for each pixel.
9: Indexed, RLE compressed.
10: Truecolor, RLE compressed.
11: Grayscale, RLE compressed.
16 colorMap_firstEntryIndex Index of the first color in the palette.
16 colorMap_length Number of colors in the palette.
8 colorMap_entrySize Number of bits of a color in the palette.
Can be 15, 16, 24 or 32 bits.
15 and 16 bits are the same. There are 5 bits per component, the 16th is unused.
16 xOrigin x position of the image on the screen. We will always assume it's 0.
16 yOrigin y position of the image on the screen. We will always assume it's 0.
16 imageWidth Horizontal size of the image in pixels.
16 imageHeight Vertical size of the image in pixels.
8 pixelDepth Size of the datas for each pixel in the "image datas" area in bits.
It can be either the size of the pixel components, the size of the index in the palette, or the size of
the gray value.
This field can take the values 8, 16, 24 or 32.
8 flags
  • bits 0 to 3: number of bits of alpha. This can either be 0, 1 or 8.
    The value 1 is only used for 16 bits images, but we discard this value in this case because the
    documentation is not really precise about that.
  • bit 4: horizontal direction of the image. 0: left to right, 1: right to left
  • bit 5: vertical direction of the image. 0: bottom to top, 1: top to bottom
Unlike most of image formats, Targa can store the image in various orientations. The default is "left to right"
and "bottom to top". Meaning that the first pixel is at the bottom-left of the screen.

I will write all the code related to the Targa file format in a TGA class.
The header for this class contains a few enums for various values defined in the table above, and a structure with
all the fields of the header.
		#ifndef TGA_H
		#define TGA_H

		#include "../Graphics.h"
		#include "../CFile.h"

		class TGA
		{
		public:
			Color*  load(const char* filename, int* width, int* height);

		private:
			enum EHDir
			{
				eLeftToRight,
				eRightToLeft
			};

			enum EVDir
			{
				eTopToBottom,
				eBottomToTop
			};

			enum EImageType
			{
				eNoImage = 0,
				eIndexed = 1,
				eTrueColor = 2,
				eGray = 3,
				eIndexedRLE = 9,
				eTrueColorRLE = 10,
				eGrayRLE = 11
			};

			// header datas
			struct SHeader
			{
				Uint8   IDLength;
				Uint8   colorMapType;
				Uint8   imageType;
				Uint16  colorMap_firstEntryIndex;
				Uint16  colorMap_length;
				Uint8   colorMap_entrySize;
				Uint16  xOrigin;
				Uint16  yOrigin;
				Uint16  imageWidth;
				Uint16  imageHeight;
				Uint8   pixelDepth;
				Uint8   alphaBits;
				EHDir   hDirection;
				EVDir   vDirection;
			};

			void    printError(const char* error, CFile::SFile* f);

			void    readHeader(CFile::SFile* f, SHeader* h);
			void    printHeader(SHeader* h);
		};

		extern TGA tga;

		#endif // TGA_H
				
Now I will explain the few functions.
load() is the main entry point to load an image.
It takes as input the name of the file we want to load, and returns the Color array, the width and the height of
the image.
		Color* TGA::load(const char* filename, int* width, int* height)
		{
			CFile::SFile*   f = file.open(filename, "rb");

			SHeader header;
			readHeader(f, &header);
			printHeader(&header);

			Color* image = NULL;
			*width  = header.imageWidth;
			*height = header.imageHeight;

			file.close(f);
			return image;
		}
				
printError() is a small function that prints an error message and exits the program.
		void TGA::printError(const char* error, CFile::SFile* f)
		{
			printf("TGA %s : %s\n", f->fileName, error);
			file.close(f);
			exit(EXIT_FAILURE);
		}
				
readHeader is the function that reads each field of the header following the table above, and stores them in the
SHeader structure.
For most of the fields, I check if the value we get follow the rules described in the table.
If it's not the case, we print an error message.
		void TGA::readHeader(CFile::SFile* f, SHeader* h)
		{
			h->IDLength = file.readU8(f);

			h->colorMapType = file.readU8(f);
			if (h->colorMapType > 1)
				printError("Wrong format", f);

			h->imageType = file.readU8(f);

			if (h->imageType != eIndexed &&
			    h->imageType != eTrueColor &&
			    h->imageType != eGray &&
			    h->imageType != eIndexedRLE &&
			    h->imageType != eTrueColorRLE &&
			    h->imageType != eGrayRLE)
			{
				printError("Wrong format", f);
			}

			h->colorMap_firstEntryIndex = file.readU16(f);
			h->colorMap_length = file.readU16(f);
			h->colorMap_entrySize = file.readU8(f);

			if (h->colorMapType == 1 && h->colorMap_length != 0)
			{
				if (h->colorMap_entrySize != 15 &&
				    h->colorMap_entrySize != 16 &&
				    h->colorMap_entrySize != 24 &&
				    h->colorMap_entrySize != 32)
				{
					printError("Wrong format", f);
				}
			}

			h->xOrigin = file.readU16(f);
			h->yOrigin = file.readU16(f);
			h->imageWidth  = file.readU16(f);
			h->imageHeight = file.readU16(f);
			h->pixelDepth = file.readU8(f);

			if (h->pixelDepth != 8 &&
			    h->pixelDepth != 16 &&
			    h->pixelDepth != 24 &&
			    h->pixelDepth != 32)
			{
				printError("Wrong format", f);
			}

			Uint8 flags = file.readU8(f);

			if ((flags & 0xC0) != 0)
				printError("Wrong format", f);

			h->alphaBits = flags & 0xF;

			if (h->alphaBits != 0 &&
			    h->alphaBits != 1 &&
			    h->alphaBits != 8)
			{
				printError("Wrong format", f);
			}

			if ((flags & 0x10) == 0)
				h->hDirection = eLeftToRight;
			else
				h->hDirection = eRightToLeft;

			if ((flags & 0x20) == 0)
				h->vDirection = eBottomToTop;
			else
				h->vDirection = eTopToBottom;

			if (h->imageType == eIndexed && h->colorMap_length == 0)
				printError("Wrong format", f);

			if (h->imageType == eIndexedRLE && h->colorMap_length == 0)
				printError("Wrong format", f);
		}
				
And to test this program I wrote a printHeader function that displays all the values we read from the file.
		void TGA::printHeader(SHeader* h)
		{
			printf("IDLength: %d\n", h->IDLength);
			printf("colorMapType: %d\n", h->colorMapType);
			printf("imageType: %d\n", h->imageType);
			printf("colorMap_firstEntryIndex: %d\n", h->colorMap_firstEntryIndex);
			printf("colorMap_length: %d\n", h->colorMap_length);
			printf("colorMap_entrySize: %d\n", h->colorMap_entrySize);
			printf("xOrigin: %d\n", h->xOrigin);
			printf("yOrigin: %d\n", h->yOrigin);
			printf("imageWidth: %d\n", h->imageWidth);
			printf("imageHeight: %d\n", h->imageHeight);
			printf("pixelDepth: %d\n", h->pixelDepth);
			printf("alphaBits: %d\n", h->alphaBits);
			printf("hDirection: %s\n", (h->hDirection == eLeftToRight ? "left to right" : "right to left"));
			printf("vDirection: %s\n", (h->vDirection == eTopToBottom ? "top to bottom" : "bottom to top"));
		}
				
Finally the main function simply calls the TGA::load function:
		int main(int argc, char* argv[])
		{
			// init the window
			int width, height;

			Color* image = tga.load("image.tga", &width, &height);

			if (image != NULL)
				delete[] image;

			return EXIT_SUCCESS;
		}

		Download source code
		Download executable for Windows
				
With the sample image I included in the sources, the output of this program is:
	IDLength: 0
	colorMapType: 1
	imageType: 1
	colorMap_firstEntryIndex: 0
	colorMap_length: 256
	colorMap_entrySize: 24
	xOrigin: 0
	yOrigin: 0
	imageWidth: 800
	imageHeight: 574
	pixelDepth: 8
	alphaBits: 0
	hDirection: left to right
	vDirection: bottom to top
				

Loading: the palette

Now we will load the color palette, so we will add these lines to the load function:
		Color* TGA::load(const char* filename, int* width, int* height)
		{
			[...]
			// read header
			[...]

			// skip image ID
			if (header.IDLength != 0)
				file.seek(f, header.IDLength, SEEK_CUR);

			// read palette
			Color*  palette = readPalette(f, &header);
			printPalette(&header, palette);

			// read image
			[...]
		}
				
The number of colors in the palette is given by colorMap_length.
colorMap_entrySize defines the format of the colors.

If colorMap_entrySize is 15 or 16 bits, we read a 16 bits word.
The red, green and blue components of the color are 5 bits each. The 16th bit is not used.
This table shows which bits are used for each components in the 16 bits word:

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0

If colorMap_entrySize is 24 bits, the blue, green and red components are stored in this order in 3 bytes.

byte 0 byte 1 byte 2

If colorMap_entrySize is 32 bits, a 4th byte is added for the alpha component.

byte 0 byte 1 byte 2 byte 3

The readPalette() function will read all the colors and store them in a Color array.
If the type of the image does not need a palette, we simply skip the datas in the file.
		Color* TGA::readPalette(CFile::SFile* f, SHeader* h)
		{
			Color*  palette = NULL;
			int bytesPerColor = (h->colorMap_entrySize + 7) / 8;

			if (h->imageType == eIndexed || h->imageType == eIndexedRLE)
			{
				palette = new Color[h->colorMap_length];

				for (int i = 0; i < h->colorMap_length; i++)
				{
					if (h->colorMap_entrySize == 15 || h->colorMap_entrySize == 16)
					{
						Uint16 value = file.readU16(f);
						palette[i].b = ( value        & 0x1F) << 3;
						palette[i].g = ((value >>  5) & 0x1F) << 3;
						palette[i].r = ((value >> 10) & 0x1F) << 3;
						palette[i].a = 255;
					}
					else if (h->colorMap_entrySize == 24)
					{
						palette[i].b = file.readU8(f);
						palette[i].g = file.readU8(f);
						palette[i].r = file.readU8(f);
						palette[i].a = 255;
					}
					else if (h->colorMap_entrySize == 32)
					{
						palette[i].b = file.readU8(f);
						palette[i].g = file.readU8(f);
						palette[i].r = file.readU8(f);
						palette[i].a = file.readU8(f);
					}
				}
			}
			else
			{
				file.seek(f, h->colorMap_length * bytesPerColor, SEEK_CUR);
			}

			return palette;
		}
				
As for the header, I wrote a small function to print the value we read.
		void TGA::printPalette(SHeader* h, Color* palette)
		{
			for (int i = 0; i < h->colorMap_length; i++)
				printf("color %d: (r:%02x, g:%02x, b:%02x, a:%02x)\n", i, palette[i].r, palette[i].g, palette[i].b, palette[i].a);
		}

		Download source code
		Download executable for Windows
				
With the sample image in the sources you will see that.
	color 0: (r:04, g:b3, b:ab, a:ff)
	color 1: (r:bc, g:a8, b:95, a:ff)
	color 2: (r:2d, g:83, b:d3, a:ff)
	color 3: (r:02, g:a3, b:a0, a:ff)
	color 4: (r:b0, g:94, b:82, a:ff)
	color 5: (r:25, g:26, b:28, a:ff)
	color 6: (r:c5, g:bc, b:ab, a:ff)
	color 7: (r:35, g:3b, b:40, a:ff)
	color 8: (r:30, g:67, b:b2, a:ff)
	color 9: (r:3a, g:5c, b:90, a:ff)
	color 10: (r:3c, g:51, b:67, a:ff)
	color 11: (r:94, g:79, b:6b, a:ff)
	color 12: (r:07, g:86, b:86, a:ff)
	color 13: (r:33, g:a4, b:e9, a:ff)
	color 14: (r:c3, g:cf, b:cf, a:ff)
	color 15: (r:6c, g:90, b:ab, a:ff)
	color 16: (r:00, g:00, b:00, a:ff)
	color 17: (r:00, g:00, b:00, a:ff)
	...
				
Note: it's a 16 color image that I created with "The Gimp", but it decided to save a 256 color palette full of 0.

Loading: uncompressed image data

Now we will read the image data in a file that is not RLE compressed.
The load function will now call a readImage function:
		Color* TGA::load(const char* filename, int* width, int* height)
		{
			[...]

			// read image
			Color* image = readImage(f, &header, palette);
			*width  = header.imageWidth;
			*height = header.imageHeight;

			[...]
		}
				
In this function we will load the image data from the file and store it in an imageData array:
		Color*  TGA::readImage(CFile::SFile* f, SHeader* h, Color* palette)
		{
			int bytesPerPixel = (h->pixelDepth + 7) / 8;
			Color*  image = new Color[h->imageWidth * h->imageHeight];
			Uint8*  imageData = new Uint8[h->imageWidth * h->imageHeight * bytesPerPixel];
			int pixelIndex = 0;

			if (h->imageType == eIndexed ||
			    h->imageType == eTrueColor ||
			    h->imageType == eGray)
			{
				file.read(f, imageData, h->imageWidth * h->imageHeight * bytesPerPixel);
			}
			else
			{
				// compressed image
			}
				
We loop over each pixel of the image:
			for (int y = 0; y < h->imageHeight; y++)
				for (int x = 0; x < h->imageWidth; x++)
				{
					Color   c;
				
We read the next pixel in imageData:
					// read the next pixel
					Uint32 pixel = 0;

					for (int i = 0; i < bytesPerPixel; i++)
						pixel |= imageData[pixelIndex++] << (i * 8);
				
And now we will decode its color.
The first case is an indexed image. So the pixel we read is the index of a color in the palette:
					// decode color
					if (h->imageType == eIndexed || h->imageType == eIndexedRLE)
					{
						c = palette[pixel - h->colorMap_firstEntryIndex];
					}
				
In the second case we read a grayscale image. The pixel in this case is a gray level.
We simply set this level to the red, green and blue components of our color.
					else if (h->imageType == eGray || h->imageType == eGrayRLE)
					{
						int g = pixel >> (h->pixelDepth - 8);
						c = Color(g, g, g, 255);
					}
				
In the last case, we have a truecolor image. Here the color is in the same format as the palette:
					else
					{
						if (bytesPerPixel == 2)
						{
							c.b = ( pixel        & 0x1F) << 3;
							c.g = ((pixel >>  5) & 0x1F) << 3;
							c.r = ((pixel >> 10) & 0x1F) << 3;
							c.a = 255;
						}
						else if (bytesPerPixel == 3)
						{
							c.b =  pixel        & 0xFF;
							c.g = (pixel >>  8) & 0xFF;
							c.r = (pixel >> 16) & 0xFF;
							c.a = 255;
						}
						else if (bytesPerPixel == 4)
						{
							c.b =  pixel        & 0xFF;
							c.g = (pixel >>  8) & 0xFF;
							c.r = (pixel >> 16) & 0xFF;
							c.a = (pixel >> 24) & 0xFF;
						}
					}
				
Finally, to find the place where we will write this color we have to handle the image orientation:
					// handle image directions
					int destX, destY;

					if (h->hDirection == eLeftToRight)
						destX = x;
					else
						destX = h->imageWidth - 1 - x;

					if (h->vDirection == eTopToBottom)
						destY = y;
					else
						destY = h->imageHeight - 1 - y;

					image[destY * h->imageWidth + destX] = c;
				}

			return image;
		}
				
Now we will display this image on the screen in the main() function:
		int main(int argc, char* argv[])
		{
			// init the window
			int width, height;

			Color* image = tga.load("image.tga", &width, &height);

			gfx.init("TGA Reader", width, height);
			gfx.init2D();
			gfx.clearScreen(Color(0, 0, 0));

			// copy image to the screen
			for (int y = 0; y < height; y++)
				for (int x = 0; x < width; x++)
					gfx.setPixel(x, y, image[y * width + x]);

			// wait till the end
			while (sys.isQuitRequested() == false)
			{
				gfx.render();
				sys.processEvents();
				sys.wait(50);
			}

			gfx.quit();

			if (image != NULL)
				delete[] image;

			return EXIT_SUCCESS;
		}
				
		Download source code
		Download executable for Windows
				
You should now see the sample image:

Loading: uncompressing

As we said the image can be compressed with RLE. I already wrote an article about this compression method.
The compressed data can be seen as several packets.
Each packet contains a 1 byte counter followed by one or more pixels.
The pixels data are in the same format as the palette datas or the uncompressed image data, so they can be 8, 16,
24 or 32 bits values.

If the most significant bit of the counter is 0, it is followed by N pixels to be copied as is to the output.
Where N is the value of the counter + 1.
Here is an example:

0x03 pixel 1 pixel 2 pixel 3 pixel 4

If the most significant bit of the counter is 1, it is followed by only one pixel to repeat N times in the output.
N here is the value of the 7 least significant bits of the counter + 1.
Here is an example:

0x83 pixel

The pixel will be repeated 4 times in this case.

So the uncompression function is quite simple.
We start by computing the size of the uncompressed data.
We assume that an output memory of this size has been allocated outside of this function.
		void TGA::uncompress(CFile::SFile* f, SHeader* h, Uint8* imageData, int bytesPerPixel)
		{
			int uncompSize = h->imageWidth * h->imageHeight * bytesPerPixel;
			int index = 0;
				
Then we will loop until we reach this size
			while (index < uncompSize)
			{
				
We read the counter and extract the number of pixels from it
				Uint8   counter = file.readU8(f);
				Uint8   counter2 = (counter & 0x7f) + 1;
				
If uncompressing this packet would make us overflow the output memory, we print an error.
				if (index + counter2 * bytesPerPixel > uncompSize)
					printError("Wrong format", f);
				
If the most significant bit of the counter is 0, we write the "N" following pixels to the output.
				if ((counter & 0x80) == 0)
				{
					// raw packet
					file.read(f, &imageData[index], counter2 * bytesPerPixel);
					index += counter2 * bytesPerPixel;
				}
				
If the most significant bit is 1, we read the following pixel, and write it "N" times to the output.
				else
				{
					// run-length packet
					static Uint8 pixel[4];

					file.read(f, pixel, bytesPerPixel);

					for (int i = 0; i < counter2; i++)
						for (int j = 0; j < bytesPerPixel; j++)
							imageData[index++] = pixel[j];
				}
			}
		}
				
And we're done.
Now, we don't forget to call this function at the beginning of readImage:
		Color*  TGA::readImage(CFile::SFile* f, SHeader* h, Color* palette)
		{
			[...]

			if (h->imageType == eIndexed ||
			    h->imageType == eTrueColor ||
			    h->imageType == eGray)
			{
				file.read(f, imageData, h->imageWidth * h->imageHeight * bytesPerPixel);
			}
			else
			{
				uncompress(f, h, imageData, bytesPerPixel);
			}

		Download source code
		Download executable for Windows
				
The source code contains a sample compressed image that is read by the program.

Saving: uncompressed image

We will now write a save function to save an uncompressed image.
As my SDL library only handles an image as an array of Color objects, we will save it as a truecolor 32 bits file.
So here is the save function:
		void TGA::save(const char* filename, int width, int height, Color* image, bool compress)
		{
			CFile::SFile*   f = file.open(filename, "wb");

			writeHeader(f, width, height, compress);
			writeImage(f, width, height, image, compress);

			file.close(f);
		}
				
As we chose a specific format, the writeHeader function is simple:
		void TGA::writeHeader(CFile::SFile* f, int width, int height, bool compress)
		{
			file.writeU8(f, 0); // IDLength
			file.writeU8(f, 0); // colorMapType

			// imageType
			if (compress == false)
				file.writeU8(f, 2);
			else
				file.writeU8(f, 10);

			file.writeU16(f, 0); // colorMap_firstEntryIndex
			file.writeU16(f, 0); // colorMap_length
			file.writeU8(f, 0);  // colorMap_entrySize

			file.writeU16(f, 0);      // xOrigin
			file.writeU16(f, 0);      // yOrigin
			file.writeU16(f, width);  // imageWidth
			file.writeU16(f, height); // imageHeight
			file.writeU8(f, 32);      // pixelDepth

			file.writeU8(f, 8); // flags: 8 bits of alpha, left to right, bottom to top
		}
				
The writeImage function simply saves an array of pixels:
		void TGA::writeImage(CFile::SFile* f, int width, int height, Color* image, bool compress)
		{
			if (compress == false)
			{
				for (int y = 0; y < height; y++)
					for (int x = 0; x < width; x++)
					{
						Color c = image[(height - 1 - y) * width + x];
						writePixel(f, c);
					}
			}
			else
			{
				// compress
			}
		}
				
And here is the function to write one pixel:
		void TGA::writePixel(CFile::SFile* f, Color c)
		{
			file.writeU8(f, c.b);
			file.writeU8(f, c.g);
			file.writeU8(f, c.r);
			file.writeU8(f, c.a);
		}
				
Now to test that, in the main function, we will load an image, save it to another name, delete the image memory and
reload the image we saved. If everything went right you should see the same image on the screen.
		int main(int argc, char* argv[])
		{
			// init the window
			int width, height;

			Color* image = tga.load("image2.tga", &width, &height);
			tga.save("test.tga", width, height, image, false);
			delete[] image;
			image = tga.load("test.tga", &width, &height);

		Download source code
		Download executable for Windows
				

Saving: compressing

We will not compress the image datas as a whole chunk, but line by line.
This is not a requirement in the 1.0 Targa specification but the documentation advises this for the 2.0 version.
So the writeImage function will contain a loop:
		void TGA::writeImage(CFile::SFile* f, int width, int height, Color* image, bool compress)
		{
			if (compress == false)
			{
				[...]
			}
			else
			{
				for (int y = 0; y < height; y++)
				{
					Color* line = &image[(height - 1 - y) * width];
					compressLine(f, line, width);
				}
			}
		}
				
To compress the image data I will use the compression function I described in my article about RLE compression. I only slightly modified it to write the result directly to the file.
		#define TGA_MIN_RUN     3
		#define TGA_MAX_RUN     128
		#define TGA_MIN_STRING  1
		#define TGA_MAX_STRING  128

		void TGA::compressLine(CFile::SFile* f, Color* input, int inputSize)
		{
			int stringStart = 0;
			int runStart = 0;
			int currentPixel = 1;
			bool isInRun = false;

			while (true)
			{
				if (isInRun == false)
				{
					// string mode
					if (input[currentPixel] != input[runStart])
						runStart = currentPixel;

					int stringLength = runStart - stringStart;
					int runLength    = currentPixel + 1 - runStart;

					if (currentPixel == inputSize ||
					    runLength    == TGA_MIN_RUN ||
					    stringLength == TGA_MAX_STRING)
					{
						// emit string
						if (stringLength >= TGA_MIN_STRING)
						{
							file.writeU8(f, stringLength - 1);

							for (int i = 0; i < stringLength; i++)
								writePixel(f, input[stringStart++]);
						}

						// go to run mode
						if (runLength == TGA_MIN_RUN)
							isInRun = true;
					}
				}
				else
				{
					// run mode
					stringStart = currentPixel;

					int runLength = currentPixel - runStart;

					if (input[currentPixel] != input[runStart] ||
					    currentPixel == inputSize ||
					    runLength == TGA_MAX_RUN)
					{
						// emit run
						file.writeU8(f, 0x80 + runLength - 1);
						writePixel(f, input[runStart]);
						runStart = currentPixel;

						// go to string mode
						isInRun = false;
					}
				}

				if (currentPixel == inputSize)
					break;

				currentPixel++;
			}
		}

		Download source code
		Download executable for Windows
				

Links

Official format specifications

Sample files