
/****************************************************************************
 *
 * This file is a product of Criterion Software Ltd.
 *
 * This file is provided as is with no warranties of any kind and is
 * provided without any obligation on Criterion Software Ltd.
 * or Canon Inc. to assist in its use or modification.
 *
 * Criterion Software Ltd. and Canon Inc. will not, under any
 * circumstances, be liable for any lost revenue or other damages
 * arising from the use of this file.
 *
 * Copyright (c) 2000 Criterion Software Ltd.
 * All Rights Reserved.
 *
 */

/****************************************************************************
 *                                                                         
 * tga.c
 *
 * Copyright (C) 2000 Criterion Technologies.
 *
 * Original author: Nicolas Vale.
 * Reviewed by: John Irwin.
 *                                                                         
 * Purpose: Demonstrates how different image file formats can be registered 
 *          and used with RenderWare.
 *                         
 ****************************************************************************/

#include "tga.h"
//#include <memory.h>

#define MAX_ID_LENGTH 255

#define GETBYTE(p, off) ((RwUInt8 *)(p) + (off))

#ifdef rwLITTLEENDIAN    
/* 
 * We can just pull from memory...
 */
#define GETLITTLERWINT16(p)  (*(RwInt16 *)(GETBYTE(p, 0)))

#else /* rwLITTLEENDIAN */
/* 
 * We have to shuffle the bits...
 */
#define GETLITTLERWINT16(p)  ((((RwInt16)(*GETBYTE(p, 0))))  | \
                                  (((RwInt16)(*GETBYTE(p, 1))) << 8))
#define GETBIGRWINT16(p)     ((((RwInt16)(*GETBYTE(p, 1))))  | \
                                  (((RwInt16)(*GETBYTE(p, 0))) << 8))
#endif /* rwLITTLEENDIAN */


#define GETPIXELDATA(dest, src, offset) \
    (dest[0] = src[offset+0], \
     dest[1] = src[offset+1], \
     dest[2] = src[offset+2])


/* 
 * TGA image file format header...
 */
typedef struct _TGAHeader TGAHeader;
struct _TGAHeader
{
    RwUInt8  idLength;
    RwUInt8  colorMapType;
    RwUInt8  imageType;
    RwUInt16 firstEntryIndex;
    RwUInt16 colorMapLength;
    RwUInt8  colorMapEntrySize;
    RwUInt16 xOriginImage;
    RwUInt16 yOriginImage;
    RwUInt16 imageWidth;
    RwUInt16 imageHeight;
    RwUInt8  pixelDepth;
    RwUInt8  imageDescriptor;
};

typedef struct _RLE24bitPacket RLE24bitPacket;
struct _RLE24bitPacket
{
    RwUInt8 pixelCount;
    RwUInt8 pixelData[3];
};



/*
 *****************************************************************************
 */
static RwImage *
tgaImageReadRLECompressed24bitRGB(TGAHeader header, RwStream *stream)
{
    RwImage *tgaImage;
    RwUInt8 *imagePixels;
    RwUInt8 *pixelData;
    RwInt32 imageSize;
    RwInt32 i, j;
    RLE24bitPacket rlePacket;
    
    /*
     * Create an image...
     */
    imageSize = header.imageWidth * header.imageHeight;
    tgaImage = RwImageCreate((RwInt32)header.imageWidth, 
        (RwInt32)header.imageHeight, 32);

    /*
     * Allocate pixel space...
     */
    RwImageAllocatePixels(tgaImage);

    /*
     * Get pointer to image pixels...
     */
    imagePixels = RwImageGetPixels(tgaImage);

    /*
     * Read the pixel data from file...
     */
    pixelData = (RwUInt8 *)(RwMalloc((unsigned int)(imageSize*3)));
  
    i = 0;
    while( i < imageSize*3 )
    {
        RwStreamRead(stream, &rlePacket.pixelCount, 1);

        if( rlePacket.pixelCount & 128 )
        {
            /* 
             * Process single RLE packet...
             */
            RwStreamRead(stream, &rlePacket.pixelData, 3);

            for(j = 0; j < (rlePacket.pixelCount%128 + 1); j++) 
            {   
                memcpy(pixelData+i, &rlePacket.pixelData, 3);

                i += 3;
            }
        }
        else
        {   
            /* 
             * Process block of RAW packets...
             */
            RwStreamRead(stream, pixelData+i, (rlePacket.pixelCount+1)*3);

            i += (rlePacket.pixelCount+1)*3;
        }

    }

    /*
     * Convert data to 32-bit...
     */
    for(i=0; i < imageSize; i++)
    {
        imagePixels[(i*4)+0] = pixelData[(i*3)+2];
        imagePixels[(i*4)+1] = pixelData[(i*3)+1];
        imagePixels[(i*4)+2] = pixelData[(i*3)+0];
        imagePixels[(i*4)+3] = 255;
    }

    RwFree(pixelData);

    return tgaImage;
}


/*
 *****************************************************************************
 */
static RwImage *
tgaImageReadUncompressed24bitRGB(TGAHeader header, RwStream *stream)
{
    RwImage *tgaImage;
    RwUInt8 *imagePixels;
    RwUInt8 *pixelData;
    RwInt32 imageSize;
    RwInt32 i;

    /* 
     * Create an image...
     */
    imageSize = header.imageWidth * header.imageHeight;
    tgaImage  = RwImageCreate((RwInt32)header.imageWidth, 
        (RwInt32)header.imageHeight, 32);

    /*
     * Allocate pixel space...
     */
    RwImageAllocatePixels(tgaImage);

    /*
     * Get pointer to image pixels...
     */
    imagePixels = RwImageGetPixels(tgaImage);

    /*
     * Read the pixel data from file...
     */
    pixelData = (RwUInt8 *)(RwMalloc(imageSize*3));
    RwStreamRead(stream, pixelData, imageSize*3);

    /*
     * Convert data to 32-bit...
     */
    for(i=0; i < imageSize; i++)
    {
        imagePixels[(i*4)+0] = pixelData[(i*3)+2];
        imagePixels[(i*4)+1] = pixelData[(i*3)+1];
        imagePixels[(i*4)+2] = pixelData[(i*3)+0];
        imagePixels[(i*4)+3] = 255;
    }

    RwFree(pixelData);

    return tgaImage;
}

/*
 *****************************************************************************
 */
RwImage * 
ImageReadTGA(const RwChar *imageName)
{
    RwStream *stream;

    stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, imageName);
    
    if( stream )
    {
        TGAHeader tgaHeader;
        RwImage *image;

        /*
         * Read the header...
         */
        RwStreamRead(stream, &tgaHeader.idLength,         1);
        RwStreamRead(stream, &tgaHeader.colorMapType,     1);
        RwStreamRead(stream, &tgaHeader.imageType,        1);
        RwStreamRead(stream, &tgaHeader.firstEntryIndex,  2);
        RwStreamRead(stream, &tgaHeader.colorMapLength,   2);
        RwStreamRead(stream, &tgaHeader.colorMapEntrySize,1);
        RwStreamRead(stream, &tgaHeader.xOriginImage,     2);
        RwStreamRead(stream, &tgaHeader.yOriginImage,     2);
        RwStreamRead(stream, &tgaHeader.imageWidth,       2);
        RwStreamRead(stream, &tgaHeader.imageHeight,      2);
        RwStreamRead(stream, &tgaHeader.pixelDepth,       1);
        RwStreamRead(stream, &tgaHeader.imageDescriptor,  1);

#ifndef rwLITTLEENDIAN
        /*
         * Those variables longer than 1 byte will be incorrect...
         */
        tgaHeader.firstEntryIndex = GETLITTLERWINT16(&tgaHeader.firstEntryIndex);
        tgaHeader.colorMapLength  = GETLITTLERWINT16(&tgaHeader.colorMapLength);
        tgaHeader.xOriginImage    = GETLITTLERWINT16(&tgaHeader.xOriginImage);
        tgaHeader.yOriginImage    = GETLITTLERWINT16(&tgaHeader.yOriginImage);
        tgaHeader.imageWidth      = GETLITTLERWINT16(&tgaHeader.imageWidth);
        tgaHeader.imageHeight     = GETLITTLERWINT16(&tgaHeader.imageHeight);
#endif /* rwLITTLEENDIAN */

        /*
         * We only load 24-bit images...
         */
        if( tgaHeader.pixelDepth != 24 )
        {
            RwStreamClose(stream, NULL);

            return NULL;
        }

        switch( tgaHeader.imageType )
        {
            case 2:
            {
                /* 
                 * Uncompressed, RGB image...
                 */
                image = tgaImageReadUncompressed24bitRGB(tgaHeader, stream);

                break;
            }

            case 10:
            {
                /* 
                 * RLE encoded, RGB image...
                 */
                image = tgaImageReadRLECompressed24bitRGB(tgaHeader, stream);

                break;
            }

            default:
            {
                image = NULL;
            }
        }

        RwStreamClose(stream, NULL);

        return image;
    }

    return NULL;
}


/*
 *****************************************************************************
 */
static RwImage *
tgaImageWriteUncompressed24bitRGB(RwImage *image, TGAHeader header, 
                                  RwStream *stream)
{
    RwUInt8 *imagePixels;
    RwUInt8 *pixelData;
    RwInt32 imageSize;
    RwInt32 i;
    
    /*
     * Create an image...
     */
    imageSize = header.imageWidth * header.imageHeight;

    /*
     * Get pointer to image pixels...
     */
    imagePixels = RwImageGetPixels(image);
    
    /*
     * Create the pixel data...
     */
    pixelData = (RwUInt8 *)(RwMalloc(imageSize*3));

    /*
     * Convert data back to 24-bit...
     */
    for(i=0; i < imageSize; i++)
    {
        pixelData[(i*3)+0] = imagePixels[(i*4)+2];
        pixelData[(i*3)+1] = imagePixels[(i*4)+1];
        pixelData[(i*3)+2] = imagePixels[(i*4)+0];
    }

    RwStreamWrite(stream, pixelData, imageSize*3);

    RwFree(pixelData);

    return image;
}


/*
 *****************************************************************************
 */
static RwImage *
tgaImageWriteRLECompressed24bitRGB(RwImage *image, TGAHeader header, 
                                   RwStream *stream)
{
    RwUInt8 *imagePixels;
    RwUInt8 *pixelData;
    RwInt32 pixelDataSize = 0;
    RwInt32 imageSize;
    RwInt32 i;
    
    /*
     * Create an image...
     */
    imageSize = header.imageWidth * header.imageHeight;

    /*
     * Get pointer to image pixels...
     */
    imagePixels = RwImageGetPixels(image);
    
    /* 
     * Create the pixel data: 
     * - potentially there can be up to 4 bytes per pixel, a header(1) and data(3)
     * - the image is not analysed to determine whether there are any benefits to 
     *   storing the data as RLE. Images with few areas of the same color don't 
     *   benefit from RLE and advanced writers use RAW format for such images.
     */
    pixelData = (RwUInt8 *)(RwMalloc(imageSize*4));

    /*
     * Convert data to RLE 24-bit...
     */
    for(i=0; i < imageSize; i++)
    {
        static RwUInt8 curPixelData[3];
        static RwUInt8 pixelCount = 0;
        static RwBool newPixel = TRUE;

        /*
         * Compare the current pixel data to the current pixel we are counting...
         */
        if( newPixel )
        {
            /*
             * Initialize pixel data and counter...
             */
            pixelCount = 1;

            GETPIXELDATA(curPixelData, imagePixels, i*4);

            newPixel = FALSE;
        }
        else if( imagePixels[(i*4)+2] == curPixelData[0] &&
                 imagePixels[(i*4)+1] == curPixelData[1] &&
                 imagePixels[(i*4)+0] == curPixelData[2] )
        {
            /*
             * Count number of pixels that are the same...
             */
            pixelCount++;
            
            /* 
             * The packet cannot cross scanlines...
             */
            if( (i+1)%header.imageWidth == 0 ) 
            {
                /*
                 * Write the header...
                 */
                pixelData[pixelDataSize] = (pixelCount - 1) | 128;

                /*
                 * Write the pixel data...
                 */
                pixelData[pixelDataSize+1] = curPixelData[2];
                pixelData[pixelDataSize+2] = curPixelData[1];
                pixelData[pixelDataSize+3] = curPixelData[0];        
                pixelDataSize += 4;

                newPixel=TRUE;
            }
        }
        else
        {
            /* 
             * Pixel is different...
             */

            /*
             * Write the header...
             */
            pixelData[pixelDataSize] = (pixelCount - 1) | 128;

            /*
             * Write the pixel data...
             */
            pixelData[pixelDataSize+1] = curPixelData[2];
            pixelData[pixelDataSize+2] = curPixelData[1];
            pixelData[pixelDataSize+3] = curPixelData[0];        
            pixelDataSize += 4;
            
            /*
             * Record different pixel data...
             */
            pixelCount = 1;
            
            GETPIXELDATA(curPixelData, imagePixels, i*4);
        }
    }

    RwStreamWrite(stream, pixelData, pixelDataSize);

    RwFree(pixelData);

    return image;
}



/*
 *****************************************************************************
 */
RwImage * 
ImageWriteTGA(RwImage *image, const RwChar *imageName)
{
    RwStream *stream;

    stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMWRITE, imageName);

    if( stream )
    {
        TGAHeader tgaHeader;
        RwImage *img;

        /* 
         * Construct the header (we only write uncompressed image files)...
         */
        tgaHeader.idLength          = (RwUInt8)  0;
        tgaHeader.colorMapType      = (RwUInt8)  0;
        tgaHeader.imageType         = (RwUInt8)  2;
        tgaHeader.firstEntryIndex   = (RwUInt16) 0;
        tgaHeader.colorMapLength    = (RwUInt16) 0;
        tgaHeader.colorMapEntrySize = (RwUInt8)  0;
        tgaHeader.xOriginImage      = (RwUInt16) 0;
        tgaHeader.yOriginImage      = (RwUInt16) 0;
        tgaHeader.imageWidth        = (RwUInt16) RwImageGetWidth(image);
        tgaHeader.imageHeight       = (RwUInt16) RwImageGetHeight(image);
        tgaHeader.pixelDepth        = (RwUInt8)  24;
        tgaHeader.imageDescriptor   = (RwUInt8)  0;

#ifndef rwLITTLEENDIAN
        /*
         * Those variables longer than 1 byte will be incorrect...
         */
        tgaHeader.firstEntryIndex = GETBIGRWINT16(&tgaHeader.firstEntryIndex);
        tgaHeader.colorMapLength  = GETBIGRWINT16(&tgaHeader.colorMapLength);
        tgaHeader.xOriginImage    = GETBIGRWINT16(&tgaHeader.xOriginImage);
        tgaHeader.yOriginImage    = GETBIGRWINT16(&tgaHeader.yOriginImage);
        tgaHeader.imageWidth      = GETBIGRWINT16(&tgaHeader.imageWidth);
        tgaHeader.imageHeight     = GETBIGRWINT16(&tgaHeader.imageHeight);
#endif /* rwLITTLEENDIAN */

        /* Write the header */
        RwStreamWrite(stream, &tgaHeader.idLength,         1);
        RwStreamWrite(stream, &tgaHeader.colorMapType,     1);
        RwStreamWrite(stream, &tgaHeader.imageType,        1);
        RwStreamWrite(stream, &tgaHeader.firstEntryIndex,  2);
        RwStreamWrite(stream, &tgaHeader.colorMapLength,   2);
        RwStreamWrite(stream, &tgaHeader.colorMapEntrySize,1);
        RwStreamWrite(stream, &tgaHeader.xOriginImage,     2);
        RwStreamWrite(stream, &tgaHeader.yOriginImage,     2);
        RwStreamWrite(stream, &tgaHeader.imageWidth,       2);
        RwStreamWrite(stream, &tgaHeader.imageHeight,      2);
        RwStreamWrite(stream, &tgaHeader.pixelDepth,       1);
        RwStreamWrite(stream, &tgaHeader.imageDescriptor,  1);
    
        /* 
         * Uncompressed, RGB images...
         */
        img = tgaImageWriteUncompressed24bitRGB(image, tgaHeader, stream);

        RwStreamClose(stream, NULL);

        return img;
    }

    return NULL;
}

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