/**************************************************************************
 *{@C
 *      Copyright:      1988-2023 Paul Obermeier (obermeier@poSoft.de)
 *
 *      Module:         FileFormats
 *      Filename:       FF_ImageTarga.c
 *
 *      Author:         Paul Obermeier
 *
 *      Description:    The file format dependent layer of the image file
 *                      format library: Functions to read and write
 *                      files in Truevision's TARGA pixel file format.
 *                      The following TARGA image types are supported:
 *
 *                          Type  2: Uncompressed RGB image
 *                          Type 10: Run-length encoded RGB image
 *
 *                      Both types may have a transparency channel.
 *
 *      Additional documentation:
 *                      None.
 *
 *      Exported functions:
 *                      FF_ImgOpenWriteTarga
 *                      FF_ImgOpenReadTarga
 *
 **************************************************************************/

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

#include "UT_Compat.h"
#include "UT_Error.h"
#include "UT_Portable.h"
#include "UT_Memory.h"
#include "UT_FileIO.h"

#include "FF_Image.h"
#include "FF_ImagePrivate.h"
#include "FF_ImageTarga.h"

#define MinRun 3
#define MaxRun 127

/* Macros for reading from the pixbuf */

#define FROM_PIXBUF                     \
    {   *(blue++)  = pixbuf[0];         \
        *(green++) = pixbuf[1];         \
        *(red++)   = pixbuf[2];         \
        if (tf->nchan == 4)             \
            *(matte++) = pixbuf[3];     \
    }

static UInt8
        pixbuf[4],      /* Buffer for one pixel */
        *rowbuf,        /* Scanline pointers for reading uncompresses pixel values */
        *red,           /* Scanline pointers for stepping through */
        *green,
        *blue,
        *matte;

static UT_Bool          
        left_right = UT_True;   /* Are scanlines oriented from left to right 
                                   or vice versa */

#if defined (DEBUG_LOCAL)
    static Int32 
        total;
#endif

/* Close a pixel file previously opened with
   FF_ImgOpenWriteTarga or FF_ImgOpenReadTarga. */

static void tga_close (FF_ImgFileDesc *px_f)
{
    tgafile *tf;
    tf = ((pixfile *)px_f->pixprivate)->fmtprivate;
    UT_FileClose (tf->file);
    UT_MemFree (tf->rowbuf);
    UT_MemFree (tf->red);
    UT_MemFree (tf->green);
    UT_MemFree (tf->blue);
    UT_MemFree (tf->matte);
    UT_MemFree (tf);
    return;
}

static UT_Bool read_error (FILE *file)
{
    if (feof (file))
        UT_ErrSetNum (UT_ErrUnexpEof, "fread");
    else
        UT_ErrSetNum (UT_ErrFromOs (), "fread");
    return UT_False;
}

static UT_Bool write_error (void)
{
    UT_ErrSetNum (UT_ErrFromOs (), "fwrite");
    return UT_False;
}

/* Read 1 byte, representing an unsigned integer number. */

static UT_Bool read_UInt8 (FILE *file, UInt8 *b)
{
    if (1 != fread (b, sizeof(*b), 1, file))
        return read_error (file);
    return UT_True;
}

/* Read 2 bytes, representing a short integer in the form <LowByte, HighByte>, 
   from a file and convert them into the current machine's format. */

static UT_Bool read_Int16 (FILE *file, Int16 *s)
{
    Int8 buf[2];
    if (1 != fread (buf, sizeof(buf), 1, file))
        return read_error (file);
    *s = (buf[0] & 0xFF) | (buf[1] << 8);
    return UT_True;
}

/* Read 4 bytes, representing a floating point number in a machine-independent
   format, from a file and convert them into the current machine's format. */

static UT_Bool read_Float32 (FILE *file, Float32 *f)
{
    Int8 buf[4];
    if (1 != fread (buf, sizeof(buf), 1, file))
        return read_error (file);
    *f = UT_PortByteToFloat32 (buf);
    return UT_True;
}

/* Write a byte, representing an unsigned integer to a file. */

static UT_Bool write_UInt8 (FILE *file, UInt8 b)
{
    UInt8 buf[1];
    buf[0] = b;
    if (1 != fwrite (buf, sizeof(buf), 1, file))
        return write_error ();
    return UT_True;
}

/* Write a byte, representing a signed integer to a file. */

static UT_Bool write_Int8 (FILE *file, Int8 b)
{
    Int8 buf[1];
    buf[0] = b;
    if (1 != fwrite (buf, sizeof(buf), 1, file))
        return write_error ();
    return UT_True;
}

/* Convert a short integer number into the format <LowByte, HighByte> (an array
   of 2 bytes) and write the array to a file. */

static UT_Bool write_Int16 (FILE *file, Int16 s)
{
    Int8 buf[2];
    buf[0] = s;
    buf[1] = s >> 8;
    if (1 != fwrite (buf, sizeof(buf), 1, file))
        return write_error ();
    return UT_True;
}

/* Convert a floating point number into a machine-independent format (an array
   of 4 bytes) and write the array to a file. */

static UT_Bool write_Float32 (FILE *file, Float32 f)
{
    Int8 buf[4];
    UT_PortFloat32ToByte (f, buf);
    if (1 != fwrite (buf, sizeof(buf), 1, file))
        return write_error ();
    return UT_True;
}

/* Read the id data, which is located between file header and pixel data.
   We use this - up to 256 byte long - space for internal storage of gamma
   factor, aspect ratio and white point.
   If the information read is not useful, we skip the id_data. */

static UT_Bool read_id_data (FILE *file, FF_ImgFileDesc *px_f)
{
    Int16 magic;

    if (!read_Int16 (file, &magic) ||
        magic != TGA_int_magic)
        return UT_False;
    if (!read_Float32 (file, &px_f->desc.gamma) ||
        !read_Float32 (file, &px_f->desc.aspect) ||
        !read_Float32 (file, &px_f->desc.red.x) ||
        !read_Float32 (file, &px_f->desc.red.y) ||
        !read_Float32 (file, &px_f->desc.green.x) ||
        !read_Float32 (file, &px_f->desc.green.y) ||
        !read_Float32 (file, &px_f->desc.blue.x) ||
        !read_Float32 (file, &px_f->desc.blue.y) ||
        !read_Float32 (file, &px_f->desc.white.x) ||
        !read_Float32 (file, &px_f->desc.white.y) ||
        px_f->desc.gamma <= 0.01 || px_f->desc.gamma > 100.0 ||
        px_f->desc.aspect <= 0.01 || px_f->desc.aspect > 100.0 ||
        px_f->desc.red.x < 0.0 || px_f->desc.red.x > 1.0 ||
        px_f->desc.red.y < 0.0 || px_f->desc.red.y > 1.0 ||
        px_f->desc.green.x < 0.0 || px_f->desc.green.x > 1.0 ||
        px_f->desc.green.y < 0.0 || px_f->desc.green.y > 1.0 ||
        px_f->desc.blue.x < 0.0 || px_f->desc.blue.x > 1.0 ||
        px_f->desc.blue.y < 0.0 || px_f->desc.blue.y > 1.0 ||
        px_f->desc.white.x < 0.0 || px_f->desc.white.x > 1.0 ||
        px_f->desc.white.y < 0.0 || px_f->desc.white.y > 1.0)
        return UT_False;
    return UT_True;
}

/* Write the id data */

static UT_Bool write_id_data (FILE *file, FF_ImgFileDesc *px_f)
{
    if (!write_Int16 (file, TGA_int_magic) ||
        !write_Float32 (file, px_f->desc.gamma) ||
        !write_Float32 (file, px_f->desc.aspect) ||
        !write_Float32 (file, px_f->desc.red.x) ||
        !write_Float32 (file, px_f->desc.red.y) ||
        !write_Float32 (file, px_f->desc.green.x) ||
        !write_Float32 (file, px_f->desc.green.y) ||
        !write_Float32 (file, px_f->desc.blue.x) ||
        !write_Float32 (file, px_f->desc.blue.y) ||
        !write_Float32 (file, px_f->desc.white.x) ||
        !write_Float32 (file, px_f->desc.white.y))
        return UT_False;
    return UT_True;
}

/* Write the TARGA pixel file header. 
   If the format version is "ext", we write additional infomation into 
   the id_data field. */

static UT_Bool write_header (FF_ImgFileDesc *px_f)
{
    tgafile *tf;
    tgaheader th;

    tf = ((pixfile *)px_f->pixprivate)->fmtprivate;
    if (!strcmp (px_f->version, str_int_version))
        th.numid = TGA_iddata_len; 
    else
        th.numid = 0;
    th.maptyp = 0;
    th.imgtyp = px_f->compression[FF_ImgChanTypeRed] == FF_ImgComprTypeNone? 
                TGA_RGB_uncomp: TGA_RGB_comp;
    th.maporig = 0;
    th.mapsize = 0;
    th.mapbits = 0;
    th.xorig = 0;
    th.yorig = 0;
    th.xsize = px_f->desc.width;
    th.ysize = px_f->desc.height;
    th.pixsize = px_f->channel[FF_ImgChanTypeMatte]? 32: 24;
    th.imgdes = 0;              /* Origin is lower left */

    if (!write_UInt8 (tf->file, th.numid) ||
        !write_UInt8 (tf->file, th.maptyp) ||
        !write_UInt8 (tf->file, th.imgtyp) ||
        !write_Int16 (tf->file, th.maporig) ||
        !write_Int16 (tf->file, th.mapsize) ||
        !write_UInt8 (tf->file, th.mapbits) ||
        !write_Int16 (tf->file, th.xorig) ||
        !write_Int16 (tf->file, th.yorig) ||
        !write_Int16 (tf->file, th.xsize) ||
        !write_Int16 (tf->file, th.ysize) ||
        !write_UInt8 (tf->file, th.pixsize) ||
        !write_UInt8 (tf->file, th.imgdes))
        return UT_False;
    if (th.numid > 0)
        if (!write_id_data (tf->file, px_f))
            return UT_False;
    return UT_True;
}

static UT_Bool read_header (FILE *file, const char name[], FF_ImgFileDesc *px_f)
{
    Int32 chn;
    UT_Bool int_version = UT_False;
    tgaheader th;

    if (!read_UInt8 (file, &th.numid) ||
        !read_UInt8 (file, &th.maptyp) ||
        !read_UInt8 (file, &th.imgtyp) ||
        !read_Int16 (file, &th.maporig) ||
        !read_Int16 (file, &th.mapsize) ||
        !read_UInt8 (file, &th.mapbits) ||
        !read_Int16 (file, &th.xorig) ||
        !read_Int16 (file, &th.yorig) ||
        !read_Int16 (file, &th.xsize) ||
        !read_Int16 (file, &th.ysize) ||
        !read_UInt8 (file, &th.pixsize) ||
        !read_UInt8 (file, &th.imgdes))
        return UT_False;

    /* Try to find out if this file can possibly be a TARGA pixel file. */
    if (!((th.imgtyp == TGA_RGB_uncomp || th.imgtyp == TGA_RGB_comp) &&
          (th.pixsize == 24 || th.pixsize == 32))) {
        UT_ErrSetNum (FF_ImgErrFormat, str_open_read, name);
        return UT_False;
    }

    /* If there is additional information in the id_data field, try to read it.
       If it could not be interpreted correctly, skip that data. */
    if (th.numid > 0) {
        if (th.numid != TGA_iddata_len || !read_id_data (file, px_f)) {  
            rewind (file);
            fseek (file, TGA_hdr_len + th.numid, 0);
            int_version = UT_False;
        } else {
            int_version = UT_True;
            strcpy (px_f->version, str_int_version);
        }
    }

    /* If not the internal version, fill the image descriptor 
       with default values. */
    if (!int_version) {
        px_f->desc.red.x = 0.670f;
        px_f->desc.red.y = 0.330f;
        px_f->desc.green.x = 0.210f;
        px_f->desc.green.y = 0.710f;
        px_f->desc.blue.x = 0.140f;
        px_f->desc.blue.y = 0.080f;
        px_f->desc.white.x = 0.313f;
        px_f->desc.white.y = 0.329f;
        px_f->desc.aspect = (Float32)th.xsize / (Float32)th.ysize;      
        px_f->desc.gamma  = 2.0;
        strcpy (px_f->version, str_ext_version);
    }

    if (th.xsize < 2 || th.ysize < 2) {
        UT_ErrSetNum (FF_ImgErrSyntax, str_header);
        return UT_False;
    }

    px_f->scanorder = ((th.imgdes >> 5) & 0x1)? 
                      FF_ImgOrderTypeTopDown: 
                      FF_ImgOrderTypeBottomUp;
    left_right = ((th.imgdes >> 4) & 0x1)? UT_False: UT_True;
    px_f->desc.width = th.xsize;
    px_f->desc.height = th.ysize;
    for (chn=0; chn<FF_NumImgChanTypes; chn++) {
        switch (chn) {
            case FF_ImgChanTypeRed:
            case FF_ImgChanTypeGreen:
            case FF_ImgChanTypeBlue:
                px_f->channel[chn] = FF_ImgFmtTypeUByte;
                px_f->compression[chn] = th.imgtyp == TGA_RGB_uncomp? 
                                         FF_ImgComprTypeNone: FF_ImgComprTypeRle;
                break;
            case FF_ImgChanTypeMatte:
                if (th.pixsize == 32) {
                    px_f->channel[chn] = FF_ImgFmtTypeUByte;
                    px_f->compression[chn] = th.imgtyp == TGA_RGB_uncomp? 
                                             FF_ImgComprTypeNone: FF_ImgComprTypeRle;
                    break;
                }
            default:
                px_f->channel[chn] = FF_ImgFmtTypeNone;
                px_f->compression[chn] = FF_ImgComprTypeNone;
                break;
        }
    }

    /* Skip colormap data, if present. */
    if (th.mapsize > 0) {
        Int32   mapbytes;
        UInt8 dummy;
        switch (th.mapbits) {
            case 15:
            case 16:
                mapbytes = 2 * th.mapsize;
                break;
            case 24:
                mapbytes = 3 * th.mapsize;
                break;
            case 32:
                mapbytes = 4 * th.mapsize;
                break;
            default:
                UT_ErrSetNum (FF_ImgErrSyntax, str_colormap, th.mapbits);
                return UT_False;
        }
        while (mapbytes--) {
            if (!read_UInt8 (file, &dummy)) {
                return UT_False;
            }
        }
    }
    return UT_True;
}

static UT_Bool write_pixel (FILE *fp, UInt8 b, UInt8 g, 
                         UInt8 r, UInt8 m, Int32 nchan)
{
    UInt8 buf[4];
    buf[0] = b;
    buf[1] = g;
    buf[2] = r;
    buf[3] = m;
    if (nchan != fwrite (buf, sizeof (buf[0]), nchan, fp))
        return write_error ();
    return UT_True;
}

/* Write a scanline of channel "type". */

static void write_channel (const FF_ImgFileDesc *px_f, const pixfile *pf, 
                           FF_ImgChanType chantype, 
                           UInt8 *dest, UInt8 *stop)
{
    switch (px_f->channel[chantype])
    {
        case FF_ImgFmtTypeUByte:
        {
            UInt8 *src = pf->buf[chantype].ubytes;
            while (dest < stop)
                *(dest++) = *(src)++;
            break;
        }
        case FF_ImgFmtTypeFloat:
        {
            Float32 *src = pf->buf[chantype].floats;
            while (dest < stop)
                *(dest++) = (UInt32)(*(src++) * 255.0);
            break;
        }
        default:
            while (dest < stop)
                *(dest++) = 0;
    }
    return;
}

/* All channels of scan line number y for pixel file "file" are stored
   in the file. Note that the TARGA file format doesn't support the
   FF_ImgOrderTypeRandom scanline order. 
   So we write TARGA files in FF_ImgOrderTypeBottomUp order. */

static UT_Bool tga_write_scan (FF_ImgFileDesc *px_f, Int32 y)
{
    pixfile *pf;
    tgafile *tf;
    UInt8 *dest, *stop, *red_end, *green_end, *blue_end, *matte_end;

    pf = px_f->pixprivate;
    tf = pf->fmtprivate;
    /* Check if the parameters supplied are reasonable. */
    if (pf->readfile)
        UT_ErrFatal ("tga_write_scan", "pixel file is read-only");
    if (tf->lastscan != y - 1)
        UT_ErrFatal ("tga_write_scan", "wrong scan line order");
    /* Collect the data for the red, green, blue and matte channels 
       and store them in the intermediate buffers. */
    dest = tf->red;
    stop = dest + px_f->desc.width;
    write_channel (px_f, pf, FF_ImgChanTypeRed, dest, stop);

    dest = tf->green;
    stop = dest + px_f->desc.width;
    write_channel (px_f, pf, FF_ImgChanTypeGreen, dest, stop);

    dest = tf->blue;
    stop = dest + px_f->desc.width;
    write_channel (px_f, pf, FF_ImgChanTypeBlue, dest, stop);

    if (tf->nchan == 4)
    {
        dest = tf->matte;
        stop = dest + px_f->desc.width;
        write_channel (px_f, pf, FF_ImgChanTypeMatte, dest, stop);
    }

    red = tf->red;
    green = tf->green;
    blue = tf->blue;
    matte = tf->matte;
    stop = red + px_f->desc.width;

    /* Write the scanline data to the file. */
    if (px_f->compression[FF_ImgChanTypeRed] == FF_ImgComprTypeNone)
    {
        while (red < stop)
        {
            if (!write_pixel (tf->file, *blue, *green, *red, *matte, tf->nchan))
                return UT_False;
            blue++;
            green++;
            red++;
            matte++;
        }
    }
    else        /* Run-length Compression */
    {
        red_end = red + 1;
        green_end = green + 1;
        blue_end = blue + 1;
        matte_end = matte + 1;
        while (red < stop)
        {
            while (red_end < stop &&
                   *red == *red_end &&
                   *green == *green_end &&
                   *blue == *blue_end &&
                   red_end - red - 1 < MaxRun)
            {
                if (tf->nchan == 4)
                {
                    if (*matte != *matte_end)
                        break;
                }
                red_end++;
                green_end++;
                blue_end++;
                matte_end++;
            }
            if (red_end - red >= MinRun)
            {   /* Found a run of compressable data */
                if (!write_Int8 (tf->file, (Int8)(((red_end - red)-1)|0x80)) ||
                    !write_pixel (tf->file, *blue, *green, 
                                  *red, *matte, tf->nchan))
                    return UT_False;
                red = red_end;
                green = green_end;
                blue = blue_end;
                matte = matte_end;
            }
            else
            {   /* Found a run of uncompressable data */
                while (red_end < stop &&
                       ((red_end + 1 >= stop ||
                        *red_end != *(red_end + 1)) ||
                        (red_end + 2 >= stop ||
                        *(red_end + 1) != *(red_end + 2))) &&
                       ((green_end + 1 >= stop ||
                        *green_end != *(green_end + 1)) ||
                        (green_end + 2 >= stop ||
                        *(green_end + 1) != *(green_end + 2))) &&
                       ((blue_end + 1 >= stop ||
                        *blue_end != *(blue_end + 1)) ||
                        (blue_end + 2 >= stop ||
                        *(blue_end + 1) != *(blue_end + 2))) &&
                        red_end - red < MaxRun)
                {
                    if (tf->nchan == 4)
                    {
                        if (! ((matte_end + 1 >= stop ||
                               *matte_end != *(matte_end + 1)) ||
                               (matte_end + 2 >= stop ||
                               *(matte_end + 1) != *(matte_end + 2))))
                            break;
                    }
                    red_end++;
                    green_end++;
                    blue_end++;
                    matte_end++;
                }
                if (!write_Int8 (tf->file, (Int8)((red_end - red) - 1)))
                    return UT_False;
                while (red < red_end)
                {
                    if (!write_pixel (tf->file, *blue, *green, 
                                      *red, *matte, tf->nchan))
                        return UT_False;
                    red++;
                    green++;
                    blue++;
                    matte++;
                }
            }
            red_end++;
            green_end++;
            blue_end++;
            matte_end++;
        }
    }
    tf->lastscan = y;
    return UT_True;
}

static void read_channel (const FF_ImgFileDesc *px_f, const pixfile *pf,
                          FF_ImgChanType chantype, 
                          const UInt8 *src, Int32 n)
{
    const UInt8 *stop = src + n;
    if (left_right)
    {
        switch (px_f->channel[chantype])
        {
            case FF_ImgFmtTypeNone: {
                break;
            }
            case FF_ImgFmtTypeUByte:
            {
                UInt8 *dest = pf->buf[chantype].ubytes;
                while (src < stop)
                    *(dest++) = *(src++);
                break;
            }
            case FF_ImgFmtTypeFloat:
            {
                Float32 *dest = pf->buf[chantype].floats;
                while (src < stop)
                    *(dest++) = (Float32)(UInt32)*(src++) * (1.0 / 255.0);
                break;
            }
        }
    }
    else
    {
        switch (px_f->channel[chantype])
        {
            case FF_ImgFmtTypeNone: {
                break;
            }
            case FF_ImgFmtTypeUByte:
            {
                UInt8 *dest = pf->buf[chantype].ubytes + n - 1;
                while (src < stop)
                    *(dest--) = *(src++);
                break;
            }
            case FF_ImgFmtTypeFloat:
            {
                Float32 *dest = pf->buf[chantype].floats + n - 1;
                while (src < stop)
                    *(dest--) = (Float32)(UInt32)*(src++) * (1.0 / 255.0);
                break;
            }
        }
    }
    return;
}


/* Read one pixel value from file "fp". 
   A pixel is represented by 3 or 4 bytes in the order Blue/Green/Red/Matte. 
   The pixel value should be repeated in the scanline "n" times.
   Note that TARGA allows pixel values to be compressed across scanline
   boundaries. */

static UT_Bool read_pixels (UInt8 *stop, Int32 n, tgafile *tf)
{
    Int32 i;

    if (tf->nchan != fread (pixbuf, sizeof (pixbuf[0]), 
                            tf->nchan, tf->file))
        return read_error (tf->file);
    for (i=0; i<n; i++)
    {
        #if defined (DEBUG_LOCAL)
            total++;
        #endif
        FROM_PIXBUF;

        if (red == stop)
        {   /* Scanline is filled with pixel values.
               Determine the number of pixels to keep for next scanline. */
            tf->scanrest = n - i - 1;
            return UT_True;
        }
    }
    return UT_True;
}

/* The channels of scan line number "y" of the pixel file "px_f" are read.
   Note that the TARGA file format only supports FF_ImgOrderTypeTopDown or
   FF_ImgOrderTypeBottomUp scanline order! */

static UT_Bool tga_read_scan (FF_ImgFileDesc *px_f, Int32 y)
{
    pixfile *pf;
    tgafile *tf;
    UInt8   *stop;

    pf = px_f->pixprivate;
    tf = pf->fmtprivate;

    /* Check if the parameters supplied are reasonable. */
    if (!pf->readfile)
        UT_ErrFatal ("tga_read_scan", "pixel file is write-only");

    /* Read and decode the scan line data and store them in an
    intermediate buffer. */
    rowbuf = tf->rowbuf;
    red    = tf->red;
    green  = tf->green;
    blue   = tf->blue;
    matte  = tf->matte;
    stop = red + px_f->desc.width;
    #if defined (DEBUG_LOCAL)
        total = 0;
    #endif

    if (px_f->compression[FF_ImgChanTypeRed] == FF_ImgComprTypeRle)
    {   
        Int8 cbuf[1];
        Int32  pix, numpix;
        /* While there are pixels left from the previous scanline,
           either fill the current scanline with the pixel value
           still stored in "pixbuf" (TGA_mode_same) or read in the
           appropriate number of pixel values (TGA_mode_diff). */
        while (tf->scanrest)
        {
            if (tf->scanmode == TGA_mode_diff)
            {   
                if (tf->nchan != fread (pixbuf, sizeof (pixbuf[0]), 
                                        tf->nchan, tf->file))
                    return read_error (tf->file);
            }
            #if defined (DEBUG_LOCAL)
                total++;
            #endif
            FROM_PIXBUF;
            tf->scanrest--;
            /* If the image is small, the compression might go over several
               scanlines. */
            if (red == stop)
                goto write_scanline;
        }

        /* Read the byte telling us the compression mode and the compression
           count. Then read the pixel values till a scanline is filled. */
        do
        {
            if (1 != fread (cbuf, sizeof (cbuf), 1, tf->file))
                return read_error (tf->file);
            numpix = (cbuf[0] & 0x7F) + 1;

            if ((cbuf[0] & 0x80) != 0x80)
            {
                tf->scanmode = TGA_mode_diff;
                for (pix=0; pix<numpix; pix++)
                {
                    if (!read_pixels (stop, 1, tf))
                        return UT_False;
                    if (red == stop)
                    {
                        tf->scanrest = numpix - pix - 1;
                        break;
                    }
                }
            }
            else
            {
                tf->scanmode = TGA_mode_same;
                if (!read_pixels (stop, numpix, tf))
                    return UT_False;
            }
        } 
        while (red < stop);
        #if defined (DEBUG_LOCAL)
            printf ("\tScanline %d: Pixels: %d Rest: %d\n", 
                    y, total, tf->scanrest);
        #endif
    }
    else
    {   /* Read uncompressed pixel data. */
        Int32 i, bytesPerLine;

        bytesPerLine =  tf->nchan * px_f->desc.width;
        if (bytesPerLine != fread (rowbuf, 1, bytesPerLine, tf->file)) {
            return read_error (tf->file);
        }
        for (i=0; i<px_f->desc.width; i++) {
            *(blue++)  = *(rowbuf++);
            *(green++) = *(rowbuf++);
            *(red++)   = *(rowbuf++);
            if (tf->nchan == 4) {
                *(matte++) = *(rowbuf++);
            }
        }
    }

write_scanline:
    /* Copy the data to their final destination. */
    read_channel (px_f, pf, FF_ImgChanTypeRed,   tf->red,   px_f->desc.width);
    read_channel (px_f, pf, FF_ImgChanTypeGreen, tf->green, px_f->desc.width);
    read_channel (px_f, pf, FF_ImgChanTypeBlue,  tf->blue,  px_f->desc.width);

    if (tf->nchan == 4)
        read_channel (px_f, pf, FF_ImgChanTypeMatte, tf->matte, px_f->desc.width);

    return UT_True;
}

/***************************************************************************
 *[@e
 *      Name:           FF_ImgOpenWriteTarga
 *
 *      Usage:          Open a TARGA pixel file for writing.
 *
 *      Synopsis:       UT_Bool FF_ImgOpenWriteTarga
 *                              (const char *name, FF_ImgFileDesc *px_f)
 *
 *      Description:    TARGA pixel file "name" is opened for writing.
 *
 *                      Only FF_ImgChanTypeRed, FF_ImgChanTypeGreen,
 *                      FF_ImgChanTypeBlue and FF_ImgChanTypeMatte channel
 *                      types are supported.
 *                      Since the TARGA file format requires, that all 
 *                      three/four supported channels are stored in the file,
 *                      unused channels in the image are filled with zeros.
 *                      The only data compression method supported is
 *                      run-length encoding.
 *
 *      Return value:   UT_True if successful, else UT_False.
 *
 *      See also:       FF_ImgOpenReadTarga
 *
 ***************************************************************************/

UT_Bool FF_ImgOpenWriteTarga (const char *name, FF_ImgFileDesc *px_f)
{
    pixfile *pf;
    tgafile *tf;
    UT_MemState memstate;

    memstate = UT_MemRemember ();
    /* Allocate memory for a "tgafile". */
    if (!(tf =         UT_MemTemp (tgafile)) ||
        !(tf->rowbuf = UT_MemTempArray (px_f->desc.width*4, UInt8)) ||
        !(tf->red =    UT_MemTempArray (px_f->desc.width, UInt8)) ||
        !(tf->green =  UT_MemTempArray (px_f->desc.width, UInt8)) ||
        !(tf->blue =   UT_MemTempArray (px_f->desc.width, UInt8)) ||
        !(tf->matte =  UT_MemTempArray (px_f->desc.width, UInt8)))
    {
        UT_MemRestore (memstate);
        return UT_False;
    }
    pf = px_f->pixprivate;
    pf->fmtprivate = tf;
    /* Open the file for writing. */
    if (!(tf->file = UT_FileOpen (name, "wb"))) {
        UT_MemRestore (memstate);
        return UT_False;
    }
    tf->lastscan = -1;
    tf->nchan = px_f->channel[FF_ImgChanTypeMatte]? 4: 3;

    /* Generate the file header and write it to the file. */
    if (!write_header (px_f))
        return UT_False;

    /* Initialize the FF_ImgFileDesc. */
    pf->writescan = tga_write_scan;
    pf->close = tga_close;
    px_f->scanorder = FF_ImgOrderTypeBottomUp;
    UT_MemSave (memstate);
    return UT_True;
}

/***************************************************************************
 *[@e
 *      Name:           FF_ImgOpenReadTarga
 *
 *      Usage:          Open a TARGA pixel file for reading.
 *
 *      Synopsis:       UT_Bool FF_ImgOpenReadTarga
 *                              (const char *name,
 *                              FF_ImgFileDesc *px_f)
 *
 *      Description:    TARGA pixel file "name" is opened for reading.
 *
 *                      Only FF_ImgChanTypeRed, FF_ImgChanTypeGreen,
 *                      FF_ImgChanTypeBlue and FF_ImgChanTypeMatte channel
 *                      types are supported.
 *
 *      Return value:   UT_True if successful, else UT_False.
 *
 *      See also:       FF_ImgOpenWriteTarga
 *
 ***************************************************************************/

UT_Bool FF_ImgOpenReadTarga (const char *name, FF_ImgFileDesc *px_f)
{
    pixfile *pf;
    tgafile *tf;
    FILE *file;
    UT_MemState memstate;

    memstate = UT_MemRemember ();
    pf = px_f->pixprivate;
    /* Open the file for reading. */
    if (!(file = UT_FileOpen (name, "rb"))) {
        return UT_False;
    }

    if (!read_header (file, name, px_f))
    {
        UT_FileClose (file);
        return UT_False;
    }

    /* Generate the "tgafile". */
    if (!(tf =         UT_MemTemp (tgafile)) ||
        !(tf->rowbuf = UT_MemTempArray (px_f->desc.width*4, UInt8)) ||
        !(tf->red =    UT_MemTempArray (px_f->desc.width, UInt8)) ||
        !(tf->green =  UT_MemTempArray (px_f->desc.width, UInt8)) ||
        !(tf->blue =   UT_MemTempArray (px_f->desc.width, UInt8)) ||
        !(tf->matte =  UT_MemTempArray (px_f->desc.width, UInt8)))
    {
        UT_MemRestore (memstate);
        UT_FileClose (file);
        return UT_False;
    }

    /* Fill the "pixfile". */
    pf->fmtprivate = tf;
    pf->readscan = tga_read_scan;
    pf->close = tga_close;

    /* Fill the "tgafile". */
    tf->lastscan = -1;
    tf->nchan = px_f->channel[FF_ImgChanTypeMatte]? 4: 3;
    tf->scanrest = 0;
    tf->file = file;
    UT_MemSave (memstate);
    return UT_True;
}
