/* tgatoppm.c - read a TrueVision Targa file and write a portable pixmap
**
** Partially based on tga2rast, version 1.0, by Ian MacPhedran.
**
** Copyright (C) 1989 by Jef Poskanzer.
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted, provided
** that the above copyright notice appear in all copies and that both that
** copyright notice and this permission notice appear in supporting
** documentation.  This software is provided "as is" without express or
** implied warranty.
*/

#define _DEFAULT_SOURCE 1  /* New name for SVID & BSD source defines */
#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */

#include <string.h>

#include "pm_c_util.h"
#include "ppm.h"
#include "tga.h"
#include "shhopt.h"
#include "nstring.h"

#define MAXCOLORS 16384

static int mapped, rlencoded;

static pixel ColorMap[MAXCOLORS];
static gray AlphaMap[MAXCOLORS];

struct cmdlineInfo {
    /* All the information the user supplied in the command line,
       in a form easy for the program to use.
    */
    const char *input_filename;
    unsigned int headerdump;
    const char *alpha_filename;
    unsigned int alpha_stdout;
};



static void
parseCommandLine(int argc, char ** argv,
                   struct cmdlineInfo *cmdlineP) {
/*----------------------------------------------------------------------------
   Note that many of the strings that this function returns in the
   *cmdlineP structure are actually in the supplied argv array.  And
   sometimes, one of these strings is actually just a suffix of an entry
   in argv!
-----------------------------------------------------------------------------*/
    optEntry *option_def = malloc(100*sizeof(optEntry));
        /* Instructions to OptParseOptions2 on how to parse our options.
         */
    optStruct3 opt;

    unsigned int option_def_index;

    unsigned int alpha_spec;

    option_def_index = 0;   /* incremented by OPTENTRY */
    OPTENT3(0, "headerdump", OPT_FLAG,   NULL, &cmdlineP->headerdump,   0);
    OPTENT3(0, "debug",      OPT_FLAG,   NULL, &cmdlineP->headerdump,   0);
    OPTENT3(0, "alphaout",   OPT_STRING, &cmdlineP->alpha_filename,
            &alpha_spec, 0);

    opt.opt_table = option_def;
    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */

    pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
        /* Uses and sets argc, argv, and some of *cmdlineP and others. */

    if (argc - 1 == 0)
        cmdlineP->input_filename = "-";  /* he wants stdin */
    else if (argc - 1 == 1)
        cmdlineP->input_filename = strdup(argv[1]);
    else
        pm_error("Too many arguments.  The only argument accepted "
                 "is the input file specification");

    if (alpha_spec &&
        streq(cmdlineP->alpha_filename, "-"))
        cmdlineP->alpha_stdout = 1;
    else
        cmdlineP->alpha_stdout = 0;

    if (!alpha_spec)
        cmdlineP->alpha_filename = NULL;
}



static unsigned char
getbyte(FILE * const ifP) {

    unsigned char c;

    if ( fread( (char*) &c, 1, 1, ifP ) != 1 )
        pm_error( "EOF / read error" );

    return c;
}



static int RLE_count = 0, RLE_flag = 0;



static void
handleRun(FILE * const ifP,
          bool * const repeatP) {

    if (rlencoded) {
        if (RLE_count == 0) {
            /* Have to restart run. */
            unsigned char i;
            i = getbyte(ifP);
            RLE_flag = (i & 0x80);
            if (RLE_flag == 0) {
                /* Stream of unencoded pixels. */
                RLE_count = i + 1;
            } else {
                /* Single pixel replicated. */
                RLE_count = i - 127;
            }
            /* Decrement count & get pixel. */
            --RLE_count;
            *repeatP = false;
        } else {
            /* Have already read count & (at least) first pixel. */
            --RLE_count;
            if (RLE_flag != 0) {
                /* Replicated pixels. */
                *repeatP = true;
            } else
                *repeatP = false;
        }
    } else
        *repeatP = false;
}



static void
getPixel(FILE *  const ifP,
         pixel * const dest,
         int     const size,
         gray *  const alphaP) {

    static pixval red, grn, blu;
    static pixval alpha;
    static unsigned int l;
    unsigned char j, k;
    bool repeat;
        /* Next pixel is just a repeat (from an encoded run) */

    handleRun(ifP, &repeat);

    if (repeat) {
        /* Use red, grn, blu, alpha, and l from prior call to getPixel */
    } else {
        /* Read appropriate number of bytes, break into RGB. */
        switch (size) {
        case 8:             /* Grayscale, read and triplicate. */
            red = grn = blu = l = getbyte(ifP);
            alpha = 0;
            break;

        case 16:            /* 5 bits each of red green and blue. */
        case 15:            /* Watch byte order. */
            j = getbyte(ifP);
            k = getbyte(ifP);
            l = ((unsigned int)k << 8) + j;
            red = (k & 0x7C) >> 2;
            grn = ((k & 0x03) << 3) + ((j & 0xE0) >> 5);
            blu = j & 0x1F;
            alpha = 0;
            break;

        case 32:            /* 8 bits each of blue, green, red, and alpha */
        case 24:            /* 8 bits each of blue, green, and red. */
            blu = getbyte(ifP);
            grn = getbyte(ifP);
            red = getbyte(ifP);
            if (size == 32)
                alpha = getbyte(ifP);
            else
                alpha = 0;
            l = 0;
            break;

        default:
            pm_error("unknown pixel size (#2) - %d", size);
        }
    }
    if (mapped) {
        *dest = ColorMap[l];
        *alphaP = AlphaMap[l];
    } else {
        PPM_ASSIGN(*dest, red, grn, blu);
        *alphaP = alpha;
    }
}



static void
readtga(FILE * const ifP, struct ImageHeader * tgaP) {

    unsigned char flags;
    ImageIDField junk;

    tgaP->IdLength = getbyte( ifP );
    tgaP->CoMapType = getbyte( ifP );
    tgaP->ImgType = getbyte( ifP );
    tgaP->Index_lo = getbyte( ifP );
    tgaP->Index_hi = getbyte( ifP );
    tgaP->Length_lo = getbyte( ifP );
    tgaP->Length_hi = getbyte( ifP );
    tgaP->CoSize = getbyte( ifP );
    tgaP->X_org_lo = getbyte( ifP );
    tgaP->X_org_hi = getbyte( ifP );
    tgaP->Y_org_lo = getbyte( ifP );
    tgaP->Y_org_hi = getbyte( ifP );
    tgaP->Width_lo = getbyte( ifP );
    tgaP->Width_hi = getbyte( ifP );
    tgaP->Height_lo = getbyte( ifP );
    tgaP->Height_hi = getbyte( ifP );
    tgaP->PixelSize = getbyte( ifP );
    flags = getbyte( ifP );
    tgaP->AttBits = flags & 0xf;
    tgaP->Rsrvd = ( flags & 0x10 ) >> 4;
    tgaP->OrgBit = ( flags & 0x20 ) >> 5;
    tgaP->IntrLve = ( flags & 0xc0 ) >> 6;

    if ( tgaP->IdLength != 0 )
        fread( junk, 1, (int) tgaP->IdLength, ifP );
}



static void
get_map_entry(FILE * const ifP, pixel * Value, int Size, gray * Alpha) {

    unsigned char j, k, r, g, b, a;

    /* Read appropriate number of bytes, break into rgb & put in map. */
    switch ( Size )
    {
    case 8:             /* Grayscale, read and triplicate. */
        r = g = b = getbyte( ifP );
        a = 0;
        break;

    case 16:            /* 5 bits each of red green and blue. */
    case 15:            /* Watch for byte order. */
        j = getbyte( ifP );
        k = getbyte( ifP );
        r = ( k & 0x7C ) >> 2;
        g = ( ( k & 0x03 ) << 3 ) + ( ( j & 0xE0 ) >> 5 );
        b = j & 0x1F;
        a = 0;
        break;

    case 32:            /* 8 bits each of blue, green, red, and alpha */
    case 24:            /* 8 bits each of blue green and red. */
        b = getbyte( ifP );
        g = getbyte( ifP );
        r = getbyte( ifP );
        if ( Size == 32 )
            a = getbyte( ifP );
        else
            a = 0;
        break;

    default:
        pm_error( "unknown colormap pixel size (#2) - %d", Size );
    }
    PPM_ASSIGN( *Value, r, g, b );
    *Alpha = a;
}



static void
dumpHeader(struct ImageHeader const tga_head) {
    const char * imgTypeName;
    switch(tga_head.ImgType) {
    case TGA_Map:      imgTypeName = "TGA_Map";      break;
    case TGA_RGB:      imgTypeName = "TGA_RGB";      break;
    case TGA_Mono:     imgTypeName = "TGA_Mono";     break;
    case TGA_RLEMap:   imgTypeName = "TGA_RLEMap";   break;
    case TGA_RLERGB:   imgTypeName = "TGA_RLERGB";   break;
    case TGA_RLEMono:  imgTypeName = "TGA_RLEMono";  break;
    case TGA_CompMap:  imgTypeName = "TGA_CompMap";  break;
    case TGA_CompMap4: imgTypeName = "TGA_CompMap4"; break;
    default:           imgTypeName = "unknown";
    }
    pm_message( "IdLength = %d", (int) tga_head.IdLength );
    pm_message( "CoMapType = %d", (int) tga_head.CoMapType );
    pm_message( "ImgType = %d (%s)", (int) tga_head.ImgType, imgTypeName );
    pm_message( "Index_lo = %d", (int) tga_head.Index_lo );
    pm_message( "Index_hi = %d", (int) tga_head.Index_hi );
    pm_message( "Length_lo = %d", (int) tga_head.Length_lo );
    pm_message( "Length_hi = %d", (int) tga_head.Length_hi );
    pm_message( "CoSize = %d", (int) tga_head.CoSize );
    pm_message( "X_org_lo = %d", (int) tga_head.X_org_lo );
    pm_message( "X_org_hi = %d", (int) tga_head.X_org_hi );
    pm_message( "Y_org_lo = %d", (int) tga_head.Y_org_lo );
    pm_message( "Y_org_hi = %d", (int) tga_head.Y_org_hi );
    pm_message( "Width_lo = %d", (int) tga_head.Width_lo );
    pm_message( "Width_hi = %d", (int) tga_head.Width_hi );
    pm_message( "Height_lo = %d", (int) tga_head.Height_lo );
    pm_message( "Height_hi = %d", (int) tga_head.Height_hi );
    pm_message( "PixelSize = %d", (int) tga_head.PixelSize );
    pm_message( "AttBits = %d", (int) tga_head.AttBits );
    pm_message( "Rsrvd = %d", (int) tga_head.Rsrvd );
    pm_message( "OrgBit = %d", (int) tga_head.OrgBit );
    pm_message( "IntrLve = %d", (int) tga_head.IntrLve );
}



int
main(int argc, char * argv[]) {

    struct cmdlineInfo cmdline;
    struct ImageHeader tga_head;
    FILE* ifP;
    FILE *imageout_file, *alpha_file;
    int rows, cols, row, realrow, truerow, baserow;
    int maxval;
    pixel** pixels;   /* The image array in ppm format */
    gray** alpha;     /* The alpha channel array in pgm format */

    ppm_init(&argc, argv);

    parseCommandLine(argc, argv, &cmdline);

    ifP = pm_openr(cmdline.input_filename);

    if (cmdline.alpha_stdout)
        alpha_file = stdout;
    else if (cmdline.alpha_filename == NULL)
        alpha_file = NULL;
    else
        alpha_file = pm_openw(cmdline.alpha_filename);

    if (cmdline.alpha_stdout)
        imageout_file = NULL;
    else
        imageout_file = stdout;

    /* Read the Targa file header. */
    readtga(ifP, &tga_head);

    if (cmdline.headerdump)
        dumpHeader(tga_head);

    rows = ((int) tga_head.Height_lo) + ((int) tga_head.Height_hi) * 256;
    cols = ((int) tga_head.Width_lo)  + ((int) tga_head.Width_hi)  * 256;

    switch (tga_head.ImgType) {
    case TGA_Map:
    case TGA_RGB:
    case TGA_Mono:
    case TGA_RLEMap:
    case TGA_RLERGB:
    case TGA_RLEMono:
        break;
    case TGA_CompMap:
    case TGA_CompMap4:
        pm_error("Targa image type %d (compressed color-mapped data). "
                 "Cannot handle this format.", tga_head.ImgType);
        break;
    default:
        pm_error("unknown Targa image type %d", tga_head.ImgType);
    }

    if (tga_head.ImgType == TGA_Map ||
        tga_head.ImgType == TGA_RLEMap ||
        tga_head.ImgType == TGA_CompMap ||
        tga_head.ImgType == TGA_CompMap4)
    { /* Color-mapped image */
        if (tga_head.CoMapType != 1)
            pm_error(
                "mapped image (type %d) with color map type != 1",
                tga_head.ImgType );
        mapped = true;
        /* Figure maxval from CoSize. */
        switch (tga_head.CoSize) {
        case 8:
        case 24:
        case 32:
            maxval = 255;
            break;

        case 15:
        case 16:
            maxval = 31;
            break;

        default:
        pm_error(
        "unknown colormap pixel size - %d", tga_head.CoSize );
        }
    } else {
        /* Not colormap, so figure maxval from PixelSize. */
        mapped = false;
        switch ( tga_head.PixelSize ) {
        case 8:
        case 24:
        case 32:
            maxval = 255;
            break;

        case 15:
        case 16:
            maxval = 31;
            break;

        default:
            pm_error("unknown pixel size - %d", tga_head.PixelSize);
        }
    }

    /* If required, read the color map information. */
    if ( tga_head.CoMapType != 0 ) {
        unsigned int i;
        unsigned int temp1, temp2;

        temp1 = tga_head.Index_lo + tga_head.Index_hi * 256;
        temp2 = tga_head.Length_lo + tga_head.Length_hi * 256;
        if ((temp1 + temp2 + 1) >= MAXCOLORS)
            pm_error("too many colors - %d", (temp1 + temp2 + 1));
        for (i = temp1; i < (temp1 + temp2); ++i)
            get_map_entry(ifP, &ColorMap[i], (int) tga_head.CoSize,
                          &AlphaMap[i]);
    }

    /* Check run-length encoding. */
    if (tga_head.ImgType == TGA_RLEMap ||
        tga_head.ImgType == TGA_RLERGB ||
        tga_head.ImgType == TGA_RLEMono)
        rlencoded = 1;
    else
        rlencoded = 0;

    /* Read the Targa file body and convert to portable format. */
    pixels = ppm_allocarray( cols, rows );
    alpha = pgm_allocarray( cols, rows );
    truerow = 0;
    baserow = 0;
    for (row = 0; row < rows; ++row) {
        unsigned int col;

        realrow = truerow;
        if (tga_head.OrgBit == 0)
            realrow = rows - realrow - 1;

        for (col = 0; col < cols; ++col)
            getPixel(ifP, &(pixels[realrow][col]), (int) tga_head.PixelSize,
                     &(alpha[realrow][col]));
        if (tga_head.IntrLve == TGA_IL_Four)
            truerow += 4;
        else if (tga_head.IntrLve == TGA_IL_Two)
            truerow += 2;
        else
            ++truerow;
        if (truerow >= rows)
            truerow = ++baserow;
    }
    pm_close(ifP);

    if (imageout_file)
        ppm_writeppm(imageout_file, pixels, cols, rows, (pixval) maxval, 0);
    if (alpha_file)
        pgm_writepgm(alpha_file, alpha, cols, rows, (pixval) maxval, 0);
    if (imageout_file)
        pm_close(imageout_file);
    if (alpha_file)
        pm_close(alpha_file);

    return 0;
}



