/******************************************************************************
 * $Id: gfx_tga.cc,v 1.7 2006/11/15 18:34:51 devel Exp $
 * @file
 * @brief	Loader for Truevision Targa images
 * @author	Dan Maas <dmaas@dcine.com>,
 *          Piotr Pawlow <pp@siedziba.pl>,
 *          Jim E. Brooks http://www.jimbrooks.org
 * @remarks	- Based on TGA specifications available at: 
 *			  http://www.wotsit.org/cgi-bin/search.cgi?TGA
 *			- header/footer structures courtesy of the GIMP Targa plugin 
 *			- Modified 2004/06/13 by Jim Brooks http://www.jimbrooks.org:
 *			  - Modified to process a buffer (for precompiled TGA data)
 *			    rather than a file directly.
 *            - Original C code rewritten in C++, cleanup.
 ******************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "gfx.hh"
#include "gfx_tga.hh"

namespace gfx {

////////////////////////////////////////////////////////////////////////////////
///////////////////////////////  Targa class  //////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
Targa::Targa( const uint8* const ibuf, uint ibufLen, TargaFilterFuncType filterFunc )
{
	mWidth = mHeight = mBufLen = 0;
    mBuf = tga_load( ibuf, ibufLen, &mBufLen, &mWidth, &mHeight, filterFunc );
}
Targa::~Targa()
{
	delete[] mBuf; mBuf = NULL;
}

////////////////////////////////////////////////////////////////////////////////
///////////////////////////  Targa C functions  ////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

#define MAX_BUF_LEN  (32 * MEGABYTE)

// TGA pixel formats
#define TGA_TYPE_MAPPED      1
#define TGA_TYPE_COLOR       2 
#define TGA_TYPE_GRAY        3 
#define TGA_TYPE_MAPPED_RLE  9 
#define TGA_TYPE_COLOR_RLE  10 
#define TGA_TYPE_GRAY_RLE   11

// TGA header flags
#define TGA_DESC_ABITS      0x0f 
#define TGA_DESC_HORIZONTAL 0x10 
#define TGA_DESC_VERTICAL   0x20 

#define TGA_SIGNATURE "TRUEVISION-XFILE" 
#define TGA_FOOTER_LEN 26

struct tga_header
{ 
    uint8   idLength;
    uint8   colorMapType;
    uint8   imageType;
    uint8   colorMapIndexLo, colorMapIndexHi;
    uint8   colorMapLengthLo, colorMapLengthHi;
    uint8   colorMapSize;
    uint8   xOriginLo, xOriginHi;
    uint8   yOriginLo, yOriginHi;
    uint8   widthLo, widthHi;
    uint8   heightLo, heightHi;
    uint8   bpp;
    uint8   descriptor;
}; 

struct tga_footer
{ 
    uint32  extensionAreaOffset;
    uint32  developerDirectoryOffset;
    uint8   signature[16];
    uint8   dot;
    uint8   null;
}; 

/*****************************************************************************
 * Default function to filter RGBA from Targa (pass-thru).
 * Specifying a different filter function can be used to adjust color and alpha
 * of Targa images.
 *****************************************************************************/
void
DefaultTargaFilterFunc( uint8* p, uint8 r, uint8 g, uint8 b, uint8 a )
{
    p[0] = r;
    p[1] = g;
    p[2] = b;
    p[3] = a;
}

/*****************************************************************************
 * Load TGA file.
 * 
 * As written this function only recognizes the following types of Targas: 
 *		Type 02 - Uncompressed RGB, 24 or 32 bits 
 *		Type 03 - Uncompressed grayscale, 8 bits
 *		Type 10 - RLE-compressed RGB, 24 or 32 bits
 *		Type 11 - RLE-compressed grayscale, 8 bits  
 * There are several other (uncommon) Targa formats which this function can't currently handle
 *****************************************************************************/
uint8*
tga_load( const uint8* const ibuf, uint ibufLen, uint* olen, uint* w, uint* h, TargaFilterFuncType filterFunc )
{ 
    if ( ibuf == NULL || ibufLen > MAX_BUF_LEN )
    {
Error:
        *olen = *w = *h = 0;
        return NULL;
    }

    // read the footer first
    uint idx = ibufLen - TGA_FOOTER_LEN;
    if ( ! idx || idx+TGA_FOOTER_LEN > ibufLen )
        goto Error;
    tga_footer footer;
    memcpy( &footer, &ibuf[idx], TGA_FOOTER_LEN );

    // check the footer to see if we have a v2.0 TGA file
    const bool footer_present = memcmp(footer.signature,TGA_SIGNATURE,sizeof(footer.signature)) == 0;
   
    // now read the header
    idx = 0;
    tga_header header;
    memcpy( &header, &ibuf[idx], sizeof(header) );
    idx += sizeof(header);

    // skip over alphanumeric ID field
    idx += header.idLength;

    // now parse the header
   
    // this flag indicated bottom-up pixel storage
    const bool vinverted = header.descriptor ^ TGA_DESC_VERTICAL;

    bool rle = true;
    switch ( header.imageType )
    { 
        case TGA_TYPE_COLOR_RLE: case TGA_TYPE_GRAY_RLE:
            rle = true;
        break; 
	
        case TGA_TYPE_COLOR: case TGA_TYPE_GRAY:
            rle = false; 
        break; 
	
        default:
            *gStreamError << "Targa format not supported" << endl;
            goto Error;
        break;
    }

    // bits per pixel
    const uint bpp = header.bpp;
    if ( bpp != 32 && bpp != 24 && bpp != 8 )
    {
        *gStreamError << "Targa BPP=" << bpp << " not supported" << endl;
        goto Error;
    }
   
    // endian-safe loading of 16-bit sizes
    const uint im_w = (header.widthHi << 8) | header.widthLo; 
    const uint im_h = (header.heightHi << 8) | header.heightLo;    
    if ((im_w > 32767) || (im_w < 1) || (im_h > 32767) || (im_h < 1))
    {
        *gStreamError << "Targa width/height invalid" << endl;
        goto Error;
    }
   
    // allocate the destination buffer
    uint32* im_data = new uint32[im_w * im_h];
	
    // first we read the file data into a buffer for parsing
    // then we decode from RAM
	
	// find out how much data must be read from the file
    // (this is NOT simply width*height*4, due to compression)
	
    const uint datasize = ibufLen - sizeof(tga_header) - header.idLength - (footer_present ? TGA_FOOTER_LEN : 0);
    uint8* const buf = new uint8[datasize];

    // copy input buffer to temp buffer
    memcpy( buf, &ibuf[idx], datasize );

	// buffer is ready for parsing
	
    // bufptr is the next byte to be read from the buffer
	uint8* bufptr = buf; 
	
    // dataptr is the next 32-bit pixel to be filled in
	uint32* dataptr = im_data;
	
    // decode uncompressed BGRA data
    if ( ! rle )
    { 
        for ( uint y = 0; y < im_h; ++y ) // for each row
        { 
            // point dataptr at the beginning of the row
            if ( ! vinverted )  // some TGA's are stored upside-down!
                dataptr = im_data + (im_h - (y+1)) * im_w;
            else
                dataptr = im_data + y * im_w;
		  
            for ( uint x = 0; x < im_w; ++x )  // for each pixel in the row
		    {
                switch ( bpp )
                {
                    // 32-bit BGRA pixels
                    case 32:
                    {
                        filterFunc( reinterpret_cast<uint8*>(dataptr),
                                    *(bufptr + 2),    // R
                                    *(bufptr + 1),    // G
                                    *(bufptr + 0),    // B
                                    *(bufptr + 3) );  // A
			            ++dataptr;
                        bufptr += 4;
                    }
                    break;
			  
                    // 24-bit BGR pixels
                    case 24:
                    {
                        filterFunc( reinterpret_cast<uint8*>(dataptr),
                                    *(bufptr + 2),  // R
                                    *(bufptr + 1),  // G
                                    *(bufptr + 0),  // B
                                    0xff );         // A
                        ++dataptr;
                        bufptr += 3;
                    }
                    break;
			  
                    // 8-bit grayscale
                    case 8:
                    {
                        filterFunc( reinterpret_cast<uint8*>(dataptr),
                                    *bufptr,  // grayscale
                                    *bufptr,
                                    *bufptr,
                                    0xff );
                        ++dataptr;
                        ++bufptr;
                    }
                    break;
		       }
		    }
        }
    }
    else 
	{
        // decode RLE compressed data
        uint8 curbyte, red, green, blue, alpha; 
        const uint32* final_pixel = dataptr + im_w * im_h; 
	     
	    // loop until we've got all the pixels
        while ( dataptr < final_pixel )
        { 
            curbyte = *bufptr++; 
            const uint count = (curbyte & 0x7F) + 1; 
            if ( curbyte & 0x80 )  // RLE packet
            { 
                switch ( bpp )
                {
                    case 32:
                    {
                        blue = *bufptr++; green = *bufptr++; red = *bufptr++; 
                        alpha = *bufptr++;
                        for ( uint i = 0; i < count; ++i )
                        {
                            filterFunc( reinterpret_cast<uint8*>(dataptr), red, green, blue, alpha );
                            ++dataptr;
                        }
                    }
                    break;
			  
                    case 24:
                    {
                        blue = *bufptr++; green = *bufptr++; red = *bufptr++;
                        for ( uint i = 0; i < count; ++i )
                        {
                            filterFunc( reinterpret_cast<uint8*>(dataptr), red, green, blue, 0xff );
                            ++dataptr;
                        }
                    }
                    break;
			  
                    case 8:
                    {
                        alpha = *bufptr++;
                        for ( uint i = 0; i < count; ++i )
                        {
                            filterFunc( reinterpret_cast<uint8*>(dataptr), alpha, alpha, alpha, 0xff );
                            ++dataptr;
                        }
                    }
                    break;
                }
            }
            else
		    {
                // raw packet
                for ( uint i = 0; i < count; ++i ) 
                { 
                    switch ( bpp )
                    {
                        // 32-bit BGRA pixels
                        case 32:
                        {
                            filterFunc( reinterpret_cast<uint8*>(dataptr),
					                    *(bufptr + 2),    // R
					                    *(bufptr + 1),    // G
					                    *(bufptr + 0),    // B
					                    *(bufptr + 3) );  // A
                            ++dataptr;
                            bufptr += 4;
                        }
                        break;
			       
                        // 24-bit BGR pixels
                        case 24:
                        {
                            filterFunc( reinterpret_cast<uint8*>(dataptr),
                                        *(bufptr + 2),  // R
                                        *(bufptr + 1),  // G
					                    *(bufptr + 0),  // B
					                    0xff );         // A
                            ++dataptr;
                            bufptr += 3;
                        }
                        break;
			       
                        // 8-bit grayscale
                        case 8:
                        {
                            filterFunc( reinterpret_cast<uint8*>(dataptr),
                                        *bufptr,  // pseudo-grayscale
					                    *bufptr,
					                    *bufptr,
					                    0xff );
                            ++dataptr;
                            ++bufptr;
                        }
            			break;
                    }  // switch ( bpp )
                }  // for (raw packet)
		    }  // else (curbyte & 0x80)
        }  // while ( dataptr < final_pixel )
	     
        // must now flip a bottom-up image
        // This is the best of several ugly implementations
        // I considered. It's not very good since the image
        //  will be upside-down throughout the loading process.
        // This could be done in-line with the de-RLE code
	    // above, but that would be messy to code. There's probably a better way...

        if ( ! vinverted )
        {
            uint32* im_data_2 = new uint32[im_w * im_h];
            uint adv = 0;
            uint adv2 = im_w * im_h;
            for ( uint i = 0; i < im_h; ++i )
            {
                adv2 -= im_w;
                memmove( im_data_2 + adv,
                         im_data   + adv2,
                         im_w * sizeof(uint32) );
                adv += im_w;
            }
            delete[] im_data;   // switch to flipped buffer, delete old one
            im_data = im_data_2;
        }

    } // end if (image is RLE)

    // Output results and free resources.
	*olen = im_w * im_h * sizeof(uint32);  // byte cnt
    *w = im_w;
    *h = im_h;
    delete[] buf;
    return reinterpret_cast<uint8*>( im_data );
}

} // namespace gfx
