/*****************************************************************
 * fltga.c:  FBM Release 1.0 25-Feb-90 Ian MacPhedran
 *
 * Author Ian MacPhedran.
 * Permission is given to use any portion of this file, (including
 * its entirety) for whatever you wish. Howvever, please note that
 * it was written for Michael Mauldin's FBM Library, and conditions
 * therein are more restrictive.
 *
 * FBM is Copyright (C) 1989,1990 by Michael Mauldin.  Permission
 * is granted to use this file in whole or in part for any purpose,
 * educational, recreational or commercial, provided that this
 * copyright notice is retained unchanged.  This software is
 * available to all free of charge by anonymous FTP and in the
 * UUNET archives.
 *
 * CONTENTS
 *    read_tga (image, rfile, mstr, mlen)
 *    write_tga (image, wfile)
 *
 * HISTORY
 * 25-Jun-90  Michael Mauldin (mlm@cs.cmu.edu) Carnegie Mellon
 *	Package for Release 1.0
 *
 * 13-Mar-89  Ian J. MacPhedran
 *	Add write_tga
 *
 * 07-Mar-89  Ian J. MacPhedran, University of Saskatchewan.
 *	Created.
 *****************************************************************/

#include <stdio.h>
#include "fbm.h"

#ifndef lint
static char *fbmid =
"$FBM fltga.c <1.0> 25-Jun-90  by Ian MacPhedran, source code available \
free from MLM@CS.CMU.EDU and from UUNET archives$";
#endif

/* For convenience, the TGA header file is included herein. */

/*
 * Header file for Targa file definitions.
 *
 * These definitions will allow a consistant interface to build Targa (.TGA)
 * image files.
 *
 * Created NOV-15-1988 IJMP
 *
 */

/* File header definition */

struct TGA_ImageHeader {
                unsigned char IDLength;/* Length of Identifier String */
                unsigned char CoMapType; /* 0 = NoMap */
                unsigned char ImgType;  /* Image Type (1,2,3,9,10) */
                unsigned char Index_lo, Index_hi;
                        /* Index of first colour map entry */
                unsigned char Length_lo, Length_hi;
                        /* Length of colour map (number of entries) */
                unsigned char CoSize;   /* Length of colour map entry */
                unsigned char X_org_lo, X_org_hi;       /* X Origin of Image */
                unsigned char Y_org_lo, Y_org_hi;       /* Y Origin of Image */
                unsigned char Width_lo, Width_hi;       /* Width of Image */
                unsigned char Height_lo, Height_hi;     /* Height of Image */
                unsigned char PixelSize;        /* Pixel Size (8,16,24) */
                unsigned AttBits : 4;   /* Number of Attribute Bits per pixel */
                unsigned Rsrvd   : 1;   /* Reserved bit */
                unsigned OrgBit  : 1;
                        /* Origin Bit (0=lower left, 1=upper left) */
                unsigned IntrLve : 2;   /* Interleaving Flag */
                };

char TGA_ImageIDField[256];

/* Definitions for Image Types */

#define TGA_MapRGBType 1
#define TGA_RawRGBType 2
#define TGA_RawMonoType 3
#define TGA_MapEnCodeType 9
#define TGA_RawEnCodeType 10

/*
 * read_tga(image, rfile, mstr, mlen)
 * from tga2rast.c:
 * Version 1.0 - first released for public consumption, 21 Feb, 1989
 *
 */

#define MAXCOLOURS 16384

/* Define flags for mode - these indicate special conditions */
#define GREYSC 0
#define COLOUR 1
#define MAPPED 2
#define RLENCD 4
#define INTERL 8
#define FOURWY 16

unsigned char ColourMap[MAXCOLOURS][3];
int RLE_count=0,RLE_flag=0;

read_tga(image, rfile, mstr, mlen)
FBM *image;
FILE *rfile;
char *mstr;
int mlen;
{
/* Define Identifiers */
struct TGA_ImageHeader  *tga;
int                     i, j, l;
unsigned int            temp1, temp2, mode;
unsigned char           r, g, b;
unsigned long           k, baseline,linewidth;
unsigned char           *Red, *Grn, *Blu, *Redk, *Grnk, *Bluk;

/* Input the Targa file header */
        if ((tga=(struct TGA_ImageHeader *)
                malloc(sizeof(struct TGA_ImageHeader))) == NULL)
        {
                fprintf(stderr,"Can't allocate TGA memory\n");
                exit(1);
        }

        if ((i = fread(tga,1,18,rfile)) != 18)
        {
                fprintf(stderr,"Read only %d bytes in header\n",i);
                exit(1);
        }
        switch (tga->ImgType)
        {
                case TGA_MapRGBType:
                case TGA_RawRGBType:
                case TGA_RawMonoType:
                case TGA_MapEnCodeType:
                case TGA_RawEnCodeType:
                        break;

                default:
                        fprintf(stderr,"Targa File Type %d",tga->ImgType);
                        fprintf(stderr," not supported!\n");
                        exit(1);
        }

/* Create output image header */
        temp1 = tga->Height_lo; temp2 = tga->Height_hi;
        image->hdr.rows = temp1 + temp2 * 256;
        temp1 = tga->Width_lo; temp2 = tga->Width_hi;
        image->hdr.cols = temp1 + temp2 * 256;
        /* If this is odd number of bytes, add one */
        if ((image->hdr.cols & 1) != 0) image->hdr.cols++;

/* If greyscale, use only one plane */
        if (tga->ImgType == TGA_RawMonoType)
        {
                image->hdr.planes = 1;
                mode = GREYSC;
        }
        else
        {
                image->hdr.planes = 3;
                mode = COLOUR;
        }

/* Uses 8 bits, sort of - 16 bits/pixel is 5 bits per colour */
        image->hdr.bits = 8;
        image->hdr.physbits = 8;
        image->hdr.rowlen = image->hdr.cols;
        image->hdr.plnlen = image->hdr.rows * image->hdr.cols;
/* Ignore colour map for this version. */
        image->hdr.clrlen = 0;
        image->hdr.aspect = 1.0;
        image->hdr.title[0] = '\0';
        image->hdr.credits[0] = '\0';

/* Get the Image */
        alloc_fbm(image);

/* Read ID String, if present */
        if (tga->IDLength != 0)
                fread(TGA_ImageIDField,1,tga->IDLength,rfile);

/* If present, read the colour map information */
        if (tga->CoMapType != 0)
        {
                temp1 = tga->Index_lo + tga->Index_hi * 256;
                temp2 = tga->Length_lo + tga->Length_hi * 256;
                if ((temp1+temp2+1) >= MAXCOLOURS)
                {
                        fprintf(stderr,"Too many colours %d\n",(temp1+temp2+1));
                        exit(1);
                }
                for (i=temp1; i<(temp1+temp2); i++)
                        get_map_entry(&ColourMap[i][0],&ColourMap[i][1],
                        &ColourMap[i][2],tga->CoSize,mode);
                if ((tga->ImgType != TGA_RawRGBType) &&
                        (tga->ImgType != TGA_RawMonoType) &&
                        (tga->ImgType != TGA_RawEnCodeType))
                        mode = mode | MAPPED;
        }

/* Check Run Length Encoding */
        if ((tga->ImgType == TGA_MapEnCodeType) ||
                (tga->ImgType == TGA_RawEnCodeType))
                mode = mode | RLENCD;

/* Check for interlacing of the Targa file */
        switch (tga->IntrLve)
        {
                case 2: /* Four way interlace */
                        mode = mode | FOURWY;
                case 1: /* Two way interlace */
                        mode = mode | INTERL;
                case 0: /* No interlace */
                        break;
                default: /* Reserved - we'll let it pass */
                        break;
        }

/* Set up byte map for writing */

        Red = image->bm;
        if ((mode & COLOUR) != GREYSC)
        {
                Grn = Red + image->hdr.plnlen;
                Blu = Grn + image->hdr.plnlen;
        }
/* Read the Targa file body and convert to image format */
        linewidth = tga->Width_lo + tga->Width_hi * 256;
        for (i=0; i< image->hdr.rows; i++)
        {
                /* No interlace */
                if ((mode & INTERL) == 0)
                {
                        j = i;
                }
                /* Two way interlace */
                else if ((mode & FOURWY) != 0)
                {
                        if (2*i < image->hdr.rows)
                                j = 2*i;
                        else
                        {
                                j = i - image->hdr.rows/2;
                                j = 2*j + 1;
                        }
                }
                /* Four way interlace */
                else
                {
                        if (4*i < image->hdr.rows)
                                j = 4*i;
                        else if (2*i < image->hdr.rows)
                        {
                                j = i - image->hdr.rows/4;
                                j = 4*j + 1;
                        }
                        else if (4*i < 3*image->hdr.rows)
                        {
                                j = i - image->hdr.rows/2;
                                j = 4*j + 2;
                        }
                        else
                        {
                                j = i - image->hdr.rows/2 - image->hdr.rows/4;
                                j = 4*j + 3;
                        }
                }
                k = (image->hdr.rows - 1 - j) * image->hdr.cols;
                Redk = Red + k;
                if ((mode & COLOUR) != GREYSC)
                {
                        Grnk = Grn + k; Bluk = Blu + k;
                }
                for (j=0; j<linewidth; j++)
                {
                        get_pixel(&r,&g,&b,tga->PixelSize,mode);
                        *Redk++=r;
                        if ((mode & COLOUR) != GREYSC)
                        {
                                *Grnk++=g; *Bluk++=b;
                        }
                }
        }
        free(tga);
}


get_map_entry(Red,Grn,Blu,Size,mode)
unsigned char     *Red,*Grn,*Blu;
int     Size,mode;
{
unsigned int j,k,l,m;
unsigned char i,r,g,b;

        /* read appropriate number of bytes, break into rgb & put in map */
        switch (Size)
        {
                case 8: /* Grey Scale already, read and triplicate */
                        fread(&i,1,1,stdin);
                        r = i; g = i; b = i;
                        break;

                case 16: /* 5 bits each of red green and blue */
                case 15: /* Watch for byte order */
                        fread(&j,1,1,stdin);
                        fread(&k,1,1,stdin);
                        l = j + k*256;
                        r = ((l >> 10) & 31) << 3;
                        g = ((l >>  5) & 31) << 3;
                        b = (l & 31) << 3;
                        break;

                case 32: /* Read alpha byte & throw away */
                case 24: /* Eight bits each of red green and blue */
                        fread(&i,1,1,stdin); b = i;
                        fread(&i,1,1,stdin); g = i;
                        fread(&i,1,1,stdin); r = i;
                        if (Size == 32) fread(&i,1,1,stdin);
                        break;

                default:
                        fprintf(stderr,"Unknown Pixel Size\n"); exit(1);
        }
        *Red = r; *Grn = g; *Blu = b;
}

get_pixel(rRed,rGrn,rBlu,Size,mode)
unsigned char *rRed,*rGrn,*rBlu;
int Size,mode;
{
        static unsigned char Red, Grn, Blu;
        unsigned char i,j,k;
        static unsigned int l;

        /* Check if run length encoded. */
        if ((mode & RLENCD) != 0)
        {
                if (RLE_count == 0) /* Have to restart run */
                {
                        fread(&i,1,1,stdin);
                        RLE_flag = (i & 0x80) >> 7;
                        if (RLE_flag == 0)
                        { /* Stream of unencoded pixels */
                                RLE_count = i + 1;
                        }
                        else
                        { /* Single pixel replicated */
                                RLE_count = i - 127;
                        }
                        RLE_count--; /* Decrement count & get pixel */
                }
                else
                { /* Have already read count & (at least) first pixel */
                        RLE_count--;
                        if (RLE_flag != 0)
                        { /* Replicated pixels */
                                goto PixEncode;
                        }
                }
        }
        /* Read appropriate number of bytes, break into RGB */
        switch(Size)
        {
        case 8: /* Grey Scale - read a byte and triplicate */
                fread(&i,1,1,stdin);
                Red = i; Grn = i; Blu = i; l = i;
                break;

        case 16: /* Five bits each of red green and blue */
        case 15: /* Watch byte order */
                fread(&j,1,1,stdin);
                fread(&k,1,1,stdin);
                l = j + k*256;
                Red = ((k & 0x7C) << 1);
                Grn = ((k & 0x03) << 6) + ((j & 0xE0) >> 2);
                Blu = ((j & 0x1F) << 3);
                break;

        case 32: /* Read alpha byte & throw away */
        case 24: /* Eight bits each of red green and blue */
                fread(&i,1,1,stdin); Blu = i;
                fread(&i,1,1,stdin); Grn = i;
                fread(&i,1,1,stdin); Red = i;
                if (Size == 32) fread(&i,1,1,stdin);
                l = 0;
                break;

        default:
                fprintf(stderr,"Unknown Pixel Size\n"); exit(1);
        }

PixEncode:
        if ((mode & MAPPED) == MAPPED)
        {
                *rRed = ColourMap[l][0];
                *rGrn = ColourMap[l][1];
                *rBlu = ColourMap[l][2];
        }
        else
        {
                *rRed = Red;
                *rGrn = Grn;
                *rBlu = Blu;
        }
}

/*
 * write_tga(image, wfile)
 *
 */
write_tga(image, wfile)
FBM *image;
FILE *wfile;
{
unsigned char           *Red, *Grn, *Blu, *Redk, *Grnk, *Bluk;
unsigned char           *Redc, *Grnc, *Bluc, *Redck, *Grnck, *Bluck;
struct TGA_ImageHeader  *tga;
unsigned char           buffer[MAXCOLOURS];
unsigned int            mode;
unsigned long           index, index2;
int                     i, j, k, l;

        if (image->hdr.cols > (MAXCOLOURS / 2))
        {
                fprintf(stderr,"Line too wide is %d, must be %d\n",
                image->hdr.cols, MAXCOLOURS/2);
                exit(1);
        }

        if ((image->hdr.planes != 1) && (image->hdr.planes != 3))
        {
                fprintf(stderr,"TGA files must 1 or 3 planes deep\n");
                exit(1);
        }
        if (image->hdr.planes == 1) mode = GREYSC;
        else mode = COLOUR;

        if ((tga=(struct TGA_ImageHeader *)
                malloc(sizeof(struct TGA_ImageHeader))) == NULL)
        {
                fprintf(stderr,"Can't allocate TGA memory\n");
                exit(1);
        }
        tga->IDLength = 0; /* Don't write ID into file */
        tga->CoMapType = 0; /* Use raw bytes, not mapped */

        tga->ImgType = TGA_RawRGBType;
        if ((mode & COLOUR) == GREYSC) tga->ImgType = TGA_RawMonoType;

        tga->Index_hi = 0; tga->Index_lo = 0; /* Colour Mapped stuff */
        tga->Length_hi = 0; tga->Length_lo = 0;
        tga->CoSize = 8;

        tga->X_org_lo = 0; tga->X_org_hi = 0; /* Origin at 0,0 */
        tga->Y_org_lo = 0; tga->Y_org_hi = 0;

        tga->Width_hi = (unsigned char)(image->hdr.cols / 256);
        tga->Width_lo = (unsigned char)(image->hdr.cols % 256);
        tga->Height_hi = (unsigned char)(image->hdr.rows / 256);
        tga->Height_lo = (unsigned char)(image->hdr.rows % 256);

        tga->PixelSize = 16;
        if ((mode & COLOUR) == GREYSC) tga->PixelSize = 8;

        /* All funny bits set to zero */
        tga->AttBits = 0; tga->Rsrvd = 0; tga->OrgBit = 0; tga->IntrLve = 0;

        fwrite(tga,1,18,wfile); /* Write out header */

        Red = image->bm;
        l = image->hdr.cols;
        if ((mode & COLOUR) == COLOUR)
        {
                l = l * 2;
                Grn = Red + image->hdr.plnlen;
                Blu = Grn + image->hdr.plnlen;
        }

        if (image->hdr.clrlen > 0)
        {
                mode = mode | MAPPED;
                Redc = image->cm;
                if ((mode & COLOUR) == COLOUR)
                {
                        Grnc = Redc + image->hdr.clrlen/3;
                        Bluc = Grnc + image->hdr.clrlen/3;
                }
        }

        Redk = Red + image->hdr.plnlen - image->hdr.cols;
        if ((mode & COLOUR) == COLOUR)
        {
                Grnk = Grn + image->hdr.plnlen - image->hdr.cols;
                Bluk = Blu + image->hdr.plnlen - image->hdr.cols;
        }

        /* Okay, ready to write */
        for (j=0; j<image->hdr.rows; j++)
        {
        for (i=0; i<image->hdr.cols; i++)
        {
                index = *Redk++;
                if ((mode & COLOUR) == COLOUR)
                        index = (index << 16) + ((*Grnk++) << 8) +  *Bluk++;

                if ((mode & MAPPED) == MAPPED)
                {
                        index2 = index;
                        Redck = Redc + index2;
                        index = *Redck;
                        if ((mode & COLOUR) == COLOUR)
                        {
                                Grnck = Grnc + index2; Bluck = Bluc + index2;
                                index = (index << 16) +
                                        ((unsigned long)*Grnck << 8) +
                                        *Bluck;
                        }
                }

                if ((mode & COLOUR) == COLOUR)
                {
                        index2 = ((index & 0x00f80000) >> 9)
                               + ((index & 0x0000f800) >> 6)
                               + ((index & 0x000000f8) >> 3);
                        k = 2 * i;
                        buffer[k] = (unsigned char)(index2 % 256);
                        k = k + 1;
                        buffer[k] = (unsigned char)(index2 / 256);
                }
                else
                {
                        buffer[i] = (unsigned char)index;
                }
        }
        fwrite(buffer,l,1,wfile);
        Redk = Redk - 2 * image->hdr.cols;
        if ((mode & COLOUR) == COLOUR)
        {
                Grnk = Grnk - 2 * image->hdr.cols;
                Bluk = Bluk - 2 * image->hdr.cols;
        }
        }
        free(tga);
}
