File: cpw_libtarga.c

/*
** libtarga.c -- routines for reading targa files.
*/

#include "cpw_libtarga.h"

#define TGA_IMG_NODATA             (0)
#define TGA_IMG_UNC_PALETTED       (1)
#define TGA_IMG_UNC_TRUECOLOR      (2)
#define TGA_IMG_UNC_GRAYSCALE      (3)
#define TGA_IMG_RLE_PALETTED       (9)
#define TGA_IMG_RLE_TRUECOLOR      (10)
#define TGA_IMG_RLE_GRAYSCALE      (11)


#define TGA_LOWER_LEFT             (0)
#define TGA_LOWER_RIGHT            (1)
#define TGA_UPPER_LEFT             (2)
#define TGA_UPPER_RIGHT            (3)


#define HDR_LENGTH               (18)
#define HDR_IDLEN                (0)
#define HDR_CMAP_TYPE            (1)
#define HDR_IMAGE_TYPE           (2)
#define HDR_CMAP_FIRST           (3)
#define HDR_CMAP_LENGTH          (5)
#define HDR_CMAP_ENTRY_SIZE      (7)
#define HDR_IMG_SPEC_XORIGIN     (8)
#define HDR_IMG_SPEC_YORIGIN     (10)
#define HDR_IMG_SPEC_WIDTH       (12)
#define HDR_IMG_SPEC_HEIGHT      (14)
#define HDR_IMG_SPEC_PIX_DEPTH   (16)
#define HDR_IMG_SPEC_IMG_DESC    (17)



#define TGA_ERR_NONE                    (0)
#define TGA_ERR_BAD_HEADER              (1)
#define TGA_ERR_OPEN_FAILS              (2)
#define TGA_ERR_BAD_FORMAT              (3)
#define TGA_ERR_UNEXPECTED_EOF          (4)
#define TGA_ERR_NODATA_IMAGE            (5)
#define TGA_ERR_COLORMAP_FOR_GRAY       (6)
#define TGA_ERR_BAD_COLORMAP_ENTRY_SIZE (7)
#define TGA_ERR_BAD_COLORMAP            (8)
#define TGA_ERR_READ_FAILS              (9)
#define TGA_ERR_BAD_IMAGE_TYPE          (10)
#define TGA_ERR_BAD_DIMENSIONS          (11)

static int16 ttohs( int16 val );
static int16 htots( int16 val );
static int32 ttohl( int32 val );
static int32 htotl( int32 val );

static uint32 tga_get_pixel( FILE * tga, ubyte bytes_per_pix, 
                            ubyte * colormap, ubyte cmap_bytes_entry );

static uint32 tga_convert_color( uint32 pixel, uint32 bpp_in, ubyte alphabits, uint32 format_out );

static void tga_write_pixel_to_mem( ubyte * dat, ubyte img_spec, uint32 number, 
                                   uint32 w, uint32 h, uint32 pixel, uint32 format );

/* returns a pointer to the string for an error code */
const char* tga_error_string( uint32 error_code )
{

    switch( error_code ) {
    
    case TGA_ERR_NONE:
        return( "no error" );

    case TGA_ERR_BAD_HEADER:
        return( "bad image header" );

    case TGA_ERR_OPEN_FAILS:
        return( "cannot open file" );

    case TGA_ERR_BAD_FORMAT:
        return( "bad format argument" );

    case TGA_ERR_UNEXPECTED_EOF:
        return( "unexpected end-of-file" );

    case TGA_ERR_NODATA_IMAGE:
        return( "image contains no data" );

    case TGA_ERR_COLORMAP_FOR_GRAY:
        return( "found colormap for a grayscale image" );

    case TGA_ERR_BAD_COLORMAP_ENTRY_SIZE:
        return( "unsupported colormap entry size" );

    case TGA_ERR_BAD_COLORMAP:
        return( "bad colormap" );

    case TGA_ERR_READ_FAILS:
        return( "cannot read from file" );

    case TGA_ERR_BAD_IMAGE_TYPE:
        return( "unknown image type" );

    case TGA_ERR_BAD_DIMENSIONS:
        return( "image has size 0 width or height (or both)" );

    default:
        return( "unknown error" );

    }

    return( NULL );
}

/* creates a targa image of the desired format */

void * tga_create( TargaInfo * ti ) 
{

    switch( ti->format ) {
        
    case TGA_TRUECOLOR_32:
        return( (void *)ltgamalloc( ti->width * ti->height * 4 ) );
        
    case TGA_TRUECOLOR_24:
        return( (void *)ltgamalloc( ti->width * ti->height * 3 ) );
        
    default:
        ti->TargaError = TGA_ERR_BAD_FORMAT;
        break;

    }

    return( NULL );
}

/* loads a targa from disk and return info about it */

void tga_info( TargaInfo * ti, const char * filename ) {
    
    ubyte  idlen;               // length of the image_id string below.
    ubyte  cmap_type;           // paletted image <=> cmap_type
    ubyte  image_type;          // can be any of the IMG_TYPE constants above.
    uint16 cmap_first;          // 
    uint16 cmap_length;         // how long the colormap is
    ubyte  cmap_entry_size;     // how big a palette entry is.
    uint16 img_spec_xorig;      // the x origin of the image in the image data.
    uint16 img_spec_yorig;      // the y origin of the image in the image data.
    uint16 img_spec_width;      // the width of the image.
    uint16 img_spec_height;     // the height of the image.
    ubyte  img_spec_pix_depth;  // the depth of a pixel in the image.
    ubyte  img_spec_img_desc;   // the image descriptor.

    FILE * targafile;

    ubyte * tga_hdr = NULL;

    ubyte * colormap = NULL;

    ubyte alphabits = 0;

    uint32 num_pixels;
    
    uint32 bytes_total = 0;

    if ( ti == null ) return;

    ti->error = false;

    /* open binary image file */
    targafile = fopen( filename, "rb" );
    if( targafile == NULL ) {
        ti->TargaError = TGA_ERR_OPEN_FAILS;
        ti->error = true;
        return;
    }


    /* allocate memory for the header */
    tga_hdr = (ubyte *)ltgamalloc( HDR_LENGTH );

    /* read the header in. */
    if( fread( (void *)tga_hdr, 1, HDR_LENGTH, targafile ) != HDR_LENGTH ) {
        ltgafree( tga_hdr );
        ti->TargaError = TGA_ERR_BAD_HEADER;
        ti->error = true;
        return;
    }

    
    /* byte order is important here. */
    idlen              = (ubyte)tga_hdr[HDR_IDLEN];
    
    image_type         = (ubyte)tga_hdr[HDR_IMAGE_TYPE];
    
    cmap_type          = (ubyte)tga_hdr[HDR_CMAP_TYPE];
    cmap_first         = ttohs( *(uint16 *)(&tga_hdr[HDR_CMAP_FIRST]) );
    cmap_length        = ttohs( *(uint16 *)(&tga_hdr[HDR_CMAP_LENGTH]) );
    cmap_entry_size    = (ubyte)tga_hdr[HDR_CMAP_ENTRY_SIZE];

    img_spec_xorig     = ttohs( *(uint16 *)(&tga_hdr[HDR_IMG_SPEC_XORIGIN]) );
    img_spec_yorig     = ttohs( *(uint16 *)(&tga_hdr[HDR_IMG_SPEC_YORIGIN]) );
    img_spec_width     = ttohs( *(uint16 *)(&tga_hdr[HDR_IMG_SPEC_WIDTH]) );
    img_spec_height    = ttohs( *(uint16 *)(&tga_hdr[HDR_IMG_SPEC_HEIGHT]) );
    img_spec_pix_depth = (ubyte)tga_hdr[HDR_IMG_SPEC_PIX_DEPTH];
    img_spec_img_desc  = (ubyte)tga_hdr[HDR_IMG_SPEC_IMG_DESC];

    ltgafree( tga_hdr );


    num_pixels = img_spec_width * img_spec_height;

    if( num_pixels == 0 ) {
        ti->TargaError = TGA_ERR_BAD_DIMENSIONS;
        ti->error = true;
        return;
    }

    
    alphabits = img_spec_img_desc & 0x0F;

    if ( img_spec_pix_depth == 24 ) ti->format = 3;
    else if ( img_spec_pix_depth == 32 ) ti->format = 4;
    else ti->error = true;
    ti->width = img_spec_width;
    ti->height = img_spec_height;

    return;
}


/* loads and converts a targa from disk */

void * tga_load( TargaInfo * ti, const char * filename ) {
    
    ubyte  idlen;               // length of the image_id string below.
    ubyte  cmap_type;           // paletted image <=> cmap_type
    ubyte  image_type;          // can be any of the IMG_TYPE constants above.
    uint16 cmap_first;          // 
    uint16 cmap_length;         // how long the colormap is
    ubyte  cmap_entry_size;     // how big a palette entry is.
    uint16 img_spec_xorig;      // the x origin of the image in the image data.
    uint16 img_spec_yorig;      // the y origin of the image in the image data.
    uint16 img_spec_width;      // the width of the image.
    uint16 img_spec_height;     // the height of the image.
    ubyte  img_spec_pix_depth;  // the depth of a pixel in the image.
    ubyte  img_spec_img_desc;   // the image descriptor.
    unsigned int format;

    FILE * targafile;

    ubyte * tga_hdr = NULL;

    ubyte * colormap = NULL;

    ubyte cmap_bytes_entry;
    uint32 cmap_bytes;
    
    uint32 tmp_col;
    uint32 tmp_int32;
    ubyte  tmp_byte;

    ubyte alphabits = 0;

    uint32 num_pixels;
    
    uint32 i;
    uint32 j;

    ubyte * image_data;
    uint32 img_dat_len;

    ubyte bytes_per_pix;

    ubyte true_bits_per_pixel;

    uint32 bytes_total = 0;

    ubyte packet_header;
    ubyte repcount;
    
    /* open binary image file */
    targafile = fopen( filename, "rb" );
    if( targafile == NULL ) {
        ti->TargaError = TGA_ERR_OPEN_FAILS;
        ti->error = true;
        return ti;
    }


    /* allocate memory for the header */
    tga_hdr = (ubyte *)ltgamalloc( HDR_LENGTH );

    /* read the header in. */
    if( fread( (void *)tga_hdr, 1, HDR_LENGTH, targafile ) != HDR_LENGTH ) {
        ltgafree( tga_hdr );
        ti->TargaError = TGA_ERR_BAD_HEADER;
        ti->error = true;
        return ti;
    }

    
    /* byte order is important here. */
    idlen              = (ubyte)tga_hdr[HDR_IDLEN];
    
    image_type         = (ubyte)tga_hdr[HDR_IMAGE_TYPE];
    
    cmap_type          = (ubyte)tga_hdr[HDR_CMAP_TYPE];
    cmap_first         = ttohs( *(uint16 *)(&tga_hdr[HDR_CMAP_FIRST]) );
    cmap_length        = ttohs( *(uint16 *)(&tga_hdr[HDR_CMAP_LENGTH]) );
    cmap_entry_size    = (ubyte)tga_hdr[HDR_CMAP_ENTRY_SIZE];

    img_spec_xorig     = ttohs( *(uint16 *)(&tga_hdr[HDR_IMG_SPEC_XORIGIN]) );
    img_spec_yorig     = ttohs( *(uint16 *)(&tga_hdr[HDR_IMG_SPEC_YORIGIN]) );
    img_spec_width     = ttohs( *(uint16 *)(&tga_hdr[HDR_IMG_SPEC_WIDTH]) );
    img_spec_height    = ttohs( *(uint16 *)(&tga_hdr[HDR_IMG_SPEC_HEIGHT]) );
    img_spec_pix_depth = (ubyte)tga_hdr[HDR_IMG_SPEC_PIX_DEPTH];
    img_spec_img_desc  = (ubyte)tga_hdr[HDR_IMG_SPEC_IMG_DESC];

    ltgafree( tga_hdr );

    if ( img_spec_pix_depth == 24 ) format = TGA_TRUECOLOR_24;
    else if ( img_spec_pix_depth == 32 ) format = TGA_TRUECOLOR_32;
    else return null;

    num_pixels = img_spec_width * img_spec_height;

    if( num_pixels == 0 ) {
        ti->TargaError = TGA_ERR_BAD_DIMENSIONS;
        ti->error = true;
        return ti;
    }

    
    alphabits = img_spec_img_desc & 0x0F;

    if ( img_spec_pix_depth == 24 ) ti->format = 3;
    else if ( img_spec_pix_depth == 32 ) ti->format = 4;
    else ti->error = true;
    ti->width = img_spec_width;
    ti->height = img_spec_height;
    
    /* seek past the image id, if there is one */
    if( idlen ) {
        if( fseek( targafile, idlen, SEEK_CUR ) ) {
            ti->TargaError = TGA_ERR_UNEXPECTED_EOF;
            ti->error = true;
            return ti;
        }
    }


    /* if this is a 'nodata' image, just jump out. */
    if( image_type == TGA_IMG_NODATA ) {
        ti->TargaError = TGA_ERR_NODATA_IMAGE;
        ti->error = true;
        return ti;
    }


    /* now we're starting to get into the meat of the matter. */
    
    
    /* deal with the colormap, if there is one. */
    if( cmap_type ) {

        switch( image_type ) {
            
        case TGA_IMG_UNC_PALETTED:
        case TGA_IMG_RLE_PALETTED:
            break;
            
        case TGA_IMG_UNC_TRUECOLOR:
        case TGA_IMG_RLE_TRUECOLOR:
            // this should really be an error, but some really old
            // crusty targas might actually be like this (created by TrueVision, no less!)
            // so, we'll hack our way through it.
            break;
            
        case TGA_IMG_UNC_GRAYSCALE:
        case TGA_IMG_RLE_GRAYSCALE:
            ti->TargaError = TGA_ERR_COLORMAP_FOR_GRAY;
            ti->error = true;
            return ti;
        }
        
        /* ensure colormap entry size is something we support */
        if( !(cmap_entry_size == 15 || 
            cmap_entry_size == 16 ||
            cmap_entry_size == 24 ||
            cmap_entry_size == 32) ) {
            ti->TargaError = TGA_ERR_BAD_COLORMAP_ENTRY_SIZE;
            ti->error = true;
            return ti;
        }
        
        
        /* allocate memory for a colormap */
        if( cmap_entry_size & 0x07 ) {
            cmap_bytes_entry = (((8 - (cmap_entry_size & 0x07)) + cmap_entry_size) >> 3);
        } else {
            cmap_bytes_entry = (cmap_entry_size >> 3);
        }
        
        cmap_bytes = cmap_bytes_entry * cmap_length;
        colormap = (ubyte *)ltgamalloc( cmap_bytes );
        
        
        for( i = 0; i < cmap_length; i++ ) {
            
            /* seek ahead to first entry used */
            if( cmap_first != 0 ) {
                fseek( targafile, cmap_first * cmap_bytes_entry, SEEK_CUR );
            }
            
            tmp_int32 = 0;
            for( j = 0; j < cmap_bytes_entry; j++ ) {
                if( !fread( &tmp_byte, 1, 1, targafile ) ) {
                    ltgafree( colormap );
                    ti->TargaError = TGA_ERR_BAD_COLORMAP;
                    ti->error = true;
                    return ti;
                }
                tmp_int32 += tmp_byte << (j * 8);
            }

            // byte order correct.
            tmp_int32 = ttohl( tmp_int32 );

            for( j = 0; j < cmap_bytes_entry; j++ ) {
                colormap[i * cmap_bytes_entry + j] = (uchar)((tmp_int32 >> (8 * j)) & 0xFF);
            }
            
        }

    }


    // compute number of bytes in an image data unit (either index or BGR triple)
    if( img_spec_pix_depth & 0x07 ) {
        bytes_per_pix = (((8 - (img_spec_pix_depth & 0x07)) + img_spec_pix_depth) >> 3);
    } else {
        bytes_per_pix = (img_spec_pix_depth >> 3);
    }


    /* assume that there's one byte per pixel */
    if( bytes_per_pix == 0 ) {
        bytes_per_pix = 1;
    }


    /* compute how many bytes of storage we need for the image */
    bytes_total = img_spec_width * img_spec_height * format;

    image_data = (ubyte *)ltgamalloc( bytes_total );

    img_dat_len = img_spec_width * img_spec_height * bytes_per_pix;

    // compute the true number of bits per pixel
    true_bits_per_pixel = cmap_type ? cmap_entry_size : img_spec_pix_depth;

    switch( image_type ) {

    case TGA_IMG_UNC_TRUECOLOR:
    case TGA_IMG_UNC_GRAYSCALE:
    case TGA_IMG_UNC_PALETTED:

        /* FIXME: support grayscale */

        for( i = 0; i < num_pixels; i++ ) {

            // get the color value.
            tmp_col = tga_get_pixel( targafile, bytes_per_pix, colormap, cmap_bytes_entry );
            tmp_col = tga_convert_color( tmp_col, true_bits_per_pixel, alphabits, format );
            
            // now write the data out.
            tga_write_pixel_to_mem( image_data, img_spec_img_desc, 
                i, img_spec_width, img_spec_height, tmp_col, format );

        }
    
        break;


    case TGA_IMG_RLE_TRUECOLOR:
    case TGA_IMG_RLE_GRAYSCALE:
    case TGA_IMG_RLE_PALETTED:

        // FIXME: handle grayscale..

        for( i = 0; i < num_pixels; ) {

            /* a bit of work to do to read the data.. */
            if( fread( &packet_header, 1, 1, targafile ) < 1 ) {
                // well, just let them fill the rest with null pixels then...
                packet_header = 1;
            }

            if( packet_header & 0x80 ) {
                /* run length packet */

                tmp_col = tga_get_pixel( targafile, bytes_per_pix, colormap, cmap_bytes_entry );
                tmp_col = tga_convert_color( tmp_col, true_bits_per_pixel, alphabits, format );
                
                repcount = (packet_header & 0x7F) + 1;
                
                /* write all the data out */
                for( j = 0; j < repcount; j++ ) {
                    tga_write_pixel_to_mem( image_data, img_spec_img_desc, 
                        i + j, img_spec_width, img_spec_height, tmp_col, format );
                }

                i += repcount;

            } else {
                /* raw packet */
                /* get pixel from file */
                
                repcount = (packet_header & 0x7F) + 1;
                
                for( j = 0; j < repcount; j++ ) {
                    
                    tmp_col = tga_get_pixel( targafile, bytes_per_pix, colormap, cmap_bytes_entry );
                    tmp_col = tga_convert_color( tmp_col, true_bits_per_pixel, alphabits, format );
                    
                    tga_write_pixel_to_mem( image_data, img_spec_img_desc, 
                        i + j, img_spec_width, img_spec_height, tmp_col, format );

                }

                i += repcount;

            }

        }

        break;
    

    default:

        ti->TargaError = TGA_ERR_BAD_IMAGE_TYPE;
        ti->error = true;
        return ti;

    }

    fclose( targafile );

    return( (void *)image_data );

}

int tga_write_raw( const char * file, int width, int height, unsigned char * dat, unsigned int format ) {

    FILE * tga;

    uint32 i, j;

    uint32 size = width * height;

    float red, green, blue, alpha;

    char id[] = "written with libtarga";
    ubyte idlen = 21;
    ubyte zeroes[5] = { 0, 0, 0, 0, 0 };
    uint32 pixbuf;
    ubyte one = 1;
    ubyte cmap_type = 0;
    ubyte img_type  = 2;  // 2 - uncompressed truecolor  10 - RLE truecolor
    uint16 xorigin  = 0;
    uint16 yorigin  = 0;
    ubyte  pixdepth = format * 8;  // bpp
    ubyte img_desc;
    
    
    switch( format ) {

    case TGA_TRUECOLOR_24:
        img_desc = 0;
        break;

    case TGA_TRUECOLOR_32:
        img_desc = 8;
        break;

    default:
        //TargaError = TGA_ERR_BAD_FORMAT;
        return( 0 );
        break;

    }

    tga = fopen( file, "wb" );

    if( tga == NULL ) {
        //TargaError = TGA_ERR_OPEN_FAILS;
        return( 0 );
    }

    // write id length
    fwrite( &idlen, 1, 1, tga );

    // write colormap type
    fwrite( &cmap_type, 1, 1, tga );

    // write image type
    fwrite( &img_type, 1, 1, tga );

    // write cmap spec.
    fwrite( &zeroes, 5, 1, tga );

    // write image spec.
    fwrite( &xorigin, 2, 1, tga );
    fwrite( &yorigin, 2, 1, tga );
    fwrite( &width, 2, 1, tga );
    fwrite( &height, 2, 1, tga );
    fwrite( &pixdepth, 1, 1, tga );
    fwrite( &img_desc, 1, 1, tga );


    // write image id.
    fwrite( &id, idlen, 1, tga );

    // color correction -- data is in RGB, need BGR.
    for( i = 0; i < size; i++ ) {

        pixbuf = 0;
        for( j = 0; j < format; j++ ) {
            pixbuf += dat[i*format+j] << (8 * j);
        }

        switch( format ) {

        case TGA_TRUECOLOR_24:

            pixbuf = ((pixbuf & 0xFF) << 16) + 
                     (pixbuf & 0xFF00) + 
                     ((pixbuf & 0xFF0000) >> 16);

            pixbuf = htotl( pixbuf );
            
            fwrite( &pixbuf, 3, 1, tga );

            break;

        case TGA_TRUECOLOR_32:

            /* need to un-premultiply alpha.. */

            red     = (pixbuf & 0xFF) / 255.0f;
            green   = ((pixbuf & 0xFF00) >> 8) / 255.0f;
            blue    = ((pixbuf & 0xFF0000) >> 16) / 255.0f;
            alpha   = ((pixbuf & 0xFF000000) >> 24) / 255.0f;

            if( alpha > 0.0001 ) {
                red /= alpha;
                green /= alpha;
                blue /= alpha;
            }

            /* clamp to 1.0f */

            red = red > 1.0f ? 255.0f : red * 255.0f;
            green = green > 1.0f ? 255.0f : green * 255.0f;
            blue = blue > 1.0f ? 255.0f : blue * 255.0f;
            alpha = alpha > 1.0f ? 255.0f : alpha * 255.0f;

            pixbuf = (ubyte)blue + (((ubyte)green) << 8) + 
                (((ubyte)red) << 16) + (((ubyte)alpha) << 24);
                
            pixbuf = htotl( pixbuf );
           
            fwrite( &pixbuf, 4, 1, tga );

            break;

        }

    }

    fclose( tga );

    return( 1 );

}

int tga_write_rle( const char * file, int width, int height, unsigned char * dat, unsigned int format ) {

    FILE * tga;

    uint32 i, j;
    uint32 oc, nc;

    enum RLE_STATE { INIT, NONE, RLP, RAWP };

    int state = INIT;

    uint32 size = width * height;

    uint16 shortwidth = (uint16)width;
    uint16 shortheight = (uint16)height;

    ubyte repcount;

    float red, green, blue, alpha;

    int idx, row, column;

    // have to buffer a whole line for raw packets.
    unsigned char * rawbuf = (unsigned char *)ltgamalloc( width * format );  

    char id[] = "written with libtarga";
    ubyte idlen = 21;
    ubyte zeroes[5] = { 0, 0, 0, 0, 0 };
    uint32 pixbuf;
    ubyte one = 1;
    ubyte cmap_type = 0;
    ubyte img_type  = 10;  // 2 - uncompressed truecolor  10 - RLE truecolor
    uint16 xorigin  = 0;
    uint16 yorigin  = 0;
    ubyte  pixdepth = format * 8;  // bpp
    ubyte img_desc  = format == TGA_TRUECOLOR_32 ? 8 : 0;
  

    switch( format ) {
    case TGA_TRUECOLOR_24:
    case TGA_TRUECOLOR_32:
        break;

    default:
        //TargaError = TGA_ERR_BAD_FORMAT;
        return( 0 );
    }


    tga = fopen( file, "wb" );

    if( tga == NULL ) {
        //TargaError = TGA_ERR_OPEN_FAILS;
        return( 0 );
    }

    // write id length
    fwrite( &idlen, 1, 1, tga );

    // write colormap type
    fwrite( &cmap_type, 1, 1, tga );

    // write image type
    fwrite( &img_type, 1, 1, tga );

    // write cmap spec.
    fwrite( &zeroes, 5, 1, tga );

    // write image spec.
    fwrite( &xorigin, 2, 1, tga );
    fwrite( &yorigin, 2, 1, tga );
    fwrite( &shortwidth, 2, 1, tga );
    fwrite( &shortheight, 2, 1, tga );
    fwrite( &pixdepth, 1, 1, tga );
    fwrite( &img_desc, 1, 1, tga );


    // write image id.
    fwrite( &id, idlen, 1, tga );

    // initial color values -- just to shut up the compiler.
    nc = 0;

    // color correction -- data is in RGB, need BGR.
    // also run-length-encoding.
    for( i = 0; i < size; i++ ) {

        idx = i * format;

        row = i / width;
        column = i % width;

        //printf( "row: %d, col: %d\n", row, column );
        pixbuf = 0;
        for( j = 0; j < format; j++ ) {
            pixbuf += dat[idx+j] << (8 * j);
        }

        switch( format ) {

        case TGA_TRUECOLOR_24:

            pixbuf = ((pixbuf & 0xFF) << 16) + 
                     (pixbuf & 0xFF00) + 
                     ((pixbuf & 0xFF0000) >> 16);

            pixbuf = htotl( pixbuf );
            break;

        case TGA_TRUECOLOR_32:

            /* need to un-premultiply alpha.. */

            red     = (pixbuf & 0xFF) / 255.0f;
            green   = ((pixbuf & 0xFF00) >> 8) / 255.0f;
            blue    = ((pixbuf & 0xFF0000) >> 16) / 255.0f;
            alpha   = ((pixbuf & 0xFF000000) >> 24) / 255.0f;

            if( alpha > 0.0001 ) {
                red /= alpha;
                green /= alpha;
                blue /= alpha;
            }

            /* clamp to 1.0f */

            red = red > 1.0f ? 255.0f : red * 255.0f;
            green = green > 1.0f ? 255.0f : green * 255.0f;
            blue = blue > 1.0f ? 255.0f : blue * 255.0f;
            alpha = alpha > 1.0f ? 255.0f : alpha * 255.0f;

            pixbuf = (ubyte)blue + (((ubyte)green) << 8) + 
                (((ubyte)red) << 16) + (((ubyte)alpha) << 24);
                
            pixbuf = htotl( pixbuf );
            break;

        }


        oc = nc;

        nc = pixbuf;


        switch( state ) {

        case INIT:
            // this is just used to make sure we have 2 pixel values to consider.
            state = NONE;
            break;


        case NONE:

            if( column == 0 ) {
                // write a 1 pixel raw packet for the old pixel, then go thru again.
                repcount = 0;
                fwrite( &repcount, 1, 1, tga );
#ifdef WORDS_BIGENDIAN
                fwrite( (&oc)+4, format, 1, tga );  // byte order..
#else
                fwrite( &oc, format, 1, tga );
#endif
                state = NONE;
                break;
            }

            if( nc == oc ) {
                repcount = 0;
                state = RLP;
            } else {
                repcount = 0;
                state = RAWP;
                for( j = 0; j < format; j++ ) {
#ifdef WORDS_BIGENDIAN
                    rawbuf[(repcount * format) + j] = (ubyte)(*((&oc)+format-j-1));
#else
                    rawbuf[(repcount * format) + j] = *(((ubyte *)(&oc)) + j);
#endif
                }
            }
            break;


        case RLP:
            repcount++;

            if( column == 0 ) {
                // finish off rlp.
                repcount |= 0x80;
                fwrite( &repcount, 1, 1, tga );
#ifdef WORDS_BIGENDIAN
                fwrite( (&oc)+4, format, 1, tga );  // byte order..
#else
                fwrite( &oc, format, 1, tga );
#endif
                state = NONE;
                break;
            }

            if( repcount == 127 ) {
                // finish off rlp.
                repcount |= 0x80;
                fwrite( &repcount, 1, 1, tga );
#ifdef WORDS_BIGENDIAN
                fwrite( (&oc)+4, format, 1, tga );  // byte order..
#else
                fwrite( &oc, format, 1, tga );
#endif
                state = NONE;
                break;
            }

            if( nc != oc ) {
                // finish off rlp
                repcount |= 0x80;
                fwrite( &repcount, 1, 1, tga );
#ifdef WORDS_BIGENDIAN
                fwrite( (&oc)+4, format, 1, tga );  // byte order..
#else
                fwrite( &oc, format, 1, tga );
#endif
                state = NONE;
            }
            break;


        case RAWP:
            repcount++;

            if( column == 0 ) {
                // finish off rawp.
                for( j = 0; j < format; j++ ) {
#ifdef WORDS_BIGENDIAN
                    rawbuf[(repcount * format) + j] = (ubyte)(*((&oc)+format-j-1));
#else
                    rawbuf[(repcount * format) + j] = *(((ubyte *)(&oc)) + j);
#endif
                }
                fwrite( &repcount, 1, 1, tga );
                fwrite( rawbuf, (repcount + 1) * format, 1, tga );
                state = NONE;
                break;
            }

            if( repcount == 127 ) {
                // finish off rawp.
                for( j = 0; j < format; j++ ) {
#ifdef WORDS_BIGENDIAN
                    rawbuf[(repcount * format) + j] = (ubyte)(*((&oc)+format-j-1));
#else
                    rawbuf[(repcount * format) + j] = *(((ubyte *)(&oc)) + j);
#endif
                }
                fwrite( &repcount, 1, 1, tga );
                fwrite( rawbuf, (repcount + 1) * format, 1, tga );
                state = NONE;
                break;
            }

            if( nc == oc ) {
                // finish off rawp
                repcount--;
                fwrite( &repcount, 1, 1, tga );
                fwrite( rawbuf, (repcount + 1) * format, 1, tga );
                
                // start new rlp
                repcount = 0;
                state = RLP;
                break;
            }

            // continue making rawp
            for( j = 0; j < format; j++ ) {
#ifdef WORDS_BIGENDIAN
                rawbuf[(repcount * format) + j] = (ubyte)(*((&oc)+format-j-1));
#else
                rawbuf[(repcount * format) + j] = *(((ubyte *)(&oc)) + j);
#endif
            }

            break;

        }
       

    }


    // clean up state.

    switch( state ) {

    case INIT:
        break;

    case NONE:
        // write the last 2 pixels in a raw packet.
        fwrite( &one, 1, 1, tga );
#ifdef WORDS_BIGENDIAN
                fwrite( (&oc)+4, format, 1, tga );  // byte order..
#else
                fwrite( &oc, format, 1, tga );
#endif
#ifdef WORDS_BIGENDIAN
                fwrite( (&nc)+4, format, 1, tga );  // byte order..
#else
                fwrite( &nc, format, 1, tga );
#endif
        break;

    case RLP:
        repcount++;
        repcount |= 0x80;
        fwrite( &repcount, 1, 1, tga );
#ifdef WORDS_BIGENDIAN
                fwrite( (&oc)+4, format, 1, tga );  // byte order..
#else
                fwrite( &oc, format, 1, tga );
#endif
        break;

    case RAWP:
        repcount++;
        for( j = 0; j < format; j++ ) {
#ifdef WORDS_BIGENDIAN
            rawbuf[(repcount * format) + j] = (ubyte)(*((&oc)+format-j-1));
#else
            rawbuf[(repcount * format) + j] = *(((ubyte *)(&oc)) + j);
#endif
        }
        fwrite( &repcount, 1, 1, tga );
        fwrite( rawbuf, (repcount + 1) * 3, 1, tga );
        break;

    }


    // close the file.
    fclose( tga );

    ltgafree( rawbuf );

    return( 1 );

}






/*************************************************************************************************/







static void tga_write_pixel_to_mem( ubyte * dat, ubyte img_spec, uint32 number, 
                                   uint32 w, uint32 h, uint32 pixel, uint32 format ) {

    // write the pixel to the data regarding how the
    // header says the data is ordered.

    uint32 j;
    uint32 x, y;
    uint32 addy;

    switch( (img_spec & 0x30) >> 4 ) {

    case TGA_LOWER_RIGHT:
        x = w - 1 - (number % w);
        y = number / h;
        break;

    case TGA_UPPER_LEFT:
        x = number % w;
        y = h - 1 - (number / w);
        break;

    case TGA_UPPER_RIGHT:
        x = w - 1 - (number % w);
        y = h - 1 - (number / w);
        break;

    case TGA_LOWER_LEFT:
    default:
        x = number % w;
        y = number / w;
        break;

    }

    addy = (y * w + x) * format;
    for( j = 0; j < format; j++ ) {
        dat[addy + j] = (ubyte)((pixel >> (j * 8)) & 0xFF);
    }
    
}





static uint32 tga_get_pixel( FILE * tga, ubyte bytes_per_pix, 
                            ubyte * colormap, ubyte cmap_bytes_entry ) {
    
    /* get the image data value out */

    uint32 tmp_col;
    uint32 tmp_int32;
    ubyte tmp_byte;

    uint32 j;

    tmp_int32 = 0;
    for( j = 0; j < bytes_per_pix; j++ ) {
        if( fread( &tmp_byte, 1, 1, tga ) < 1 ) {
            tmp_int32 = 0;
        } else {
            tmp_int32 += tmp_byte << (j * 8);
        }
    }
    
    /* byte-order correct the thing */
    switch( bytes_per_pix ) {
        
    case 2:
        tmp_int32 = ttohs( (uint16)tmp_int32 );
        break;
        
    case 3: /* intentional fall-thru */
    case 4:
        tmp_int32 = ttohl( tmp_int32 );
        break;
        
    }
    
    if( colormap != NULL ) {
        /* need to look up value to get real color */
        tmp_col = 0;
        for( j = 0; j < cmap_bytes_entry; j++ ) {
            tmp_col += colormap[cmap_bytes_entry * tmp_int32 + j] << (8 * j);
        }
    } else {
        tmp_col = tmp_int32;
    }
    
    return( tmp_col );
    
}





static uint32 tga_convert_color( uint32 pixel, uint32 bpp_in, ubyte alphabits, uint32 format_out ) {
    
    // this is not only responsible for converting from different depths
    // to other depths, it also switches BGR to RGB.

    // this thing will also premultiply alpha, on a pixel by pixel basis.

    ubyte r, g, b, a;

    switch( bpp_in ) {
        
    case 32:
        if( alphabits == 0 ) {
            goto is_24_bit_in_disguise;
        }
        // 32-bit to 32-bit -- nop.
        break;
        
    case 24:
is_24_bit_in_disguise:
        // 24-bit to 32-bit; (only force alpha to full)
        pixel |= 0xFF000000;
        break;

    case 15:
is_15_bit_in_disguise:
        r = (ubyte)(((float)((pixel & 0x7C00) >> 10)) * 8.2258f);
        g = (ubyte)(((float)((pixel & 0x03E0) >> 5 )) * 8.2258f);
        b = (ubyte)(((float)(pixel & 0x001F)) * 8.2258f);
        // 15-bit to 32-bit; (force alpha to full)
        pixel = 0xFF000000 + (r << 16) + (g << 8) + b;
        break;
        
    case 16:
        if( alphabits == 1 ) {
            goto is_15_bit_in_disguise;
        }
        // 16-bit to 32-bit; (force alpha to full)
        r = (ubyte)(((float)((pixel & 0xF800) >> 11)) * 8.2258f);
        g = (ubyte)(((float)((pixel & 0x07E0) >> 5 )) * 4.0476f);
        b = (ubyte)(((float)(pixel & 0x001F)) * 8.2258f);
        pixel = 0xFF000000 + (r << 16) + (g << 8) + b;
        break;
       
    }
    
    // convert the 32-bit pixel from BGR to RGB.
    pixel = (pixel & 0xFF00FF00) + ((pixel & 0xFF) << 16) + ((pixel & 0xFF0000) >> 16);

    r = (ubyte)(pixel & 0x000000FF);
    g = (ubyte)((pixel & 0x0000FF00) >> 8);
    b = (ubyte)((pixel & 0x00FF0000) >> 16);
    a = (ubyte)((pixel & 0xFF000000) >> 24);

#ifdef multipliedalpha   
    // not premultiplied alpha -- multiply.
    r = (ubyte)(((float)r / 255.0f) * ((float)a / 255.0f) * 255.0f);
    g = (ubyte)(((float)g / 255.0f) * ((float)a / 255.0f) * 255.0f);
    b = (ubyte)(((float)b / 255.0f) * ((float)a / 255.0f) * 255.0f);
#endif

    pixel = r + (g << 8) + (b << 16) + (a << 24);

    /* now convert from 32-bit to whatever they want. */
    
    switch( format_out ) {
        
    case TGA_TRUECOLOR_32:
        // 32 to 32 -- nop.
        break;
        
    case TGA_TRUECOLOR_24:
        // 32 to 24 -- discard alpha.
        pixel &= 0x00FFFFFF;
        break;
        
    }

    return( pixel );

}




static int16 ttohs( int16 val ) {

#ifdef WORDS_BIGENDIAN
    return( ((val & 0xFF) << 8) + (val >> 8) );
#else
    return( val );
#endif 

}


static int16 htots( int16 val ) {

#ifdef WORDS_BIGENDIAN
    return( ((val & 0xFF) << 8) + (val >> 8) );
#else
    return( val );
#endif

}


static int32 ttohl( int32 val ) {

#ifdef WORDS_BIGENDIAN
    return( ((val & 0x000000FF) << 24) +
            ((val & 0x0000FF00) << 8)  +
            ((val & 0x00FF0000) >> 8)  +
            ((val & 0xFF000000) >> 24) );
#else
    return( val );
#endif 

}


static int32 htotl( int32 val ) {

#ifdef WORDS_BIGENDIAN
    return( ((val & 0x000000FF) << 24) +
            ((val & 0x0000FF00) << 8)  +
            ((val & 0x00FF0000) >> 8)  +
            ((val & 0xFF000000) >> 24) );
#else
    return( val );
#endif 

}