/*
** THIS SOFTWARE IS SUBJECT TO COPYRIGHT PROTECTION AND IS OFFERED ONLY
** PURSUANT TO THE 3DFX GLIDE GENERAL PUBLIC LICENSE. THERE IS NO RIGHT
** TO USE THE GLIDE TRADEMARK WITHOUT PRIOR WRITTEN PERMISSION OF 3DFX
** INTERACTIVE, INC. A COPY OF THIS LICENSE MAY BE OBTAINED FROM THE 
** DISTRIBUTOR OR BY CONTACTING 3DFX INTERACTIVE INC(info@3dfx.com). 
** THIS PROGRAM IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER 
** EXPRESSED OR IMPLIED. SEE THE 3DFX GLIDE GENERAL PUBLIC LICENSE FOR A
** FULL TEXT OF THE NON-WARRANTY PROVISIONS.  
** 
** USE, DUPLICATION OR DISCLOSURE BY THE GOVERNMENT IS SUBJECT TO
** RESTRICTIONS AS SET FORTH IN SUBDIVISION (C)(1)(II) OF THE RIGHTS IN
** TECHNICAL DATA AND COMPUTER SOFTWARE CLAUSE AT DFARS 252.227-7013,
** AND/OR IN SIMILAR OR SUCCESSOR CLAUSES IN THE FAR, DOD OR NASA FAR
** SUPPLEMENT. UNPUBLISHED RIGHTS RESERVED UNDER THE COPYRIGHT LAWS OF
** THE UNITED STATES.  
** 
** COPYRIGHT 3DFX INTERACTIVE, INC. 1999, ALL RIGHTS RESERVED
**
** $Revision$
** $Date$
**
*/


#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "3dfx.h"
#include "fximg.h"
#include "fxos.h"

/*
** PRIVATE GLOBAL DATA
*/

char *imgErrorString = "No error.";

/*
** PRIVATE UTILITY FUNCTIONS
*/

/* _fixStream
**
** Summary: UNPUBLISHED - change mode for input / output streams so that
**          they are are in binary mode for dos apps.
*/
#if defined( __DOS__ ) || defined( WIN32 )
/* 
** On dos-compatible platforms, iostreams must be set to binary( untranslated )
** mode
*/
#include <fcntl.h>
#include <io.h>
void _fixStream( FILE *stream )
{
	setmode( fileno( stream ), O_BINARY );
}
#else
/*
** Otherwise do nothing
*/
#define _fixStream( X ) X = X
#endif


/* _imgStrUpr
**
** Summary: UNPUBLISHED - upper case the provided string in place.
**          Provided because strupr isn't provided in all clib implementations.
*/
void _imgStrUpr( char *str )
{
    FxU32 index;
    for ( index = 0; index < strlen( str ); index++ )
    {
		if ( str[index] >= 'a' && str[index] <= 'z' )
		{
	    	str[index] -= 'a' - 'A';
		}
    }
}

/* _imgIsLittleEndian
**
** UNPUBLISHED - returns FXTRUE if the host architecture
**               stores memory in little endian format
*/
FxBool _imgIsLittleEndian( void )
{
    unsigned long x = 0x0000FFFF;
    return( *((char *)&x)?FXTRUE:FXFALSE );
}

/* _imgTxDecodeColorFormat
**
** UNPUBLISHED - returns the integer color format give in a tdf color format
**
*/
imgTxColorFormat _imgTxDecodeColorFormat( char * String )
{
  if(!strcmp(String,"true") ||
     !strcmp(String,"TRUE"))
    return txColorTrue;
  else if(!strcmp(String,"i8") ||
	  !strcmp(String,"I8"))
    return txColorI_8;
  else if(!strcmp(String,"a8") ||
	  !strcmp(String,"A8"))
    return txColorA_8;
  else if(!strcmp(String,"ai44") ||
	  !strcmp(String,"AI44"))
    return txColorAI_44;
  else if(!strcmp(String,"yiq") ||
	  !strcmp(String,"YIQ"))
    return txColorYIQ;
  else if(!strcmp(String,"rgb332") ||
	  !strcmp(String,"RGB332"))
    return txColorRGB_332;
  else if(!strcmp(String,"rgb565") ||
	  !strcmp(String,"RGB565"))
    return txColorRGB_565;
  else if(!strcmp(String,"argb8332") ||
	  !strcmp(String,"ARGB8332"))
    return txColorARGB_8332;
  else if(!strcmp(String,"argb1555") ||
	  !strcmp(String,"ARGB1555"))
    return txColorARGB_1555;
  else if(!strcmp(String,"ayiq8422") ||
	  !strcmp(String,"AYIQ8422"))
    return txColorAYIQ_8422;
  else if(!strcmp(String,"argb4444") ||
	  !strcmp(String,"ARGB4444"))
    return txColorARGB_4444;
  else if(!strcmp(String,"ai88") ||
	  !strcmp(String,"AI88"))
    return txColorAI_88;
  else if(!strcmp(String,"argb8888") ||
	  !strcmp(String,"ARGB8888"))
    return txColorARGB_8888;
  else
    return txColorUnknown;
}

/* _imgMSBReplicate
**
** Summary: UNPUBLISHED - Replicates the msb's of the input value to fill
**                        in an 8-bit color channel;
*/
#define _imgMSBReplicate( COLOR, LEFT_SHIFT, RIGHT_SHIFT ) (COLOR << LEFT_SHIFT) | (COLOR >> RIGHT_SHIFT)

/* _imgGuessType
**
** Summary: UNPUBLISHED - read the first word of an image file stream
**          determine the file type.
*/
FxBool _imgGuessType( FILE *stream, ImgType *type )
{
    FxU32 cookie = 0;
    int   c;
    
    if ( stream == NULL )
    {
		imgErrorString = "Bad File Handle";
		return FXFALSE;
    }
    
    if ( ( c = fgetc( stream ) ) == EOF )
    {
		imgErrorString = "Unexpected end of file";
		return FXFALSE;
    }
    
    cookie = (c << 8);
    
    if ( ( c = fgetc( stream ) ) == EOF )
    {
		imgErrorString = "Unexpected end of file";
		return FXFALSE;
    }
    
    cookie |= c;
    
    switch( cookie )
    {
      	case ('P' << 8) | '9': // SBI
			*type = IMG_SBI;
			break;
      	case ('P' << 8) | '6': // PPM
			*type = IMG_P6;
			break;
      	case ('3' << 8) | 'd': // 3df
      	case ('3' << 8) | 'D': // 3df
			*type = IMG_3DF;
			break;
      	case 0xDA01:
			*type = IMG_RGT;
			break;
      	case 'EL':
	  cookie = 0;
	  if ( ( c = fgetc( stream ) ) == EOF )
	    {
	      imgErrorString = "Unexpected end of file";
	      return FXFALSE;
	    }
	  cookie = (cookie << 8) | c;
	  if ( ( c = fgetc( stream ) ) == EOF )
	    {
	      imgErrorString = "Unexpected end of file";
	      return FXFALSE;
	    }
	  cookie = (cookie << 8) | c;
	  if (cookie == 'RS')
	    *type = IMG_SRLE;
	  break;
      	default: // Might Be TGA
			if ( ( c = fgetc( stream ) ) == EOF )
			{
	    		imgErrorString = "Unexpected end of file";
	    		return FXFALSE;
			}
			cookie <<= 8;
			cookie |= c;
	
			if ( (cookie & 0x00FFFF) == 0x0002 ) *type = IMG_TGA32;
			else *type = IMG_UNKNOWN;
			break;
    }
    return FXTRUE;
}


/* _imgReadSbiHeader
**
** Summary: UNPUBLISHED - read header and fill in header info
*/
FxBool _imgReadSbiHeader( FILE *stream, SbiInfo *info )
{
    char buffer[256];
    FxU32 state = 1;
    FxBool done = FXFALSE;
    
    if ( stream == NULL )
    {
		imgErrorString = "Bad file handle.";
		return FXFALSE;
    }
    
    while( !done && fgets( buffer, 256, stream ) )
    {
		char *token;
	
		if ( buffer[0] == '#' ) continue;
		for (token = strtok( buffer, " \t\n\r" );
			token != NULL;
			token = strtok( NULL, " \t\n\r" ))
		switch( state )
	    	{
	      		case 1: // YOrigin
					if ( token[1] == '+' ) info->yOrigin = 1;
					else if ( token[1] == '-' )	info->yOrigin = 0;
					else
					{
		    			imgErrorString = "Error parsing y-origin.";
		    			return FXFALSE;
					}
					state++;
					break;
	      		case 2:   // Width
					info->width = atoi( token );
					state++;
					break;
	      		case 3: // height
					info->height = atoi( token );
					state++;
					break;
	     	 	case 4: // 'R'
					if ( token[0] != 'R' )
					{
		    			imgErrorString = "Error parsing R color channel.";
		    			return FXFALSE;
					}
					state++;
					break;
	      		case 5: // Red Channel
					info->redBits = atoi( token );
					state++;
					break;
	      		case 6: // 'G'
					if ( token[0] != 'G' )
					{
		    			imgErrorString = "Error parsing G color channel.";
		    			return FXFALSE;
					}
					state++;
					break;
	      		case 7: // Green Channel
					info->greenBits = atoi( token );
					state++;
					break;
	      		case 8: // 'B'
					if ( token[0] != 'B' )
					{
		    			imgErrorString = "Error parsing B color channel.";
		    			return FXFALSE;
					}
					state++;
					break;
	      		case 9: // Blue Channel                             
					info->blueBits = atoi( token );
					done = FXTRUE;
					state++;
					break;
	      		default:
					break;
	    	}
    }
    
    if ( state < 10 )
    {
		imgErrorString = "Read error before end of header.";
		return FXFALSE;
    }
    
    info->sizeInBytes = info->width * info->height * 4;
    
    return FXTRUE;
}

/* _imgReadP6Header
**
** Summary: UNPUBLISHED - read header and fill in header info
*/
FxBool _imgReadP6Header( FILE *stream,  P6Info *info )
{
    char buffer[256];
    FxU32 state = 1;
    FxBool done = FXFALSE;
    
    if ( stream == NULL )
    {
		imgErrorString = "Bad file handle.";
		return FXFALSE;
    }
    
    while( !done && fgets( buffer, 256, stream ) )
    {
		char *token;
	
		if ( buffer[0] == '#' ) continue;
		for (token = strtok( buffer, " \t\n\r" );
			token != NULL;
			token = strtok( NULL, " \t\n\r" ))
	    	switch( state )
	    	{
	     		case 1:   // Width
					info->width = atoi( token );
					state++;
					break;
	      		case 2: // height
					info->height = atoi( token );
					state++;
					break;
	      		case 3: // Color Depth
					info->colorChannelMax = atoi( token );
					if ( info->colorChannelMax != 255 )
					{
		    			imgErrorString = "Unsupported max color value.  Must be 255.";
		    			return FXFALSE;
					}
					state++;
					done = FXTRUE;
					break;
	      		default:
					imgErrorString = "General parse error reading header.";
					return FXFALSE;
					break;
	    	}
    }
    
    if ( state < 4 )
    {
		imgErrorString = "Read error before end of header.";
		return FXFALSE;
    }
    
    info->sizeInBytes = info->width * info->height * 4;
    
    return FXTRUE;
}

/* _imgReadTGAHeader
**
** Summary: UNPUBLISHED - read header and fill in header info
*/
FxBool _imgReadTGAHeader( FILE *stream, TgaInfo *info )
{
    struct {
		FxU8 CMapStartLo;
		FxU8 CMapStartHi;
		FxU8 CMapLengthLo;
		FxU8 CMapLengthHi;
		FxU8 CMapDepth;
		FxU8 XOffSetLo;
		FxU8 XOffSetHi;
		FxU8 YOffSetLo;
		FxU8 YOffSetHi;
		FxU8 WidthLo;
		FxU8 WidthHi;
		FxU8 HeightLo;
		FxU8 HeightHi;
		FxU8 PixelDepth;
		FxU8 ImageDescriptor;
    } partialTGAHeader;
    
    if ( stream == NULL )
    {
		imgErrorString = "Bad file handle.";
		return FXFALSE;
    }
    
    if ( fread( &partialTGAHeader, 1, 15, stream ) != 15 )
    {
	  	imgErrorString = "Unexpected end of file.";
		return FXFALSE;
    }
    
    info->width  = partialTGAHeader.WidthHi << 8 | partialTGAHeader.WidthLo;
    info->height = partialTGAHeader.HeightHi << 8 | partialTGAHeader.HeightLo;
    info->yOrigin = 0x01 & partialTGAHeader.ImageDescriptor;
    info->sizeInBytes = info->width * info->height * 4;
    
    return FXTRUE;
}

/* _imgWriteTGAHeader
**
** Summary: UNPUBLISHED - write header
*/
FxBool _imgWriteTGAHeader( FILE *stream, const TgaInfo *info ) {

    struct {
		FxU8 IDLength;
		FxU8 ColorMapType;
		FxU8 ImgType;
		FxU8 CMapStartLo;
		FxU8 CMapStartHi;
		FxU8 CMapLengthLo;
		FxU8 CMapLengthHi;
		FxU8 CMapDepth;
		FxU8 XOffSetLo;
		FxU8 XOffSetHi;
		FxU8 YOffSetLo;
		FxU8 YOffSetHi;
		FxU8 WidthLo;
		FxU8 WidthHi;
		FxU8 HeightLo;
		FxU8 HeightHi;
		FxU8 PixelDepth;
		FxU8 ImageDescriptor;
    } tgaHeader;
    
    if ( stream == NULL )
    {
		imgErrorString = "Bad file handle.";
		return FXFALSE;
    }
	
	tgaHeader.IDLength         = 0;
	tgaHeader.ColorMapType     = 0;
	tgaHeader.ImgType          = 0x2;
	tgaHeader.CMapStartLo      = 0;
	tgaHeader.CMapStartHi      = 0;
	tgaHeader.CMapLengthLo     = 0;
	tgaHeader.CMapLengthHi     = 0;
	tgaHeader.CMapDepth        = 0;
	tgaHeader.XOffSetLo        = 0;
	tgaHeader.XOffSetHi        = 0;
	tgaHeader.YOffSetLo        = 0;
	tgaHeader.YOffSetHi        = 0;
	tgaHeader.WidthHi          = (FxU8)(info->width >> 8) & 0xFF;
	tgaHeader.WidthLo          = (FxU8)info->width & 0xFF;
	tgaHeader.HeightHi         = (FxU8)(info->height >> 8) & 0xFF;
	tgaHeader.HeightLo         = (FxU8)info->height & 0xFF;
	tgaHeader.PixelDepth       = 32;
	tgaHeader.ImageDescriptor  = info->yOrigin ? ( 0x1 ) : ( 0x0 );	
	    

	if ( fwrite( &tgaHeader, 1, 18, stream ) != 18 ) {
		imgErrorString = "TGA Header stream write error.";
		return FXFALSE;
	}

	return FXTRUE;
}
    
/* _imgReadRGTHeader
**
** Summary: UNPUBLISHED - read header and fill in header info
*/
FxBool _imgReadRGTHeader( FILE *stream, RgtInfo *info )
{
    const FxU16 ITYPE_RLE      = 0x01;
    const FxU16 ITYPE_NCC      = 0x02;
    const FxU16 ITYPE_BGR      = 0x04;
    const FxU16 ITYPE_RGT      = 0x08;
    
    struct {
		FxU8 typeLo;
		FxU8 typeHi;
		FxU8 dimLo;
		FxU8 dimHi;
		FxU8 sizeXLo;
		FxU8 sizeXHi;
		FxU8 sizeYLo;
		FxU8 sizeYHi;
		FxU8 sizeZLo;
		FxU8 sizeZHigh;
    } partialRGTHeader;
    
    if ( stream == NULL )
    {
		imgErrorString = "Bad file handle.";
		return FXFALSE;
    }
    
    if ( fread( &partialRGTHeader, 1, 10, stream ) != 10 )
    {
		imgErrorString = "Unexpected end of file.";
		return FXFALSE;
    }
    
    info->width = partialRGTHeader.sizeXHi << 8 | partialRGTHeader.sizeXLo;
    info->height = partialRGTHeader.sizeYHi << 8 | partialRGTHeader.sizeYLo;
    info->sizeInBytes = info->width * info->height * 4;
    info->rle = partialRGTHeader.typeHi & ITYPE_RLE;
    info->bgr = partialRGTHeader.typeHi & ITYPE_BGR;
    info->rgb = partialRGTHeader.typeHi & ITYPE_RGT;
    info->ncc = partialRGTHeader.typeHi & ITYPE_NCC;
    
    return FXTRUE;
}

/* _imgRead3DFHeader
**
** Summary: UNPUBLISHED - read header and fill in header info
*/
FxBool _imgRead3DFHeader( FILE *stream, TdfInfo *info ) {
	FxBool done = FXFALSE;
    char buffer[256];
	FxU32 state = 0;
	float tempFloat;

    if ( stream == NULL ) {
		imgErrorString = "Bad file handle.";
		return FXFALSE;
    }

    while( !done && fgets( buffer, 256, stream ) ) {
		char *token;
	
		if ( buffer[0] == '#' ) continue;
		for (token = strtok( buffer, " \t\n\r" );
			token != NULL;
			token = strtok( NULL, " \t\n\r" ))
	    	switch( state )	{
				case  0:   // 'f'
					if ( token[0] != 'f' ) {
						imgErrorString = "Bad cookie( \"3df\" ).";
						return FXFALSE;
					}
					state++;
					break;
				case  1:   // Version
					if ( sscanf( token, 
							     "v%f", 
							     &tempFloat ) == 0 ) {	
						imgErrorString = "Couldn't determine version of 3DF file.";
						return FXFALSE;
					}
					info->version = tempFloat;
					state++;
					break;
				case  2:  // Color Format
					if ( (info->colorFormat = _imgTxDecodeColorFormat( token ))==txColorUnknown ) {
						imgErrorString = "3DF Unknown color format.";
						return FXFALSE;
					} 
					state++;
					break;
				case  3:  // "lod"
					if ( strcmp( token, "lod" ) ) {
						imgErrorString = "Bad lod range identifier.";
						return FXFALSE;
					}
					state++;
					break;
				case  4:  // "range:"
					if ( strcmp( token, "range:" ) ) {
						imgErrorString = "Bad lod range identifier.";
						return FXFALSE;
					}
					state++;
					break;
				case  5:  // lodMin
					info->lodMin = atoi( token );
					state++;
					break;
				case  6:  // lodMax
					info->lodMax = atoi( token );
					state++;
					break;
				case  7:  // "aspect"
					if ( strcmp( token , "aspect" ) ) {	
						imgErrorString = "Bad aspect ratio identifier.";
						return FXFALSE;
					}
					state++;
					break;
				case  8:  // "ratio:"
					if ( strcmp( token, "ratio:" ) ) {	
						imgErrorString = "Bad aspect ratio identifier.";
						return FXFALSE;
					}
					state++;
					break;
				case  9:  // aspectWidth
					info->aspectWidth = atoi( token );
					state++;
					break;
				case 10:  // aspectHeight
					info->aspectHeight = atoi( token );
					done = FXTRUE;
					break;
				default:
					imgErrorString = "General parse error reading header.";
					return FXFALSE;
					break;
	    	}
    }
    
    if ( state != 10 ) {
		imgErrorString = "Read error before end of header.";
		return FXFALSE;
    }

	if ( info->lodMin > info->lodMax ) {
		imgErrorString = "3DF Format Error.  lodMin must be <= lodMax.";
		return FXFALSE;
	}

	// Calculate Width and Height with All Mipmaps
	if ( info->aspectWidth < info->aspectHeight ) {
		FxU32 lod;
		info->height = info->lodMax;
		info->width = info->lodMax / info->aspectHeight;
		for ( lod = info->lodMax / 2; lod >= info->lodMin; lod /= 2 ) {
			info->width += lod > 1 ? lod / info->aspectHeight : 1;
		}
	} else {
		FxU32 lod;
		info->width = info->lodMax;
		info->height = info->lodMax / info->aspectWidth;
		for ( lod = info->lodMax / 2; lod >= info->lodMin; lod /= 2 ) {
			info->height += lod > 1 ? lod / info->aspectWidth : 1;
		}
	}

	info->sizeInBytes = info->width * info->height * 4;

    return FXTRUE;
}


/* _imgRead3DFData
**
** Summary: UNPUBLISHED - read data from a 3DF file.
**          when the texuslib has an owner, and it
**          is reliably maintained, this will be
**          converted over to calls to the library.
*/
/* Read in data from a 3DF file. */
FxBool _imgRead3DFData( FILE *stream, const TdfInfo *info, ImgData *data )
{
	FxBool doVerticalDisplay;
	ImgData *destPtr, *destOrigin;
	FxU32 xBound, yBound;
	FxU32 lod;

	struct { 
		FxU8  yRGB[16];
		FxI16 iRGB[4][3];
		FxI16 qRGB[4][3];
	} imgYIQTable;

	if ( stream == NULL ) {
		imgErrorString = "Bad file handle.";
		return FXFALSE;
	}  

  	/* Read in the YIQ decompression table, if necessary. */
  	if( (info->colorFormat == txColorYIQ) || (info->colorFormat == txColorAYIQ_8422) ) {
		FxU32 index;
		for( index=0; index < 16; index++ ) {
            getc( stream );
			imgYIQTable.yRGB[index] = (FxU8) getc( stream );
		}
		for( index=0; index < 4;index++ ) {	
			FxI16 msb = (FxI16) ( getc( stream ) & 0xFF ) << 8;
			FxI16 lsb = (FxI16) ( getc( stream ) & 0xFF );
			imgYIQTable.iRGB[index][0] = msb | lsb;
			msb = (FxI16) ( getc( stream ) & 0xFF ) << 8;
			lsb = (FxI16) ( getc( stream ) & 0xFF );
			imgYIQTable.iRGB[index][1] = msb | lsb;
			msb = (FxI16) ( getc( stream ) & 0xFF ) << 8;
			lsb = (FxI16) ( getc( stream ) & 0xFF );
			imgYIQTable.iRGB[index][2] = msb | lsb;
		}
		for( index=0; index < 4;index++ ) {	
			FxI16 msb = (FxI16) ( getc( stream ) & 0xFF ) << 8;
			FxI16 lsb = (FxI16) ( getc( stream ) & 0xFF );
			imgYIQTable.qRGB[index][0] = msb | lsb;
			msb = (FxI16) ( getc( stream ) & 0xFF ) << 8;
			lsb = (FxI16) ( getc( stream ) & 0xFF );
			imgYIQTable.qRGB[index][1] = msb | lsb;
			msb = (FxI16) ( getc( stream ) & 0xFF ) << 8;
			lsb = (FxI16) ( getc( stream ) & 0xFF );
			imgYIQTable.qRGB[index][2] = msb | lsb;
		}
		if ( feof( stream ) ) {
			imgErrorString = "Unexpected end of file reading YIQ Table.";
			return FXFALSE;
		}
	}

	/* Read in the Image One mipmap level at a time */

	doVerticalDisplay = (info->aspectHeight <= info->aspectWidth ) ? FXTRUE : FXFALSE;

	destPtr = destOrigin = data;
	if ( doVerticalDisplay ) {
		yBound = info->lodMax / info->aspectWidth;
		xBound = info->lodMax;	
	} else { 
		yBound = info->lodMax;
		xBound = info->lodMax / info->aspectHeight;
	}

	for ( lod = info->lodMax; lod >= info->lodMin; lod /= 2 )	{
		FxU32 x, y;
		switch( info->colorFormat )	{
			case txColorTrue:        /* True color, 24-bit color (RGB) + 8-bit alpha.               */
			case txColorARGB_8888:   /* 8-bits of Alpha, Red, Green, and Blue.  Not yet supported.  */
				for ( y = 0; y < yBound; y++ ) {
					for( x = 0; x < xBound; x++ ) {
						FxU8 r, g, b, a;
						a = getc( stream );
						r = getc( stream );
						g = getc( stream );
						b = getc( stream );
						*destPtr++ = b;
						*destPtr++ = g;
						*destPtr++ = r;
						*destPtr++ = a;
					}
					destPtr += (info->width - xBound) * 4;
				}
				break;
			case txColorI_8:         /* 8-bit intensity.                                            */
	        		                 /* For this, an intensity is calculated by averaging the       */
	                		         /* RG and B channels, and placed back into all three channels  */
			                         /* so that outputs of TGA's and PPM's will look okay.          */
				for ( y = 0; y < yBound; y++ ) {
					for ( x = 0; x < xBound; x++ ) {
						FxU8 intensity;
						intensity = getc( stream );
						*destPtr++ = intensity;
						*destPtr++ = intensity;
						*destPtr++ = intensity;
						*destPtr++ = intensity;
					}
					destPtr += (info->width - xBound) * 4;
				}
				break;
			case txColorA_8: /* 8-bit alpha. */
				for ( y = 0; y < yBound; y++ ) {
					for ( x = 0; x < xBound; x++ ) {
						FxU8 alpha;
						alpha = getc( stream );
						*destPtr++ = alpha;
						*destPtr++ = alpha;
						*destPtr++ = alpha;
						*destPtr++ = alpha;
					}
					destPtr += (info->width - xBound) * 4;
				}
				break;
			case txColorARGB_1555:
				for ( y = 0; y < yBound; y++ ) {
					for ( x = 0; x < xBound; x++ ) {
						FxU16 color;
						FxU8  a,r,g,b;
						color = ( 0xFF & getc( stream ) ) << 8;
						color |= ( 0xFF & getc( stream ) );
						a = ( color >> 15 ) & 0x01;
						r = ( color >> 10 ) & 0x1F;
						g = ( color >> 5  ) & 0x1F;
						b =   color         & 0x1F;

						*destPtr++ = _imgMSBReplicate( b, 3, 2);
						*destPtr++ = _imgMSBReplicate( g, 3, 2);
  						*destPtr++ = _imgMSBReplicate( r, 3, 2);
						*destPtr++ = a ? 0x0 : 0xFF;
					}
					destPtr += (info->width - xBound) * 4;
				}
				break;
			case txColorARGB_4444:
				for ( y = 0; y < yBound; y++ ) {
					for ( x = 0; x < xBound; x++ ) {
						FxU16 color;
						FxU8  a,r,g,b;
						color = ( 0xFF & getc( stream ) ) << 8;
						color |= ( 0xFF & getc( stream ) );
						a = ( color >> 12 ) & 0x0F;
						r = ( color >> 8  ) & 0x0F;
						g = ( color >> 4  ) & 0x0F;
						b = ( color >> 0  ) & 0x0F;

						*destPtr++ = _imgMSBReplicate( b, 4, 0 );
						*destPtr++ = _imgMSBReplicate( g, 4, 0 );
  						*destPtr++ = _imgMSBReplicate( r, 4, 0 );
						*destPtr++ = _imgMSBReplicate( a, 4, 0 );
					}
					destPtr += (info->width - xBound) * 4;
				}
				break;
			case txColorRGB_565:
				for ( y = 0; y < yBound; y++ ) {
					for ( x = 0; x < xBound; x++ ) {
						FxU16 color;
						FxU8  r,g,b;
						color = ( 0xFF & getc( stream ) ) << 8;
						color |= ( 0xFF & getc( stream ) );
						r = ( color >> 11 ) & 0x1F;
						g = ( color >> 5 ) &  0x3F;
						b = ( color >> 0 ) &  0x1F;

						*destPtr++ = _imgMSBReplicate( b, 3, 2 );
						*destPtr++ = _imgMSBReplicate( g, 2, 4);
  						*destPtr++ = _imgMSBReplicate( r, 3, 2);
						*destPtr++ = 0x0; // Alpha
					}
					destPtr += (info->width - xBound) * 4;
				}
				break;
			case txColorAI_44:
			default:
				imgErrorString = "Unhandled 3df color format.";
				return FXFALSE;	
		}
		if ( doVerticalDisplay ) { 
			destPtr = destOrigin + ( yBound * info->width * 4 );
		} else {
			destPtr = destOrigin + (xBound * 4);
		}
		destOrigin = destPtr;
		if ( yBound > 1 ) yBound /= 2;
		if ( xBound > 1 ) xBound /= 2;
	}
	
#if 0
	case txColorAI_44:       /* 4-bit alpha, 4-bit intensity.                               */
	                         /* For this conversion, the alpha is shredded to four bits,    */
                                 /* the intensities are averaged and then shredded into four    */
                                 /* bits, and then the intensity is placed into all three       */
                                 /* color channels.                                             */
	  /* Loop through the entire image. */
	  for(index=0;index<((Image->Width)*(Image->Height));index++)
	    {
	      /* Read in both alpha and intensity. */
	      fread(&tmpChar,1,1,ImageFile);
	      tmpColor = tmpChar;
	      
	      /* Extract the alpha portion. */
	      Image->AlphaChannel[index] = (tmpColor & 0xf0);

	      /* Extract the color portion. */
	      Image->RedChannel[index] = 
	      Image->GreenChannel[index] = 
	      Image->BlueChannel[index] = ((tmpColor & 0x0f)<<4);
	    }
	  
	  break;

	case txColorYIQ:         /* Narrow-channel, compressed color.                           */
	  /* Get a copy of the YIQ table so that it doesn't have to be dereferenced
	     every single time it's used. */
	  YIQTable = Image->YIQTable;
	  
	  /* Loop through the entire image. */
	  for(index=0;index<((Image->Width)*(Image->Height));index++)
	    {
	      /* Get the YIQ pixel into a temporary value. */
	      fread(&tmpChar,1,1,ImageFile);
	      tmpColor = (tmpChar & 0xff);
	      
	      /* Rip out YIQ from that pixel. */
	      y = tmpColor >> 4;
	      i = (tmpColor >> 2) & 0x3;
	      q = (tmpColor >> 0) & 0x3;

	      /* Convert the colors back out. */
	      r = YIQTable.yRGB[y] + YIQTable.iRGB[i][0] + YIQTable.qRGB[q][0];
	      g = YIQTable.yRGB[y] + YIQTable.iRGB[i][1] + YIQTable.qRGB[q][1];
	      b = YIQTable.yRGB[y] + YIQTable.iRGB[i][2] + YIQTable.qRGB[q][2];

	      /* Clamp the values. */
	      if( r < 0 ) r = 0; else if ( r > 0xff ) r = 0xff;
	      if( g < 0 ) g = 0; else if ( g > 0xff ) g = 0xff;
	      if( b < 0 ) b = 0; else if ( b > 0xff ) b = 0xff;

	      /* Stick the values into the image structure. */
	      Image->RedChannel[index] = (unsigned char)r;
	      Image->GreenChannel[index] = (unsigned char)g;
	      Image->BlueChannel[index] = (unsigned char)b;
	      
	      /* Make up something for alpha. */
	      Image->AlphaChannel[index] = 0xff;
	    }

	  break;

	case txColorRGB_332:     /* 3-bit red, 3-bit green, 2-bit blue.                         */
                                 /* Shred bits off of the color channels, and tip the alpha     */
                                 /* channel up to 0xff.                                         */
	  /* Loop through the entire image. */
	  for(index=0;index<((Image->Width)*(Image->Height));index++)
	    {
	      /* Combine the whole color into one byte. */
	      fread(&tmpChar,1,1,ImageFile);

	      /* Rip the byte apart into colors. */
	      Image->RedChannel[index] = (tmpChar & 0xe0);
	      Image->GreenChannel[index] = (tmpChar & 0x1c)<<3;
	      Image->BlueChannel[index] = (tmpChar & 0x03)<<6;

	      /* Make up something for alpha. */
	      Image->AlphaChannel[index] = 0xff;
	    }
	  
	  break;
	case txColorRGB_565:     /* 5-bit red, 6-bit green, 5-bit blue.                         */
                                 /* Shred bits off of the color channels, and tip the alpha     */
                                 /* channel up to 0xff.                                         */
	  /* Loop through the entire image. */
	  for(index=0;index<((Image->Width)*(Image->Height));index++)
	    {
	      /* Read both bytes into a tmp color. */
	      fread(&tmpChar,1,1,ImageFile);
	      tmpColor = (tmpChar & 0xff)<<8;
	      fread(&tmpChar,1,1,ImageFile);
	      tmpColor |= (tmpChar & 0xff);
	  
	      /* Rip the short apart into colors. */
	      Image->RedChannel[index] = (tmpColor & 0xf800)>>8;
	      Image->GreenChannel[index] = (tmpColor & 0x07e0)>>3;
	      Image->BlueChannel[index] = (tmpColor & 0x001f)<<3;
	      
	      /* Make up something for alpha. */
	      Image->AlphaChannel[index] = 0xff;
	    }
	  
	  break;
	case txColorARGB_8332:   /* 8-bit alpha, 3-bit red, 3-bit green, 2-bit blue.            */
                                 /* Nothing but shredding.                                      */
	  /* Loop through the entire image. */
	  for(index=0;index<((Image->Width)*(Image->Height));index++)
	    {
	      /* Read both bytes into a tmp color. */
	      fread(&tmpChar,1,1,ImageFile);
	      tmpColor = (tmpChar & 0xff)<<8;
	      fread(&tmpChar,1,1,ImageFile);
	      tmpColor |= (tmpChar & 0xff);
	      
	      /* Read out the alpha. */
	      Image->AlphaChannel[index] = ((tmpColor & 0xff00)>>8);

	      /* Rip the byte apart into colors. */
	      Image->RedChannel[index] = (tmpColor & 0xe0);
	      Image->GreenChannel[index] = (tmpColor & 0x1c)<<3;
	      Image->BlueChannel[index] = (tmpColor & 0x03)<<6;
	    }
	  
	  break;
	case txColorARGB_1555:   /* 1-bit alpha, 5-bit red, 5-bit green, 5-bit blue.            */
	                         /* Nothing but shredding.                                      */
	  /* Loop through the entire image. */
	  for(index=0;index<((Image->Width)*(Image->Height));index++)
	    {
	      /* Read both bytes into a tmp color. */
	      fread(&tmpChar,1,1,ImageFile);
	      tmpColor = (tmpChar & 0xff)<<8;
	      fread(&tmpChar,1,1,ImageFile);
	      tmpColor |= (tmpChar & 0xff);

	      /* Rip the color apart. */
	      Image->AlphaChannel[index] = (tmpColor & 0x8000)>>8;
	      Image->RedChannel[index] = (tmpColor & 0x7c00)>>7;
	      Image->GreenChannel[index] = (tmpColor & 0x03e0)>>2;
	      Image->BlueChannel[index] = (tmpColor & 0x001f)<<3;
	    }
	  
	  break;

	case txColorAYIQ_8422:   /* 8-bit alpha + narrow-channel compressed color.              */
	  /* Get a copy of the YIQ table so that it doesn't have to be dereferenced
	     every single time it's used. */
	  YIQTable = Image->YIQTable;
	  
	  /* Loop through the entire image. */
	  for(index=0;index<((Image->Width)*(Image->Height));index++)
	    {
	      /* Get alpha from the first byte. */
	      fread(&tmpChar,1,1,ImageFile);
	      (Image->AlphaChannel[index]) = (unsigned char)tmpChar;
	      
	      /* Get the YIQ pixel into a temporary value. */
	      fread(&tmpChar,1,1,ImageFile);
	      tmpColor = (tmpChar & 0xff);

	      /* Rip out YIQ from that pixel. */
	      y = tmpColor >> 4;
	      i = (tmpColor >> 2) & 0x3;
	      q = (tmpColor >> 0) & 0x3;

	      /* Convert the colors back out. */
	      r = YIQTable.yRGB[y] + YIQTable.iRGB[i][0] + YIQTable.qRGB[q][0];
	      g = YIQTable.yRGB[y] + YIQTable.iRGB[i][1] + YIQTable.qRGB[q][1];
	      b = YIQTable.yRGB[y] + YIQTable.iRGB[i][2] + YIQTable.qRGB[q][2];

	      /* Clamp the values. */
	      if( r < 0 ) r = 0; else if ( r > 0xff ) r = 0xff;
	      if( g < 0 ) g = 0; else if ( g > 0xff ) g = 0xff;
	      if( b < 0 ) b = 0; else if ( b > 0xff ) b = 0xff;

	      /* Stick the values into the image structure. */
	      Image->RedChannel[index] = (unsigned char)r;
	      Image->GreenChannel[index] = (unsigned char)g;
	      Image->BlueChannel[index] = (unsigned char)b;
	      
	      /* Make up something for alpha. */
	      Image->AlphaChannel[index] = 0xff;
	    }

	  break;
	case txColorARGB_4444:   /* 4-bit alpha, 4-bit red, 4-bit green, 4-bit blue.            */
	                         /* Nothing but shredding.                                      */
	  /* Loop through the entire image. */
	  for(index=0;index<((Image->Width)*(Image->Height));index++)
	    {
	      /* Read both bytes into a tmp color. */
	      fread(&tmpChar,1,1,ImageFile);
	      tmpColor = (tmpChar & 0xff)<<8;
	      fread(&tmpChar,1,1,ImageFile);
	      tmpColor |= (tmpChar & 0xff);

	      /* Rip that color apart. */
	      Image->AlphaChannel[index] = (tmpColor & 0xf000)>>8;
	      Image->RedChannel[index] = (tmpColor & 0x0f00)>>4;
	      Image->GreenChannel[index] = (tmpColor & 0x00f0);
	      Image->BlueChannel[index] = (tmpColor & 0x000f)<<4;
	    }
	  
	  break;
	case txColorAI_88:       /* 8-bit alpha, 8-bit itensity.                                */
	                         /* Same as txColorAI_44, minus the shredding.                  */
	  /* Loop through the entire image. */
	  for(index=0;index<((Image->Width)*(Image->Height));index++)
	    {
	      /* Grab alpha from the first byte. */
	      fread(&tmpChar,1,1,ImageFile);
	      (Image->AlphaChannel[index]) = (tmpChar & 0xff);
	      
	      /* And grab the intensity from the second byte. */
	      fread(&tmpChar,1,1,ImageFile);
	      Image->RedChannel[index] =
	      Image->GreenChannel[index] =
	      Image->BlueChannel[index] = (tmpChar & 0xff);
	    }
	  
	  break;
	}
    }
  
  /* Close the file. */
  fclose(ImageFile);
#endif

  return FXTRUE;
}



/* _imgReadTGAData
**
** Summary: UNPUBLISHED - read data from a TGA file
** TGA is BGRA
*/
FxBool _imgReadTGAData( FILE *stream, const TgaInfo *info, ImgData *data )
{
	FxI32 y, y0, y1, dy, scanlineLength;
	const int bytesPerPixel = 4;

    if ( stream == NULL )
    {
		imgErrorString = "Bad file handle.";
		return FXFALSE;
    }

	if ( info->height == 0 ) return FXTRUE;

	if ( info->yOrigin == 0 )
	{
		dy = -1;
		y0 = info->height - 1;
		y1 = -1;
	}
	else
	{
		dy = 1;
		y0 = 0;
		y1 = info->height;
	}

	scanlineLength = info->width * bytesPerPixel;

	for ( y = y0; y != y1; y += dy )
	{
		if ( fread( data + scanlineLength * y, 1, scanlineLength, stream ) !=
			 (FxU32) scanlineLength )
		{
			imgErrorString = "Unexpected end of file";
			return FXFALSE;
		}
	}
    
    return FXTRUE;
}



/* _imgWriteTGAData
**
** Summary: UNPUBLISHED - write data out to a tga
** TGA is BGRA
*/
FxBool _imgWriteTGAData( FILE *stream, const TgaInfo *info, const ImgData *data )
{
	FxI32 y, scanlineLength;
	const int bytesPerPixel = 4;

    if ( stream == NULL )
    {
		imgErrorString = "Bad file handle.";
		return FXFALSE;
    }

	scanlineLength = info->width * bytesPerPixel;

	for ( y = info->height - 1; y >= 0; y-- )
	{
		if ( fwrite( data + scanlineLength * y, 1, scanlineLength, stream ) !=
			 (FxU32) scanlineLength )
		{
			imgErrorString = "TGA stream write error.";
			return FXFALSE;
		}
	}
    
    return FXTRUE;
}

/* _imgReadRGTData
**
** Summary: UNPUBLISHED - read data from an RGT file ( rgt's are bottom up )
** RGT is RGBA in memory (low byte to high byte), or ABGR in a register
*/
FxBool _imgReadRGTData( FILE *stream, const RgtInfo *info, ImgData *data )
{
	FxU32 scanlineSize = info->width * 4;
	FxU32 x,y;
    
    if ( stream == NULL ) {
		imgErrorString = "Bad file handle.";
		return FXFALSE;
    }

	if ( info->ncc != 0 ) {	
		imgErrorString = "Compressed RGT's not yet supported.";
		return FXFALSE;
	}

	// load rgt, rgt's are bottom up
	for ( y = 0; y < info->height; y++ ) {
		ImgData *scanlineBuffer = data + scanlineSize * ( (info->height-1) - y );
		if ( fread( scanlineBuffer, 1, scanlineSize, stream ) != scanlineSize )	{
			imgErrorString = "Unexpected end of file.";
			return FXFALSE;
		}
	}

	if ( info->rgb ) {
		fprintf(stderr," (RGB->BGR)"); fflush(stderr);
		for ( y = 0; y < info->height; y++ ) {
			for ( x = 0; x < info->width; x++ ) {
				ImgData swap;
				swap = data[2];
				data[2] = data[0];
				data[0] = swap;
				data += 4;
			}
		}

	}

    return FXTRUE;
}

/* _imgReadSbiData
**
** Summary: UNPUBLISHED - read data from an SBI file
*/
FxBool _imgReadSbiData( FILE *stream, const SbiInfo *info, ImgData *data )
{
    FxI32 y, y0, y1, dy;
    FxU32 x;
    FxU32 rShift, gShift, bShift, rMask, gMask, bMask;
	FxU32 rRightShift, rLeftShift;
	FxU32 gRightShift, gLeftShift;
	FxU32 bRightShift, bLeftShift;
	FxU32 scanlineWidth;
	ImgData *baseOfData;
    
    if ( stream == NULL )
    {
		imgErrorString = "Bad file handle.";
		return FXFALSE;
    }
    
    bShift = 0;
    gShift = info->blueBits;
    rShift = gShift + info->greenBits;

    rMask = (FxU32) ~0;
    rMask >>= sizeof( FxU32 ) * 8 - info->redBits;
	rLeftShift = 8 - info->redBits;
	rRightShift = info->redBits - rLeftShift;
    
    gMask = (FxU32) ~0;
    gMask >>= sizeof( FxU32 ) * 8 - info->greenBits;
	gLeftShift = 8 - info->greenBits;
	gRightShift = info->greenBits - gLeftShift;
    
    bMask = (FxU32) ~0;
    bMask >>= sizeof( FxU32 ) * 8 - info->blueBits;
	bLeftShift = 8 - info->blueBits;
	bRightShift = info->blueBits - bLeftShift;

	scanlineWidth = info->width * 4;
	baseOfData = data;

    if ( info->yOrigin == 0 )
    {
		y0 = info->height - 1;
		y1 = -1;
		dy = -1;
    }
    else
    {
		y0 = 0;
		y1 = info->height;
		dy = 1;
    }
    
    for( y = y0; y != y1; y += dy )
    {
		//point data at apropriate scanline
		data = baseOfData + y * scanlineWidth;

		for ( x = 0; x < info->width; x++ )
		{
	    	int r, g, b;
	    	int inchar;
	    	FxU32 color;
	    
	    	color = getc( stream );
	    	if ( (inchar = getc( stream ))==EOF )
	    	{
				imgErrorString = "Unexpected end of file.";
				return FXFALSE;
	    	}
	    	color |= (inchar << 8);
	    
	    	// Get sig bits
	    	r = (color >> rShift ) & rMask;
	    	g = (color >> gShift ) & gMask;
	    	b = (color >> bShift ) & bMask;
	    
	    	// Expand to 8 bits/color
			// Current implementation is only meaningful if 
			// using more than 4Bits/Channel color precision.....
			// Needs to be fixed, but this is much, much faster....
	    	*data++ = _imgMSBReplicate( b, bLeftShift, bRightShift );
	    	*data++ = _imgMSBReplicate( g, gLeftShift, gRightShift );
	    	*data++ = _imgMSBReplicate( r, rLeftShift, rRightShift );
	    	data++; // Blank A
		}
    }
    return FXTRUE;
}

/* _imgReadP6Data
**
** Summary: UNPUBLISHED - read data from a PPM
** PPM is RGB
** Memory format is B - G - R - A
*/
FxBool _imgReadP6Data( FILE *stream, const P6Info *info, ImgData *data )
{
    FxU32 numPixels;
    
    numPixels = info->width * info->height;

    if ( stream == NULL )
    {
		imgErrorString = "Bad file handle.";
		return FXFALSE;
    }
    
	// Read in image data
    for( ;numPixels;numPixels-- )
    {
		int r, g, b;
		r = getc( stream );
		g = getc( stream );
		b = getc( stream );
		if ( b == EOF )
		{
	    	imgErrorString = "Unexpected end of file.";
	    	return FXFALSE;
		}
		*data++ = (ImgData)b;
		*data++ = (ImgData)g;
		*data++ = (ImgData)r;
		data++; // A
    }

    return FXTRUE;
}

/* _imgReadSRLEHeader
**
** Summary: UNPUBLISHED - read header and fill in header info
** The simple rle format is intended for glide test programs image comparison
** The file format is as follows:
** 4 bytes file signature
** 2 bytes image width
** 2 bytes image height
** 1 byte  image depth
** 1 byte  compression method (1 for rle compression)
** image data
**
** The image data started with a control byte and followed by pixel data
** Each control byte has two parts. Bit 7 (msb) equals to 1 indicates a run,
** 0 indicates a literal string. Bit 6 - 0 id the counter. It is zero based.
*/
FxBool _imgReadSRLEHeader( FILE *stream,  SrleInfo *info )
{
    FxU16 tmp16;
    FxU8  tmp8;
    
    if ( stream == NULL )
    {
      imgErrorString = "Bad file handle.";
      return FXFALSE;
    }
    
    fread(&tmp16, sizeof(FxU16), 1, stream);
    info->width = tmp16;
    fread(&tmp16, sizeof(FxU16), 1, stream);
    info->height = tmp16;
    fread(&tmp8, sizeof(FxU8), 1, stream);
    info->pixelsize = tmp8 / 8;
    fread(&tmp8 , sizeof(FxU8), 1, stream);
    info->c_type = tmp8;
    
    info->sizeInBytes = info->width * info->height * 4;
    
    return FXTRUE;
}

/* _imgReadSRLE
**
** Summary: UNPUBLISHED - read data from a simple rle format
*/
FxBool _imgReadSRLEData( FILE *stream, const SrleInfo *info, ImgData *data )
{
    int   numPixels;
    FxU16 color;
    FxU8  r,g,b,a;
    FxU8  ctl, repeat, run;
    FxBool flag;
    
    numPixels = info->width * info->height;

    if ( stream == NULL )
    {
      imgErrorString = "Bad file handle.";
      return FXFALSE;
    }

    while (numPixels) {
      ctl = getc( stream );
      flag = ctl & 0x80;
      run = repeat = (ctl & 0x7f) + 1;
      numPixels -= repeat;
      while (repeat) {
	switch (info->pixelsize) 
	  {
	  case 2:
	    if (((flag) && (repeat == run)) || (!flag))
	      fread(&color,2,1,stream);
	    r = ( color >> 11 ) & 0x1F;
	    g = ( color >> 5 ) &  0x3F;
	    b = ( color >> 0 ) &  0x1F;

	    *data++ = _imgMSBReplicate( b, 3, 2 );
	    *data++ = _imgMSBReplicate( g, 2, 4);
	    *data++ = _imgMSBReplicate( r, 3, 2);
	    *data++ = 0x0;
	    break;
	  case 4:
	    if (((flag) && (repeat == run)) || (!flag)) {
	      a = getc( stream );
	      r = getc( stream );
	      g = getc( stream );
	      b = getc( stream );
	    }
	    *data++ = b;
	    *data++ = g;
	    *data++ = r;
	    *data++ = a;
	    break;
	  }
	repeat--;
      }
      if (numPixels < 0)
	return FXFALSE;
    }
    return FXTRUE;
}

/* _imgWriteP6Header
**
** Summary: UNPUBLISHED - write out formatted header for a P6
*/
FxBool _imgWriteP6Header( FILE *stream, const P6Info *info )
{
	imgErrorString = "Image write error.";
	if ( 0 > fprintf( stream, "P6\n" ) ) return FXFALSE;
	if ( 0 > fprintf( stream, "# PPM Comment\n", info->width ) ) return FXFALSE;
	if ( 0 > fprintf( stream, "%d ", info->width ) ) return FXFALSE;
	if ( 0 > fprintf( stream, "%d\n", info->height ) ) return FXFALSE;
	if ( 0 > fprintf( stream, "255\n" ) ) return FXFALSE;
	imgErrorString = "No error.";
	return FXTRUE;
}

/* _imgWriteSbiHeader
**
** Summary: UNPUBLISHED - write out formatted header for an SBI
*/
FxBool _imgWriteSbiHeader( FILE *stream, const SbiInfo *info )
{
	imgErrorString = "Image write error.";
	if ( 0 > fprintf( stream, "P9\n" ) ) return FXFALSE;
	if ( 0 > !fprintf( stream, "Y%c\n", info->yOrigin? '+' : '-' ) ) return FXFALSE;
	if ( 0 > !fprintf( stream, "%d ", info->width ) ) return FXFALSE;
	if ( 0 > !fprintf( stream, "%d\n", info->height ) ) return FXFALSE;
	if ( 0 > !fprintf( stream, "R %d ", info->redBits ) ) return FXFALSE;
	if ( 0 > !fprintf( stream, "G %d ", info->greenBits ) ) return FXFALSE;
	if ( 0 > !fprintf( stream, "B %d\n", info->blueBits ) ) return FXFALSE;
	imgErrorString = "No Error.";
	return FXTRUE;
}

/* _imgWriteP6Data
**
** Summary: UNPUBLISHED - write P6 image data
*/
FxBool _imgWriteP6Data( FILE *stream, const P6Info *info, const ImgData *data )
{
	FxU32 dataSize = info->width * info->height * 4;
	const ImgData *dataEnd  = data + dataSize;

	while( data < dataEnd )
	{
		ImgData r, g, b;
		b = *data++;
		g = *data++;
		r = *data++;
		data++;

		putc( r, stream );
		putc( g, stream );
		if ( putc( b, stream ) == EOF )
		{
			imgErrorString = "Image write error.";
			return FXFALSE;
		}
	}
	return FXTRUE;
}

/* _imgWriteSbiData
**
** Summary: UNPUBLISHED - write sbi image data
*/
FxBool _imgWriteSbiData( FILE *stream, const SbiInfo *info, const ImgData *data )
{
	// convert each pixel, then write it out as two byte-writes
	FxU32 x;
	FxU32 y;
	FxU32 sbiPixel;

	FxI32 scanlineIncrement;

	if ( info->yOrigin == 0 ) // Lower
	{
		scanlineIncrement = (4 * (-(long)info->width)) + 1;
		// point at beginning of last SL
		data = data + (( info->height - 2 ) * info->width * 4);
	}
	else // upper
	{
		scanlineIncrement = 0;
	}

	imgErrorString = "Image write error.";
	for( y = 0; y < info->height; y++ )
	{
		for( x = 0; x < info->width; x++ )
		{
			FxU32 r, g, b;
			b =  *data++ & 0xF8;
			g =  *data++ & 0xFC;
			r =  *data++ & 0xF8;
			data++;
			sbiPixel = (r << 8) | (g << 3) | (b >> 3);
			
			if ( putc( sbiPixel, stream ) == EOF ) return FXFALSE;
			if ( putc( sbiPixel >> 8, stream ) == EOF ) return FXFALSE;
		}
		data += scanlineIncrement;
	}
	imgErrorString = "No error.";
	return FXTRUE;
}


/* _imgWriteSbiDataWide
**
** Summary: UNPUBLISHED - write sbi image data
*/
FxBool _imgWriteSbiDataWide( FILE *stream, const SbiInfo *info, const ImgData *data, FxU32 slWidth )
{
	// convert each pixel, then write it out as two byte-writes
	FxU32 x;
	FxU32 y;
	FxU32 sbiPixel;

	FxI32 scanlineIncrement;

	if ( info->yOrigin == 0 ) // Lower
	{
		scanlineIncrement = (4 * slWidth) + 1;
		// point at beginning of last SL
		data = data + (( info->height - 2 ) * info->width * 4);
	}														 
	else // upper
	{
		scanlineIncrement = (slWidth * 4) - (4*info->width);
	}

	imgErrorString = "Image write error.";
	for( y = 0; y < info->height; y++ )
	{
		for( x = 0; x < info->width; x++ )
		{
			FxU32 r, g, b;
			b =  *data++ & 0xF8;
			g =  *data++ & 0xFC;
			r =  *data++ & 0xF8;
			data++;

			sbiPixel = (r << 8) | (g << 3) | (b >> 3);
			
			if ( putc( sbiPixel, stream ) == EOF ) return FXFALSE;
			if ( putc( sbiPixel >> 8, stream ) == EOF ) return FXFALSE;
		}
		data += scanlineIncrement;
	}
	imgErrorString = "No error.";
	return FXTRUE;
}


/*
** PUBLIC INTERFACE
*/

/*
** PUBLIC DATA
*/

const char *imgTypeString[] = 
{ 
	"UNKNOWN", 
	"SBI", 
	"Binary PPM", 
	"3DF", 
	"RGT", 
	"TGA32" 
};

const char *imgTxColorFormatNames[] =
{
  "unknown",
  "true",
  "i8",
  "a8",
  "ai44",
  "yiq",
  "rgb332",
  "rgb565",
  "argb8332",
  "argb1555",
  "ayiq8422",
  "argb4444",
  "ai88",
  "argb8888"
};


/* imgGetErrorString
**
** Summary: Get a pointer to the buffer that contains a string describing
**          the most recent error in the  Image Library
**
** Returns: Point to an error description string.  Probably a bad idea to write
**         to this address as it will likely be a static string.
*/
const char *imgGetErrorString( void )
{
    return imgErrorString;
}

/* imgReadInfo
**
** Summary: Fill the Info union and return the proper image type
**
** Parameters: info - pointer to an ImgInfo structure including type
**             stream - pointer file stream
**
** Returns: FXTRUE if header read in correctly.
**          FXFALSE if header not read, error described
**                  in string obtainable by sbiGetErrorString();
*/
FxBool imgReadInfo( FILE *stream, ImgInfo *info )
{
    if ( stream == NULL )
    {
		imgErrorString = "Bad file handle.";
		return FXFALSE;
    }
    
	_fixStream( stream );
	info->any.data = NULL;

    // Determine the Type of File and Fill The Appropriate Header Structure
    if ( _imgGuessType( stream, &info->any.type ) == FXFALSE ) return FXFALSE;

    switch( info->any.type )
    {
    	case IMG_SBI:
			return _imgReadSbiHeader( stream, &(info->sbiInfo) );
      	case IMG_RGT:
			return _imgReadRGTHeader( stream, &(info->rgtInfo) );
      	case IMG_TGA32:
			return _imgReadTGAHeader( stream, &(info->tgaInfo) );
    	case IMG_P6:
			return _imgReadP6Header( stream, &(info->p6Info) );
      	case IMG_3DF:
			return _imgRead3DFHeader( stream, &(info->tdfInfo) );
      	case IMG_SRLE:
			return _imgReadSRLEHeader( stream, &(info->srleInfo) );
      	case IMG_UNKNOWN:
      	default:
			imgErrorString = "Unknown image file format.";
			break;
    }
	return FXFALSE;
}

/* imgReadData
**
** Summary: Read all of the image data from an image stream given
**          that the header has already been read by imgReadInfo.
**
** Parameters: stream - a readable file stream
**             info - pointer to filled info structure (except data)
**					- if data is NULL then we malloc data large enough
** Returns: FXTRUE if data was read successfully
**          FXFALSE if an error occured, reason may be
**          obtained with imgGetErrorString().
*/
FxBool imgReadData( FILE *stream, ImgInfo *info )
{
    if ( stream == NULL )
    {
		imgErrorString = "Bad file handle.";
		return FXFALSE;
    }
    
	_fixStream( stream );

	// if data pointer is NULL then malloc something big enough
    if ( info->any.data == NULL ) {
		info->any.data = (ImgData*) malloc( info->any.sizeInBytes );
	    if ( info->any.data == NULL ) {
			imgErrorString = "Malloc failed.";
			return FXFALSE;
		}
    }

    switch( info->any.type )
    {
    	case IMG_TGA32:
			return _imgReadTGAData( stream, &(info->tgaInfo), info->any.data );
    	case IMG_RGT:
			return _imgReadRGTData( stream, &(info->rgtInfo), info->any.data );
    	case IMG_3DF:
			return _imgRead3DFData( stream, &(info->tdfInfo), info->any.data );
    	case IMG_SBI:
			return _imgReadSbiData( stream, &(info->sbiInfo), info->any.data );
    	case IMG_P6:
			return _imgReadP6Data( stream, &(info->p6Info), info->any.data );
    	case IMG_SRLE:
			return _imgReadSRLEData( stream, &(info->srleInfo), info->any.data );
    	case IMG_UNKNOWN:
      default:
		imgErrorString = "Unknown file type.";
      break;
    }
	return FXFALSE;
}

/* imgWriteImage
**
** Summary: Write out an image file in the format specified by *type.
**
** Parameters: stream - a writeable file stream
** 		       info - pointer to initialized image info struct appropriate
**                       to type described in *type
** 		       type - desired image type to write
** 		       data - pointer to 32BPP xRGB data in linear memory storage
**
** Returns: FXTRUE if data was written sucessfully
**          FXFALSE if an error occured, reason may be
**          obtained with imgGetErrorString().
*/


FxBool imgWriteImage( FILE *stream, const ImgInfo *info, const ImgType type, const ImgData *data )
{
	// Try to catch obvious bugs.
	if ( stream == NULL )
	{
		imgErrorString = "Bad file handle.";
		return FXFALSE;
	}

	_fixStream( stream );

	if ( data == NULL )
	{
		imgErrorString = "Bad data pointer.";
		return FXFALSE;
	}

	switch( type )
	{
		case IMG_SBI:
			if ( !_imgWriteSbiHeader( stream, &(info->sbiInfo) ) )
			{
				imgErrorString = "Couldn't write SBI info.";
				return FXFALSE;
			}
			return _imgWriteSbiData( stream, &(info->sbiInfo), data );
		case IMG_P6:
			if ( !_imgWriteP6Header( stream, &(info->p6Info)) )
			{
				imgErrorString = "Couldn't write P6 info.";
				return FXFALSE;
			}
			return _imgWriteP6Data( stream, &(info->p6Info), data );
		case IMG_RGT:
			imgErrorString = "RGT writes unimplemented.";
			break;
		case IMG_TGA32:
			if ( !_imgWriteTGAHeader( stream, &(info->tgaInfo)) )
			{
				imgErrorString = "Couldn't write tga info.";
				return FXFALSE;
			}
			return _imgWriteTGAData( stream, &(info->tgaInfo), data );
			break;
		case IMG_UNKNOWN:
		default:
			imgErrorString = "Can't write unknown format.";
			break;
	}
	
	return FXFALSE;
}

/* imgWriteImageFromWideBuffer
**
** Summary: Write out an image file in the format specified by *type give a pointer
**          to an oversized buffer and a scanline width.  This is in here strictly for
**          the c-simulator, and isn't published in the header file.
**
** Parameters: stream - a writeable file stream
** 		       type - pointer to image desired image type to write
** 		       info - pointer to initialized image info struct appropriate
**                       to type described in *type
** 		       data - pointer to 32BPP xRGB data in linear memory storage
**             scanlineWidth - width of scanline in long-words.
**
** Returns: FXTRUE if data was written sucessfully
**          FXFALSE if an error occured, reason may be
**          obtained with imgGetErrorString().
*/


FxBool imgWriteImageFromWideBuffer( FILE *stream, const ImgInfo *info, 
									const ImgData *data, FxU32 scanlineWidth )
{
	// Try to catch obvious bugs.
	if ( stream == NULL )
	{
		imgErrorString = "Bad file handle.";
		return FXFALSE;
	}

	_fixStream( stream );

	if ( data == NULL )
	{
		imgErrorString = "Bad data pointer.";
		return FXFALSE;
	}

	if ( scanlineWidth < info->any.width )
	{
		imgErrorString = "Output buffer is too small.";
		return FXFALSE;
	}

	switch( info->any.type )
	{
		case IMG_SBI:
			if ( !_imgWriteSbiHeader( stream, &(info->sbiInfo) ) )
			{
				imgErrorString = "Couldn't write SBI info.";
				return FXFALSE;
			}
			return _imgWriteSbiDataWide( stream, &(info->sbiInfo), data, scanlineWidth );
		case IMG_P6:
			imgErrorString = "P6 writes unimplemented.";
			break;
		case IMG_RGT:
			imgErrorString = "RGT writes unimplemented.";
			break;
		case IMG_TGA32:
			imgErrorString = "TGA32 writes unimplemented.";
			break;
		case IMG_UNKNOWN:
		default:
			imgErrorString = "Can't write unknown format.";
			break;
	}
	
	return FXFALSE;
}

// returns a 3-letter abbreviation for the image type
const char *imgTypeName( const ImgInfo *info )
{
	switch( info->any.type ) {
	    case IMG_UNKNOWN:	return "???";
	    case IMG_P6:		return "ppm";
	    case IMG_SBI:		return "sbi";
	    case IMG_3DF:		return "3df";
	    case IMG_TGA32:		return "tga";
	    case IMG_SRLE:		return "srle";
	    case IMG_RGT:
	       	if (info->rgtInfo.rle) return "rle";
	    	if (info->rgtInfo.bgr) return "bgr";
	    	if (info->rgtInfo.rgb) return "rgb";
	    	if (info->rgtInfo.ncc) return "ncc";

		default:			return "ERR";
    }
}

//----------------------------------------------------------------------
// high level routine: reads an image file thru a search path
// exits upon failure, prints out trace messages
//----------------------------------------------------------------------
FxBool imgReadFile(const char *filename, ImgInfo *info)
{
	const char *prefix;
	FILE *file;

	file = fxFopenPath(filename,READ_ATTRIBS, getenv(IMAGE_PATH), &prefix);
	if (file == NULL) {
		fprintf(stderr,"Error: can't open input file %s\n",filename);
		exit(2);
	}
	if ( imgReadInfo( file, info ) == FXFALSE ) {
		fprintf(stderr,"Error: reading info for %s, %s\n",filename,imgGetErrorString());
		exit(2);
	}
	fprintf(stderr,"Loading %s image file ", imgTypeName(info));
	if (prefix) {					// if there's a path prefix
	    char buf[1024], *p;
	    strcpy(buf,prefix);			// copy and replace semicolon
	    if (p = strchr(buf,';')) *p = '\0';
	    fprintf(stderr,buf);
	    fprintf(stderr,"/");
	}
	fprintf (stderr,"%s (%dx%d) ...", filename, info->any.width,info->any.height);
	fflush(stderr);

	if ( imgReadData( file, info ) == FXFALSE ) {
		fprintf(stderr, "\nError: reading data for %s, %s\n",filename,imgGetErrorString());
		exit(4);
	}
	fclose(file);
	fprintf(stderr," done.\n");
	fflush(stderr);
	return FXTRUE;
}

//----------------------------------------------------------------------
// high level routine: writes an image file
// exits upon failure, prints out trace messages
//----------------------------------------------------------------------
FxBool imgWriteFile(const char *filename, const ImgInfo *info, const ImgType type, const ImgData *data)
{
    FxBool retval;
    ImgInfo tempInfo = *info;		// make a local copy
    FILE *file;

    file = fopen(filename,WRITE_ATTRIBS);
    if (file == NULL) {
	fprintf(stderr,"Error: can't open output file %s\n",filename);
	exit(2);
    }
    tempInfo.any.type = type;		// set the new type
    fprintf(stderr,"Storing %s image file %s (%dx%d) ...",
		imgTypeName(&tempInfo), filename, info->any.width,info->any.height);
    fflush(stderr);

    retval = imgWriteImage( file, info, type, data );
    fclose(file);
    fprintf(stderr,retval ? " done.\n" : " aborted.\n");
    fflush(stderr);
    return retval;
}
