LoadTGAInC

From GPWiki

I found that the TGA file format is quite useful in game development, and is used extensively by a number of modern game-engines, including Unreal Engine 3. There are a number of online examples and tutorials describing how to bang out some C or C++ code to load a TGA file so that it can be used by a DirectX or OpenGL application. However, I wanted to create my own library that solves the following problems:

   o It can load the file into a graphics-library compatible format in a single
     pass (thus being much faster than the online examples I’ve read).
   o Handles arbitrary bit-length encoding.
   o Handles run-length encoded (RLE) Targa files.
   o Automatically converts any TGA file to my desired format – 32-bit RGBA. If
     an alpha channel is missing, a default one is automatically created. It also
     converts from black & white encoding.
   o Allows me to apply color-channel masking or replacement by modifying the
     serialized RGBA value, allowing me to manipulate the image prior to creation
     as a graphics-library texture.

Given that I’ve never found a real use for Targa color mappings, I dropped support for using them in my reference library. The reference library I created is written in C, and compiles natively on Windows, Linux and Mac OS X.

C Header File:

// define targa public constants
 
#define TARGA_COLOR_RED										1
#define TARGA_COLOR_GREEN									2
#define TARGA_COLOR_BLUE									3
#define TARGA_COLOR_ALPHA									4
 
 
// define targa public data types
 
typedef struct _Targa {
	int width;
	int height;
	int imageLength;
	unsigned char *image;
} Targa;
 
 
// declare targa public functions
 
/**
 * targa_init()
 *
 * Initilize the Targa structure for utilization.
 *
 * @param	targa(in)	The Targa struct to initialize.
 *
 * @return	An integer where zero is pass, less than zero is failure.
 */
int targa_init(Targa *targa);
 
 
/**
 * targa_free()
 *
 * Free the Targa structure.
 *
 * @param	targa(in)	The Targa struct to free.
 *
 * @return	An integer where zero is pass, less than zero is failure.
 */
int targa_free(Targa *targa);
 
 
/**
 * targa_getDimensions()
 *
 * Obtain the width and height in pixels of a loaded Targa image.
 *
 * @param	targa(in)	The Targa struct of a loaded image.
 *
 * @param	width(out)	The width in pixels of a loaded Targa image.
 *
 * @param	height(out)	The height in pixels of a loaded Targa image.
 *
 * @return	An integer where zero is pass, less than zero is failure.
 */
int targa_getDimensions(Targa *targa, int *width, int *height);
 
 
/**
 * targa_getImageLength()
 *
 * Obtain the length in bytes of the serialized RGBA image.
 *
 * @param	targa(in)			The Targa struct of a loaded image.
 *
 * @param	imageLength(out)	The length in bytes of the RGBA image.
 *
 * @return	An integer where zero is pass, less than zero is failure.
 */
int targa_getImageLength(Targa *targa, int *imageLength);
 
 
/**
 * targa_getRgbaTexture()
 *
 * Obtain the serialized RGBA texture and its' length from a Targa image.
 *
 * @param	targa(in)			The Targa struct of a loaded image.
 *
 * @param	texture(out)		The serialized RGBA image pointer.
 *
 * @param	textureLength(out)	The serialized RGBA image length in bytes.
 *
 * @return	An integer where zero is pass, less than zero is failure.
 */
int targa_getRgbaTexture(Targa *targa, char **texture, int *textureLength);
 
 
/**
 * targa_loadFromFile()
 *
 * Load a targa file and decode it into a 32-bit RGBA serialized image.
 *
 * @param	targa(in)		The Targa struct of an image to load.
 *
 * @param	filename(in)	The filename of the image to load.
 *
 * @return	An integer where zero is pass, less than zero is failure.
 */
int targa_loadFromFile(Targa *targa, char *filename);
 
 
/**
 * targa_loadFromData()
 *
 * Load the targa from an in-memory location and decode it into a 32-bit RGBA
 * serialize image.
 *
 * @param	targa(in)		The Targa struct an image to load.
 *
 * @param	data(in)		A pointer to an in-memory location containing a
 *							Targa image.
 *
 * @param	dataLength(in)	The length of the Targa in-memory image.
 *
 * @return	An integer where zero is pass, less than zero is failure.
 */
int targa_loadFromData(Targa *targa, unsigned char *data, int dataLength);
 
 
/**
 * targa_applyRgbaMask()
 *
 * Apply a red, green, blue or alpha-channel additive color-mask to a loaded
 * Targa image.
 *
 * @param	targa(in)		The Targa struct of a loaded image.
 *
 * @param	colorType(in)	The color channel to mask.
 *
 * @param	value(in)		The color code (0 - 255) to mask.
 *
 * @return	An integer where zero is pass, less than zero is failure.
 */
int targa_applyRgbaMask(Targa *targa, int colorType, unsigned char value);
 
 
/**
 * targa_setRgbaChannel()
 *
 * Apply a red, green, blue or alpha-channel additive color-channel
 * replacement to a loaded Targa image.
 *
 * @param	targa(in)		The Targa struct of a loaded image.
 *
 * @param	colorType(in)	The color channel to replace.
 *
 * @param	value(in)		The color code (0 - 255) to replace.
 *
 * @return	An integer where zero is pass, less than zero is failure.
 */
int targa_setRgbaChannel(Targa *targa, int colorType, unsigned char value);

C Code File:

// preprocessor directives
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "targa.h"
 
 
// define targa private constants
 
#define TGA_COLOR_MAP_NONE							0
 
#define TGA_IMAGE_TYPE_NONE							0
#define TGA_IMAGE_TYPE_CM							1
#define TGA_IMAGE_TYPE_BGR							2
#define TGA_IMAGE_TYPE_BW							3
#define TGA_IMAGE_TYPE_RLE_CM						9
#define TGA_IMAGE_TYPE_RLE_BGR						10
#define TGA_IMAGE_TYPE_RLE_BW						11
 
#define TGA_R										0
#define TGA_G										1
#define TGA_B										2
#define TGA_A										3
 
 
// declare targa private functions
 
static int handleTargaError(FILE *fh, int errorCode, const char *function,
		size_t line);
 
static int ctoi(char value);
 
static char *localStrndup(char *string, int length);
 
 
// define targa private macros
 
#define targaErrorf() \
	handleTargaError(fh, rc, __FUNCTION__, __LINE__)
 
 
// define targa private functions
 
static int handleTargaError(FILE *fh, int errorCode, const char *function,
		size_t line)
{
	char *errorMessage = NULL;
 
	if((errorMessage = strerror(errno)) == NULL) {
		errorMessage = "uknown error";
	}
 
	fprintf(stderr, "[%s():%i] error(%i) - #%i, '%s'.\n",
			(char *)function, (int)line, errorCode, (int)errno, errorMessage);
 
	if(fh != NULL) {
		fclose(fh);
	}
 
	return -1;
}
 
static int ctoi(char value)
{
	return (int)((unsigned char)value);
}
 
static char *localStrndup(char *string, int length)
{
	char *result = NULL;
 
	if((string == NULL) || (length < 1)) {
		fprintf(stderr, "[%s():%i] error - invalid or missing argument(s).\n",
				__FUNCTION__, __LINE__);
		return NULL;
	}
 
#if defined(_GNU_SOURCE)
	result = strndup(string, length);
#else // !_GNU_SOURCE
	result = (char *)malloc(sizeof(char) * (length + 1));
	memset(result, 0, (sizeof(char) * (length + 1)));
	memcpy(result, string, length);
#endif // _GNU_SOURCE
 
	return result;
}
 
 
// define targa public functions
 
int targa_init(Targa *targa)
{
	if(targa == NULL) {
		fprintf(stderr, "[%s():%i] error - invalid or missing argument(s).\n",
				__FUNCTION__, __LINE__);
		return -1;
	}
 
	memset((void *)targa, 0, sizeof(Targa));
 
	targa->width = 0;
	targa->height = 0;
	targa->imageLength = 0;
	targa->image = NULL;
 
	return 0;
}
 
int targa_free(Targa *targa)
{
	if(targa == NULL) {
		fprintf(stderr, "[%s():%i] error - invalid or missing argument(s).\n",
				__FUNCTION__, __LINE__);
		return -1;
	}
 
	if(targa->image != NULL) {
		free(targa->image);
	}
 
	memset((void *)targa, 0, sizeof(Targa));
 
	return 0;
}
 
int targa_getDimensions(Targa *targa, int *width, int *height)
{
	if((targa == NULL) || (width == NULL) || (height == NULL)) {
		fprintf(stderr, "[%s():%i] error - invalid or missing argument(s).\n",
				__FUNCTION__, __LINE__);
		return -1;
	}
 
	*width = targa->width;
	*height = targa->height;
 
	return 0;
}
 
int targa_getImageLength(Targa *targa, int *imageLength)
{
	if((targa == NULL) || (imageLength == NULL)) {
		fprintf(stderr, "[%s():%i] error - invalid or missing argument(s).\n",
				__FUNCTION__, __LINE__);
		return -1;
	}
 
	*imageLength = targa->imageLength;
 
	return 0;
}
 
int targa_getRgbaTexture(Targa *targa, char **texture, int *textureLength)
{
	if((targa == NULL) || (texture == NULL) || (textureLength == NULL)) {
		fprintf(stderr, "[%s():%i] error - invalid or missing argument(s).\n",
				__FUNCTION__, __LINE__);
		return -1;
	}
 
	*texture = (char *)targa->image;
	*textureLength = targa->imageLength;
 
	return 0;
}
 
int targa_loadFromFile(Targa *targa, char *filename)
{
	int rc = 0;
	int fileLength = 0;
	unsigned char *buffer = NULL;
 
	FILE *fh = NULL;
 
	if((targa == NULL) || (filename == NULL)) {
		fprintf(stderr, "[%s():%i] error - invalid or missing argument(s).\n",
				__FUNCTION__, __LINE__);
		return -1;
	}
 
	if((fh = fopen(filename, "r+b")) == NULL) {
		return targaErrorf();
	}
 
	if((rc = fseek(fh, 0, SEEK_END)) != 0) {
		return targaErrorf();
	}
 
	if((fileLength = ftell(fh)) < 0) {
		return targaErrorf();
	}
 
	if((rc = fseek(fh, 0, SEEK_SET)) != 0) {
		return targaErrorf();
	}
 
	if(fileLength < 18) {
		fprintf(stderr, "error - TGA file '%s' length %i invalid.\n",
				filename, fileLength);
		fclose(fh);
		return -1;
	}
 
	buffer = (unsigned char *)malloc(sizeof(unsigned char) * fileLength);
	memset(buffer, 0, (sizeof(unsigned char) * fileLength));
 
	rc = (int)fread((char *)buffer, sizeof(char), fileLength, fh);
	if(rc != fileLength) {
		return targaErrorf();
	}
 
	fclose(fh);
 
	rc = targa_loadFromData(targa, buffer, fileLength);
 
	free(buffer);
 
	return rc;
}
 
int targa_loadFromData(Targa *targa, unsigned char *data, int dataLength)
{
	short sNumber = 0;
	int ii = 0;
	int nn = 0;
	int imageIdLength = 0;
	int colorMap = 0;
	int imageType = 0;
	int bitLength = 0;
	int colorMode = 0;
	int length = 0;
	int rleId = 0;
	int pixel[4];
	unsigned char *imageId = NULL;
	unsigned char *ptr = NULL;
 
	if((targa == NULL) || (data == NULL) || (dataLength < 18)) {
		fprintf(stderr, "[%s():%i] error - invalid or missing argument(s).\n",
				__FUNCTION__, __LINE__);
		return -1;
	}
 
	ptr = data;
 
	// determine image ID length
 
	imageIdLength = (int)ptr[0];
	ptr++;
	if((int)(ptr - data) > dataLength) {
		fprintf(stderr, "[%s():%i] error - detected data overrun with %i vs "
				"%i.\n", __FUNCTION__, __LINE__, (int)(ptr - data),
				dataLength);
		return -1;
	}
 
	// check for color map
 
	colorMap = (int)ptr[0];
	ptr++;
	if((int)(ptr - data) > dataLength) {
		fprintf(stderr, "[%s():%i] error - detected data overrun with %i vs "
				"%i.\n", __FUNCTION__, __LINE__, (int)(ptr - data),
				dataLength);
		return -1;
	}
	if(colorMap != TGA_COLOR_MAP_NONE) {
		fprintf(stderr, "[%s():%i] error - unable to read TARGA color map "
				"%i.\n", __FUNCTION__, __LINE__, colorMap);
		return -1;
	}
 
	// obtain image type
 
	imageType = (int)ptr[0];
	ptr++;
	if((int)(ptr - data) > dataLength) {
		fprintf(stderr, "[%s():%i] error - detected data overrun with %i vs "
				"%i.\n", __FUNCTION__, __LINE__, (int)(ptr - data),
				dataLength);
		return -1;
	}
	if((imageType == TGA_IMAGE_TYPE_NONE) ||
			(imageType == TGA_IMAGE_TYPE_CM) ||
			(imageType == TGA_IMAGE_TYPE_RLE_CM)) {
		fprintf(stderr, "[%s():%i] error - unsupported image type %i.\n",
				__FUNCTION__, __LINE__, imageType);
		return -1;
	}
 
	// skip 9 bytes (color-map information and x & y origins)
 
	ptr += 9;
	if((int)(ptr - data) > dataLength) {
		fprintf(stderr, "[%s():%i] error - detected data overrun with %i vs "
				"%i.\n", __FUNCTION__, __LINE__, (int)(ptr - data),
				dataLength);
		return -1;
	}
 
	// obtain image width
 
	memcpy((char *)&sNumber, ptr, sizeof(short));
	ptr += sizeof(short);
	if((int)(ptr - data) > dataLength) {
		fprintf(stderr, "[%s():%i] error - detected data overrun with %i vs "
				"%i.\n", __FUNCTION__, __LINE__, (int)(ptr - data),
				dataLength);
		return -1;
	}
	if(sNumber < 1) {
		fprintf(stderr, "[%s():%i] error - invalid image width %i.\n",
				__FUNCTION__, __LINE__, (int)sNumber);
		return -1;
	}
 
	targa->width = (int)sNumber;
 
	// obtain image height
 
	memcpy((char *)&sNumber, ptr, sizeof(short));
	ptr += sizeof(short);
	if((int)(ptr - data) > dataLength) {
		fprintf(stderr, "[%s():%i] error - detected data overrun with %i vs "
				"%i.\n", __FUNCTION__, __LINE__, (int)(ptr - data),
				dataLength);
		return -1;
	}
	if(sNumber < 1) {
		fprintf(stderr, "[%s():%i] error - invalid image height %i.\n",
				__FUNCTION__, __LINE__, (int)sNumber);
		return -1;
	}
 
	targa->height = (int)sNumber;
 
	// determine pixel depth
 
	bitLength = (int)ptr[0];
	ptr++;
	if((int)(ptr - data) > dataLength) {
		fprintf(stderr, "[%s():%i] error - detected data overrun with %i vs "
				"%i.\n", __FUNCTION__, __LINE__, (int)(ptr - data),
				dataLength);
		return -1;
	}
	if((bitLength != 16) && (bitLength != 24) && (bitLength != 32)) {
		fprintf(stderr, "[%s():%i] error - unknown pixel depth of %i-bits.\n",
				__FUNCTION__, __LINE__, bitLength);
		return -1;
	}
	if((bitLength == 16) &&
			((imageType != TGA_IMAGE_TYPE_BGR) &&
			 (imageType != TGA_IMAGE_TYPE_BW))) {
		fprintf(stderr, "[%s():%i] error - unable to RLE-decode pixel depth "
				"of %i-bits.\n", __FUNCTION__, __LINE__, bitLength);
		return -1;
	}
 
	// skip 1 byte (image descriptor)
 
	ptr++;
	if((int)(ptr - data) > dataLength) {
		fprintf(stderr, "[%s():%i] error - detected data overrun with %i vs "
				"%i.\n", __FUNCTION__, __LINE__, (int)(ptr - data),
				dataLength);
		return -1;
	}
 
	// obtain the image ID
 
	if(imageIdLength > 0) {
		if(((int)(ptr - data) + imageIdLength) > dataLength) {
			fprintf(stderr, "[%s():%i] error - detected data overrun at %i "
					"(image ID) by %i bytes.\n", __FUNCTION__, __LINE__,
					(int)(ptr - data),
					(((int)(ptr - data) + imageIdLength) - dataLength));
			return -1;
		}
		imageId = (unsigned char *)localStrndup((char *)ptr, imageIdLength);
		ptr += imageIdLength;
		if((int)(ptr - data) > dataLength) {
			fprintf(stderr, "[%s():%i] error - detected data overrun with %i "
					"vs %i.\n", __FUNCTION__, __LINE__, (int)(ptr - data),
					dataLength);
			return -1;
		}
	}
 
	// process the image
 
	targa->imageLength = (long int)(targa->width * targa->height * 4);
	targa->image = (unsigned char *)malloc(sizeof(unsigned char) *
			targa->imageLength);
 
	if((imageType == TGA_IMAGE_TYPE_BGR) || (imageType == TGA_IMAGE_TYPE_BW)) {
		if(bitLength == 16) {
			colorMode = 2;
		}
		else {
			colorMode = (bitLength / 8);
		}
		length = (targa->width * targa->height * colorMode);
		if(((int)(ptr - data) + length) > dataLength) {
			fprintf(stderr, "[%s():%i] error - detected data overrun at %i "
					"(image pixels) by %i bytes.\n", __FUNCTION__, __LINE__,
					(int)(ptr - data),
					(((int)(ptr - data) + length) - dataLength));
			return -1;
		}
		for(ii = 0, nn = 0; ((ii < length) && (nn < targa->imageLength));
				ii += colorMode, nn += 4) {
			if(colorMode == 2) {
				memcpy((char *)&sNumber, ptr, sizeof(short));
				pixel[TGA_R] = ctoi((sNumber & 0x1f) << 3);
				pixel[TGA_G] = ctoi(((sNumber >> 5) & 0x1f) << 3);
				pixel[TGA_B] = ctoi(((sNumber >> 10) & 0x1f) << 3);
				pixel[TGA_A] = 255;
			}
			else {
				pixel[TGA_R] = ctoi(ptr[2]);
				pixel[TGA_G] = ctoi(ptr[1]);
				pixel[TGA_B] = ctoi(ptr[0]);
				if(colorMode == 3) {
					pixel[TGA_A] = 255;
				}
				else {
					pixel[TGA_A] = ctoi(ptr[3]);
				}
			}
 
			targa->image[(nn + 0)] = (unsigned char)pixel[TGA_R];
			targa->image[(nn + 1)] = (unsigned char)pixel[TGA_G];
			targa->image[(nn + 2)] = (unsigned char)pixel[TGA_B];
			targa->image[(nn + 3)] = (unsigned char)pixel[TGA_A];
 
			ptr += colorMode;
		}
	}
	else { // RLE image
		ii = 0;
		nn = 0;
		rleId = 0;
		colorMode = (bitLength / 8);
		length = (targa->width * targa->height);
		while(ii < length) {
			rleId = (int)ptr[0];
			ptr++;
			if((int)(ptr - data) > dataLength) {
				fprintf(stderr, "[%s():%i] error - detected data overrun with "
						"%i vs %i.\n", __FUNCTION__, __LINE__,
						(int)(ptr - data), dataLength);
				return -1;
			}
 
			if(rleId < 128) {
				rleId++;
				while(rleId > 0) {
					pixel[TGA_R] = ctoi(ptr[2]);
					pixel[TGA_G] = ctoi(ptr[1]);
					pixel[TGA_B] = ctoi(ptr[0]);
					if(colorMode == 3) {
						pixel[TGA_A] = 255;
					}
					else {
						pixel[TGA_A] = ctoi(ptr[3]);
					}
 
					targa->image[(nn + 0)] = (unsigned char)pixel[TGA_R];
					targa->image[(nn + 1)] = (unsigned char)pixel[TGA_G];
					targa->image[(nn + 2)] = (unsigned char)pixel[TGA_B];
					targa->image[(nn + 3)] = (unsigned char)pixel[TGA_A];
 
					rleId--;
					ii++;
					nn += 4;
					if(nn >= targa->imageLength) {
						break;
					}
					ptr += colorMode;
					if((int)(ptr - data) > dataLength) {
						fprintf(stderr, "[%s():%i] error - detected data "
								"overrun with %i vs %i.\n", __FUNCTION__,
								__LINE__, (int)(ptr - data), dataLength);
						return -1;
					}
				}
			}
			else {
				pixel[TGA_R] = ctoi(ptr[2]);
				pixel[TGA_G] = ctoi(ptr[1]);
				pixel[TGA_B] = ctoi(ptr[0]);
				if(colorMode == 3) {
					pixel[TGA_A] = 255;
				}
				else {
					pixel[TGA_A] = ctoi(ptr[3]);
				}
				ptr += colorMode;
				if((int)(ptr - data) > dataLength) {
					fprintf(stderr, "[%s():%i] error - detected data overrun "
							"with %i vs %i.\n", __FUNCTION__, __LINE__,
							(int)(ptr - data), dataLength);
					return -1;
				}
 
				rleId -= 127;
				while(rleId > 0) {
					targa->image[(nn + 0)] = (unsigned char)pixel[TGA_R];
					targa->image[(nn + 1)] = (unsigned char)pixel[TGA_G];
					targa->image[(nn + 2)] = (unsigned char)pixel[TGA_B];
					targa->image[(nn + 3)] = (unsigned char)pixel[TGA_A];
 
					rleId--;
					ii++;
					nn += 4;
					if(nn >= targa->imageLength) {
						break;
					}
				}
			}
			if(nn >= targa->imageLength) {
				break;
			}
		}
	}
 
	if(imageId != NULL) {
		free(imageId);
	}
 
	return 0;
}
 
int targa_applyRgbaMask(Targa *targa, int colorType, unsigned char value)
{
	int ii = 0;
	int startPosition = 0;
 
	if((targa == NULL) ||
			((colorType != TARGA_COLOR_RED) &&
			 (colorType != TARGA_COLOR_GREEN) &&
			 (colorType != TARGA_COLOR_BLUE) &&
			 (colorType != TARGA_COLOR_ALPHA))) {
		fprintf(stderr, "[%s():%i] error - invalid or missing argument(s).\n",
				__FUNCTION__, __LINE__);
		return -1;
	}
 
	switch(colorType) {
		case TARGA_COLOR_RED:
			startPosition = 0;
			break;
		case TARGA_COLOR_GREEN:
			startPosition = 1;
			break;
		case TARGA_COLOR_BLUE:
			startPosition = 2;
			break;
		case TARGA_COLOR_ALPHA:
			startPosition = 3;
			break;
	}
 
	for(ii = startPosition; ii < targa->imageLength; ii += 4) {
		targa->image[ii] += value;
	}
 
	return 0;
}
 
int targa_setRgbaChannel(Targa *targa, int colorType, unsigned char value)
{
	int ii = 0;
	int startPosition = 0;
 
	if((targa == NULL) ||
			((colorType != TARGA_COLOR_RED) &&
			 (colorType != TARGA_COLOR_GREEN) &&
			 (colorType != TARGA_COLOR_BLUE) &&
			 (colorType != TARGA_COLOR_ALPHA))) {
		fprintf(stderr, "[%s():%i] error - invalid or missing argument(s).\n",
				__FUNCTION__, __LINE__);
		return -1;
	}
 
	switch(colorType) {
		case TARGA_COLOR_RED:
			startPosition = 0;
			break;
		case TARGA_COLOR_GREEN:
			startPosition = 1;
			break;
		case TARGA_COLOR_BLUE:
			startPosition = 2;
			break;
		case TARGA_COLOR_ALPHA:
			startPosition = 3;
			break;
	}
 
	for(ii = startPosition; ii < targa->imageLength; ii += 4) {
		targa->image[ii] = value;
	}
 
	return 0;
}

Both the ZIP archive (download it here) and the tarball (download it here) contain a Microsoft Visual Studio solution and project files as well as a simple makefile. You can read the Doxygen-generated documentation here.