/* $Id: tga.c,v 1.2 2001-09-05 14:30:47 bird Exp $ */
/*
** 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: 1.2 $
** $Date: 2001-09-05 14:30:47 $
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "texusint.h"


typedef struct _tgaHeader{
    FxU8 IDLength;
    FxU8 CMapType;
    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;

/* Definitions for image types. */
#define TGA_NULL    0
#define TGA_CMAP    1
#define TGA_TRUE    2
#define TGA_MONO    3
#define TGA_CMAP_RLE    9
#define TGA_TRUE_RLE    10
#define TGA_MONO_RLE    11

FxBool
_txReadTGAHeader( FILE *stream, FxU32 cookie, TxMip *info)
{
    TgaHeader   *tgaHeader = (TgaHeader *) info->pal;
    int         i;

    // Fill up rest of the TGA header.
    if ( fread( &(tgaHeader->ImgType), 1, sizeof(TgaHeader)-2, stream ) !=
        sizeof(TgaHeader)-2) {
        txPanic("Unexpected end of file.");
        return FXFALSE;
    }
    tgaHeader->IDLength = (FxU8) ((cookie >>  8) & 0xFF);
    tgaHeader->CMapType = (FxU8) ((cookie      ) & 0xFF);

    // Optionally, skip the image id fields.
    for (i= (tgaHeader->IDLength) & 0xFF; i; i--) {
        int c;

        if ((c = getc(stream)) == EOF) {
            txPanic("Unexpected EOF.");
            return FXFALSE;
        }
    }

    // 3Dfx specific part here.
    info->width  = tgaHeader->WidthHi << 8 | tgaHeader->WidthLo;
    info->height = tgaHeader->HeightHi << 8 | tgaHeader->HeightLo;
    info->depth  = 1;

    if ((info->width <= 0) || (info->height <= 0)) {
        txError("TGA Image: width or height is 0.");
        return FXFALSE;
    }

    switch(tgaHeader->ImgType) {
    case TGA_MONO:
    case TGA_MONO_RLE:          // True color image.
        if (tgaHeader->PixelDepth != 8) {
        txError("TGA Image: Mono image is not 8 bits/pixel.");
        return FXFALSE;
        }
        info->format = GR_TEXFMT_I_8;
        break;

    case TGA_TRUE:
    case TGA_TRUE_RLE:
        switch (tgaHeader->PixelDepth ) {
        case    15:
        case    16:
            info->format = GR_TEXFMT_ARGB_1555; break;
        case    24:
        case    32:
            info->format = GR_TEXFMT_ARGB_8888; break;
        default:
                txError("TGA Image: True color image is not 24/32 bits/pixel.");
                return FXFALSE;
                break;
        }
        break;

    case TGA_CMAP:
    case TGA_CMAP_RLE:          // Color mapped image.
        if ( tgaHeader->CMapType     != 1 ) {
            txError("TGA Image: Color-mapped TGA image has no palette");
            return FXFALSE;
        }
        if (((tgaHeader->CMapLengthLo + tgaHeader->CMapLengthHi * 256L)
            +(tgaHeader->CMapStartLo + tgaHeader->CMapStartHi * 256L)) > 256){
            txError("TGA Image: Color-mapped image has > 256 colors");
            return FXFALSE;
        }
        info->format = GR_TEXFMT_P_8;
        break;

    default:
        txError("TGA Image: unsupported format");
        return  FXFALSE;
    }
    info->size = info->width*info->height*GR_TEXFMT_SIZE(info->format);

    return FXTRUE;
}

static FxBool
_txReadTGAColorMap(FILE *stream, const TgaHeader *tgaHeader, FxU32 *palette)
{
    int     cmapStart;
    int     cmapLength;
    int     cmapDepth;
    int     i;

    cmapStart   = tgaHeader->CMapStartLo;
    cmapStart  += tgaHeader->CMapStartHi * 256L;

    cmapLength  = tgaHeader->CMapLengthLo;
    cmapLength += tgaHeader->CMapLengthHi * 256L;

    cmapDepth   = tgaHeader->CMapDepth;

    if (tgaHeader->CMapType == 0) return FXTRUE;        // no colormap.

    /* Validate some parameters */
    if (cmapStart < 0) {
        txError("TGA Image: Bad Color Map start value.");
        return FXFALSE;
    }

    cmapDepth = (cmapDepth + 1) >> 3;       // to bytes.
    if ((cmapDepth <= 0) || (cmapDepth > 4)) {
        txError("TGA Image: Bad Color Map depth.");
        return FXFALSE;
    }

    // May have to skip the color map.
    if ((tgaHeader->ImgType != TGA_CMAP) &&
        (tgaHeader->ImgType != TGA_CMAP_RLE)) {
        /* True color, yet there is a palette, this is OK, just skip. */

        cmapLength *= cmapDepth;
        while (cmapLength--) {
            int     c;

            c = getc(stream);
            if (c == EOF) {
                txError("TGA Image: Unexpected EOF reading Color Map.");
                return FXFALSE;
            }
        }
        return FXTRUE;
    }

    // This is a real palette that's going to be used.

    // Verify that it's not too large.
    if ((cmapStart + cmapLength) > 256) {
        txError("TGA Image: Color Map > 256 entries.");
        return FXFALSE;
    }


    // printf("cmapdepth = %d, start = %d, length = %d\n", cmapDepth,
    //  cmapStart, cmapLength);
    for (i=0; i<256; i++) {
        int r, g, b, a;

        if ((i < cmapStart) || (i >= (cmapStart + cmapLength))) {
            palette[i] = 0;
            // printf("Skipping palette entry %d\n", i);
            continue;
        }

        // Read this colormap entry.
        switch (cmapDepth) {
        case 1: // 8 bpp
            r = getc(stream);
            if (r == EOF) {
            txError("TGA Image: Unexpected End of File.");
            return FXFALSE;
            }
            r &= 0xFF;
            palette[i] = (r << 24) | (r << 16) | (r << 8) | (r);
        break;

        case 2: // 15, 16 bpp.

        b = getc(stream);
        r = getc(stream);
        if ((r == EOF) || (b == EOF)) {
            txError("TGA Image: Unexpected End of File.");
            return FXFALSE;
        }
        r &= 0xFF;
        b &= 0xFF;
        g = ((r & 0x3) << 6) + ((b & 0xE0) >> 2);
        r = (r & 0x7C) << 1;
        b = (b & 0x1F) << 3;

        palette[i] = (r << 16) | (g << 8) | (b) | 0xFF000000L;
        break;

        case 3:
        case 4:
        b = getc(stream);
        g = getc(stream);
        r = getc(stream);
        a = (cmapDepth == 4) ? getc(stream) : 0x0FF;

        if ((r == EOF) || (g == EOF) || (b == EOF) | (a == EOF)) {
            txError("TGA Image: Unexpected End of File.");
            return FXFALSE;
        }
        palette[i] = (a << 24) | (r << 16) | (g << 8) | b;
        // printf("Setting palette %3d to %.08x\n", i, palette[i]);
        break;

        default:
            txError("TGA Image: Bad Color Map depth.");
            return FXFALSE;
        }
    }
    return FXTRUE;
}

static  int tgaRLE, tgaRLEflag, tgaRLEcount, tgaRLEsav[4];

static  FxBool
_txReadTGARLEPixel( FILE *stream, FxU8 *data, int pixsize)
{
    int     c, i;

    // Run length encoded data Only
    if (tgaRLEcount == 0) {
        // Need to restart the run.
        if ( (tgaRLEcount = c = getc( stream )) == EOF) {
            txError("TGA Image: Unexpected End of File.");
            return FXFALSE;
        }
        tgaRLEflag = tgaRLEcount & 0x80;
        tgaRLEcount = (tgaRLEcount & 0x7F) + 1;

        if (tgaRLEflag) {
        // Replicated color, read the color to be replicated
            for (i=0; i<pixsize; i++) {
        if ( (c = getc( stream )) == EOF) {
                txError("TGA Image: Unexpected End of File\n");
                return FXFALSE;
            }
            tgaRLEsav[i] = (FxU8) c;
            }
        }
    }

    // Now deliver the data either from input or from saved values.
    tgaRLEcount--;
    if (tgaRLEflag) {
        // deliver from saved data.
        for (i=0; i<pixsize; i++) *data++ = (FxU8) tgaRLEsav[i];
    } else {
        for (i=0; i<pixsize; i++) {
            if ( (c = getc( stream )) == EOF) {
            txError("TGA Image: Unexpected End of File\n");
            return FXFALSE;
        }
            *data++ = (FxU8) c;
        }
    }
    return FXTRUE;
}

static FxBool
_txReadTGASpan( FILE *stream, FxU8 *data, int w, int pixsize)
{
    if (tgaRLE == 0) {
        if ( fread( data, 1, w * pixsize, stream) != (FxU32)(w * pixsize)) {
            txError("TGA Image: Unexpected End of File\n");
            return FXFALSE;
        }
        return FXTRUE;
    }

    // Otherwise, RLE data.
    while (w--) {
        if (!_txReadTGARLEPixel( stream, data, pixsize)) {
        return FXFALSE;
        }
        data += pixsize;
    }
    return FXTRUE;
}

FxBool
_txReadTGAData( FILE *stream, TxMip *info)
{
    TgaHeader   *tgaHeader = (TgaHeader *) info->pal;
    int     i, stride;
    int     bpp;            // bytesPerPixel
    FxU8*   data;
    long    BigEndian = 0xff000000;

    // printf("TxREAD TGA DATA\n");
    tgaRLEcount = 0;

    bpp = (tgaHeader->PixelDepth + 1) >> 3;

    switch (tgaHeader->ImgType) {
    case TGA_MONO:  tgaRLE = 0; info->format = GR_TEXFMT_I_8; break;
    case TGA_MONO_RLE:  tgaRLE = 1; info->format = GR_TEXFMT_I_8; break;

    case TGA_TRUE:  tgaRLE = 0; info->format = (bpp == 2) ?
                GR_TEXFMT_ARGB_1555 : GR_TEXFMT_ARGB_8888; break;
    case TGA_TRUE_RLE:  tgaRLE = 1; info->format = (bpp == 2) ?
                GR_TEXFMT_ARGB_1555 : GR_TEXFMT_ARGB_8888; break;

    case TGA_CMAP:  tgaRLE = 0; info->format = GR_TEXFMT_P_8; break;
    case TGA_CMAP_RLE:  tgaRLE = 1; info->format = GR_TEXFMT_P_8; break;
    }

    // printf("bpp = %d, rle = %d\n", bpp, tgaRLE);

    stride =  info->width * bpp;
    data   =  info->data[0];
    if ((tgaHeader->ImageDescriptor & 0x20) == 0) {
        // Origin is lower left
        data   = data + (info->height-1) * stride;
        stride = -stride;
    }

    /* If there's a colormap, read it now. */
    if (!_txReadTGAColorMap(stream, tgaHeader, (FxU32 *) &(info->pal[0])))
        return FXFALSE;
    // printf("read in color map\n");

    /* Read in all the data */
    for ( i = 0; i < info->height; i++) {
        if (!_txReadTGASpan( stream, data, info->width, bpp)) {
                txError("TGA Image: Unexpected end of file.");
                return FXFALSE;
        }
        data += stride;
    }

    /*
     * BPP == 1 -> P8 or I8 formatted data.
     * BPP == 2 -> ARGB1555 formatted data.
     * BPP == 4 -> ARGB8888 formatted data.
     * BPP == 3  should be translated to ARGB8888 from RGB888.
     */
    // printf("Repacking\n");
    if (bpp == 3) {
        int     npixels = info->width * info->height;
        FxU8    *src = ((FxU8 *) info->data[0]) + (npixels - 1) * 3;
        FxU8    *dst = ((FxU8 *) info->data[0]) + (npixels - 1) * 4;

        while (npixels--) {
            dst[3] = 0xFF;
            dst[2] = src[2];
            dst[1] = src[1];
            dst[0] = src[0];
            dst -= 4;
            src -= 3;
        }
    }
    // printf("Done\n");
    if (*(FxU8 *)&BigEndian) {
    /* Repack 16bpp and 32bpp cases */
        if (bpp == 2) {
            int     npixels = info->width * info->height;
            FxU16   *src = (FxU16 *) info->data[0];

            while (npixels--) {
                *src = (*src << 8) | ((*src >> 8) & 0xff);
                src++;
            }
        }
        if ((bpp == 3) || (bpp == 4)) {
            int     npixels = info->width * info->height;
            FxU32   *src = (FxU32 *) info->data[0];

            while (npixels--) {
                *src = (((*src      ) & 0xff)  << 24)|
                       (((*src >>  8) & 0xff)  << 16)|
                       (((*src >> 16) & 0xff)  <<  8)|
                       (((*src >> 24) & 0xff)       );
                src++;
            }
        }
    }
    return FXTRUE;
}
