/**************************************************************************
 *{@C
 *      Copyright:      1988-2024 Paul Obermeier (obermeier@poSoft.de)
 *
 *      Module:         FileFormats
 *      Filename:       FF_ImagePoSoft.c
 *
 *      Author:         Paul Obermeier
 *
 *      Description:    Functions for reading and writing image files
 *                      in poSoft's file format.
 *
 *      Additional documentation:
 *                      None.
 *
 *      Exported functions:
 *                      FF_ImgOpenReadPoSoft
 *                      FF_ImgOpenWritePoSoft
 *
 **************************************************************************/

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

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

#include "FF_Image.h"
#include "FF_ImagePrivate.h"
#include "FF_ImagePoSoft.h"

/***************************************************************************
 *      Type definitions and macros
 ***************************************************************************/

#define MinRun  3               /* Minimum length of a run-length
                                   encoded sequence of bytes */

#define MaxRun  127             /* Maximum length of a run-length
                                   encoded sequence of bytes */

/* Format dependent pixel file descriptor */

typedef struct {
    FILE  *file;        /* C standard I/O file descriptor */
    Int32 offoff;       /* Position of the first byte of the
                           scan line offset table in the file */
    Int32 *offsets;     /* Positions of the first bytes of the
                           scan lines in the file */
    Int8 *tmpbuf;       /* Intermediate buffer for various data conversions */
} poSoftImgFile;

/***************************************************************************
 *
 *      Memory management
 *
 ***************************************************************************/

/* Reset all pointers in an "poSoftImgFile" to NULL. */

static UT_Bool clearImgFile (poSoftImgFile * spf)
{
    spf->offsets = NULL;
    spf->tmpbuf = NULL;
    return UT_True;
}


/* Allocate and initialize "poSoftImgFile". No memory is allocated for the scan
line offset table and for the data conversion and compression buffers. */

static UT_Bool allocImgFile (FF_ImgFileDesc * px_f)
{
    poSoftImgFile *spf;

    if (!(spf = UT_MemPerm (poSoftImgFile))) {
        return UT_False;
    }
    clearImgFile (spf);
    ((pixfile *) px_f->pixprivate)->fmtprivate = spf;
    return UT_True;
}


/* Allocate memory for an poSoftImgFile's scan line offset table and for the
   data conversion and compression buffers:
   The number of entries in the scan line offset table, "offsets", is obviously
   equal to the number of scan lines in the file.
   The data conversion buffer, "tmpbuf", is allocated big enough to hold either
   the scan line offset table in machine-independent format, one scan line of
   floating point data in machine-independent format, or "QmN" scan lines of
   UInt8 data, whatever is largest. */

static UT_Bool allocTables (FF_ImgFileDesc * px_f)
{
    poSoftImgFile *spf;
    UT_MemState memstate;
    Int32 offsize, tmpsize;

    memstate = UT_MemRemember ();
    spf = ((pixfile *) px_f->pixprivate)->fmtprivate;
    offsize = px_f->desc.height;
    tmpsize = 4 * UT_MAX (px_f->desc.width, px_f->desc.height);
    if (!(spf->offsets = UT_MemTempArray (offsize, Int32)) ||
        !(spf->tmpbuf =  UT_MemTempArray (tmpsize, Int8))) {
        UT_MemRestore (memstate);
        clearImgFile (spf);
        return UT_False;
    }
    UT_MemSave (memstate);
    return UT_True;
}


/* Free all memory used for an "poSoftImgFile" and for the data
   buffers linked to it. */

static void freeImgFile (poSoftImgFile * spf)
{
    if (spf->tmpbuf) {
        UT_MemFree (spf->tmpbuf);
    }
    if (spf->offsets) {
        UT_MemFree (spf->offsets);
    }
    UT_MemFree (spf);
    return;
}

/***************************************************************************
 *[
 *      Function Name:  rleCompress
 *
 *      Description:    Compress an array of bytes, using run-length
 *                      encoding.
 *
 *      Usage:          Int32 rleCompress
 *                              (Int32 in_len, const UInt8 in[], Int8 out[]);
 *
 *                      "In" must be an array of "in_len" data bytes. The
 *                      data are compressed, and the result is stored in
 *                      array "out". The compression scheme is very basic. It
 *                      looks for repeats of a value that can be encoded into
 *                      a repeat count and a repeat value. The encoded data
 *                      consist of uncompressed and compressed data runs. Data
 *                      are not compressed if the repeat count is two or less.
 *                      Both, uncompressed and compressed runs begin with a
 *                      repeat count. A negative count indicates an
 *                      uncompressed run; the count byte is followed by -count
 *                      data bytes. A non-negative count indicates a compressed
 *                      run; the count byte is followed by a data byte which
 *                      is repeated (count + 1) times in the original
 *                      uncompressed data.
 *
 *      Return Value:   The length of the compressed data in bytes.
 *]
 ***************************************************************************/

static Int32 rleCompress (Int32 in_len, const UInt8 in[], Int8 out[])
{
    Int8 *out_write;
    const UInt8 *in_end, *run_start, *run_end;

    in_end = in + in_len;
    run_start = in;
    run_end = in + 1;
    out_write = out;
    while (run_start < in_end) {
        while (run_end < in_end &&
               *run_start == *run_end &&
               run_end - run_start - 1 < MaxRun) {
            ++run_end;
        }
        if (run_end - run_start >= MinRun) {
            /* Found a run of compressable data */

            *out_write++ = (run_end - run_start) - 1;
            *out_write++ = *(Int8 *) run_start;
            run_start = run_end;
        } else {
            /* Found a run of uncompressable data */

            while (run_end < in_end &&
                   ((run_end + 1 >= in_end ||
                     *run_end != *(run_end + 1)) ||
                    (run_end + 2 >= in_end ||
                     *(run_end + 1) != *(run_end + 2))) &&
                   run_end - run_start < MaxRun) {
                ++run_end;
            }
            *out_write++ = run_start - run_end;
            while (run_start < run_end) {
                *out_write++ = *(Int8 *) (run_start++);
            }
        }
        ++run_end;
    }
    return out_write - out;
}

/***************************************************************************
 *[
 *      Function Name:  rleUncompress
 *
 *      Description:    Uncompress run-length encoded data.
 *
 *      Usage:          UT_Bool rleUncompress
 *                              (Int32 in_len, Int32 out_len,
 *                               const Int8 in[], UInt8 out[])
 *
 *                      "In" must be an array of "in_len" run-length encoded
 *                      data bytes. The data are decompressed, and the result
 *                      is stored in array "out".  This routine is the inverse
 *                      of function rleCompress.
 *
 *      Return Value:   UT_True if the length of the uncompressed data is
 *                      exactly "out_len" bytes, UT_False otherwise.
 *]
 ***************************************************************************/

static UT_Bool rleUncompress
        (Int32 in_len, Int32 out_len, const Int8 in[], UInt8 out[])
{
    int count;
    Int8 *inPtr = (Int8 *)in;

    while (in_len > 0) {
        if (*in < 0) {
            count = -((int)*inPtr++);
            in_len -= count + 1;
            if (0 > (out_len -= count)) {
                UT_ErrSetNum (FF_ImgErrSyntax, str_long_scan);
                return UT_False;
            }
            while (count-- > 0) {
                *out++ = *(UInt8 *) (inPtr++);
            }
        } else {
            count = *inPtr++;
            in_len -= 2;
            if (0 > (out_len -= count + 1)) {
                UT_ErrSetNum (FF_ImgErrSyntax, str_long_scan);
                return UT_False;
            }
            while (count-- >= 0) {
                *out++ = *(UInt8 *) inPtr;
            }
            inPtr++;
        }
    }
    if (out_len) {
        UT_ErrSetNum (FF_ImgErrSyntax, str_short_scan);
        return UT_False;
    }
    return UT_True;
}

/***************************************************************************
 *
 *      Functions for writing pixel files
 *
 ***************************************************************************/

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

static UT_Bool write_Int32 (FILE * file, Int32 i)
{
    Int8 buf[4];

    UT_PortInt32ToByte (i, buf);
    if (!fwrite (buf, sizeof (buf), 1, file)) {
        UT_ErrSetNum (UT_ErrFromOs (), "fwrite");
        return UT_False;
    }
    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 (!fwrite (buf, sizeof (buf), 1, file)) {
        UT_ErrSetNum (UT_ErrFromOs (), "fwrite");
        return UT_False;
    }
    return UT_True;
}


/* Write the file format version to a file. */

static UT_Bool write_version (FILE * file, char buf[])
{
    if (!fwrite (buf, FF_ImgMaxNameLen, 1, file)) {
        UT_ErrSetNum (UT_ErrFromOs (), "fwrite");
        return UT_False;
    }
    return UT_True;
}


/* All channels of scan line number y for pixel file "px_f" are stored
   in the file.
   Note that the poSoft file format allows the scan lines to be written in
   any order.
   The position of the first byte of the scan line data in the file is entered
   into the scan line offset table (the offset table will be copied into the
   file after all scan lines have been writen.
   The scan line data written start with the scan line number, which is
   redundant, but helps in checking a pixel file for consistency.
   It can also be useful when an attempt is made to reconstruct the scan line
   offset table of an incomplete pixel file. The scan line number is followed
   by the pixel data for the channels in the scan line.
   Since the pixel data may be compressed, the block of pixel data for every
   channel is preceded by its length in bytes. */

static UT_Bool write_scan (FF_ImgFileDesc * px_f, Int32 y)
{
    pixfile *pf;
    poSoftImgFile *spf;
    Int32 chn, len = 0;
    Int8 *buf = NULL;
    Float32 *fbuf = NULL;

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

    /* Check if the parameters supplied are reasonable. */

    if (pf->readfile) {
        UT_ErrFatal ("write_scan", "pixel file is read-only");
    }
    if (spf->offsets[y]) {
        UT_ErrFatal ("write_scan", "scan line already exists in the file");
    }

    /* Enter the new scan line into the scan line offset table. */

    if (0 > (spf->offsets[y] = ftell (spf->file))) {
        UT_ErrSetNum (UT_ErrFromOs (), "ftell");
        return UT_False;
    }

    /* Write the scan line data. */

    if (!write_Int32 (spf->file, y)) {
        return UT_False;
    }
    for (chn = 0; chn < FF_NumImgChanTypes; ++chn) {
        if (px_f->channel[chn]) {
            switch (px_f->channel[chn]) {
                case FF_ImgFmtTypeUByte:
                    switch (px_f->compression[chn]) {
                        case FF_ImgComprTypeNone:
                            buf = (Int8 *) pf->buf[chn].ubytes;
                            len = px_f->desc.width;
                            break;
                        case FF_ImgComprTypeRle:
                            buf = spf->tmpbuf;
                            len = rleCompress
                                (px_f->desc.width,
                                 pf->buf[chn].ubytes, buf);
                            if (len >= px_f->desc.width) {
                                buf = (Int8 *) pf->buf[chn].ubytes;
                                len = px_f->desc.width;
                            }
                            break;
                        default:
                            UT_ErrFatal ("write_scan",
                                         "unknown compression scheme");
                    }
                    break;
                case FF_ImgFmtTypeFloat:
                    fbuf = pf->buf[chn].floats;
                    buf = spf->tmpbuf;
                    len = px_f->desc.width;
                    while (len--) {
                        UT_PortFloat32ToByte (*fbuf++, buf);
                        buf += 4;
                    }
                    buf = spf->tmpbuf;
                    len = 4 * px_f->desc.width;
                    break;
                default:
                    UT_ErrFatal ("write_scan", "unknown pixel format");
            }
            if (len && !write_Int32 (spf->file, len)) {
                return UT_False;
            }
            if (len && !fwrite (buf, 1, len, spf->file)) {
                UT_ErrSetNum (UT_ErrFromOs (), "fwrite");
                return UT_False;
            }
        }
    }
    return UT_True;
}


/* Convert the scan line offset table of a pixel file into a machine-
   independent format and write it to the file. */

static UT_Bool write_offsets (FF_ImgFileDesc * px_f)
{
    poSoftImgFile *spf;
    Int32 i;

    spf = ((pixfile *) px_f->pixprivate)->fmtprivate;
    for (i = 0; i < px_f->desc.height; ++i) {
        UT_PortInt32ToByte (spf->offsets[i], spf->tmpbuf + (4 * i));
    }
    if (0 > fseek (spf->file, spf->offoff, 0)) {
        UT_ErrSetNum (UT_ErrFromOs (), "fseek");
        return UT_False;
    }
    if (!fwrite (spf->tmpbuf, 4, px_f->desc.height, spf->file)) {
        UT_ErrSetNum (UT_ErrFromOs (), "fwrite");
        return UT_False;
    }
    return UT_True;
}


/* Write the beginning of an image file: First write the poSoft file header
   which identifies the file as an image file.
   Then write the image file specific header containing the size, aspect ratio
   etc. of the image stored in the file. 
   Finally write a dummy scan line offset table which will later be replaced
   with the true data. */

static UT_Bool write_header (FF_ImgFileDesc * px_f)
{
    Int32 i, chn;
    poSoftImgFile *spf;

    spf = ((pixfile *) px_f->pixprivate)->fmtprivate;
    if (!UT_FileHeaderWrite (spf->file, UT_FileTypeImage, UT_FileSubTypePoImage,
                             UT_FileCodingTypePortableBinary) ||
        !write_Int32 (spf->file, px_f->desc.width) ||
        !write_Int32 (spf->file, px_f->desc.height) ||
        !write_Float32 (spf->file, px_f->desc.aspect) ||
        !write_Float32 (spf->file, px_f->desc.red.x) ||
        !write_Float32 (spf->file, px_f->desc.red.y) ||
        !write_Float32 (spf->file, px_f->desc.green.x) ||
        !write_Float32 (spf->file, px_f->desc.green.y) ||
        !write_Float32 (spf->file, px_f->desc.blue.x) ||
        !write_Float32 (spf->file, px_f->desc.blue.y) ||
        !write_Float32 (spf->file, px_f->desc.white.x) ||
        !write_Float32 (spf->file, px_f->desc.white.y) ||
        !write_Float32 (spf->file, px_f->desc.gamma) ||
        !write_Int32 (spf->file, FF_NumImgChanTypes) ||
        !write_version (spf->file, px_f->version)) {
        return UT_False;
    }
    for (chn = 0; chn < (Int32) FF_NumImgChanTypes; ++chn) {
        if (!write_Int32 (spf->file, (Int32) px_f->channel[chn]) ||
            !write_Int32 (spf->file, (Int32) px_f->compression[chn])) {
            return UT_False;
        }
    }
    for (i = 0; i < px_f->desc.height; ++i) {
        spf->offsets[i] = 0;
    }
    if (0 > (spf->offoff = ftell (spf->file))) {
        UT_ErrSetNum (UT_ErrFromOs (), "ftell");
        return UT_False;
    }
    if (!write_offsets (px_f)) {
        return UT_False;
    }
    return UT_True;
}


/* Close a pixel file previously opened with FF_ImgOpenWritePoSoft: 
   Write the scan line lookup table. Close the standard I/O library
   file associated with the pixel file. Then deallocate the memory 
   used to hold information about the file. */

static void close_write (FF_ImgFileDesc * px_f)
{
    poSoftImgFile *spf;

    spf = ((pixfile *) px_f->pixprivate)->fmtprivate;
    write_offsets (px_f);
    UT_FileClose (spf->file);
    freeImgFile (spf);
    return;
}


/***************************************************************************
 *
 *      Functions for reading pixel files
 *
 ***************************************************************************/

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

static UT_Bool read_Int32 (FILE * file, Int32 * i)
{
    Int8 buf[4];

    if (!fread (buf, sizeof (buf), 1, file)) {
        if (feof (file)) {
            UT_ErrSetNum (UT_ErrUnexpEof, "fread");
        } else {
            UT_ErrSetNum (UT_ErrFromOs (), "fread");
        }
        return UT_False;
    }
    *i = UT_PortByteToInt32 (buf);
    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 (!fread (buf, sizeof (buf), 1, file)) {
        if (feof (file)) {
            UT_ErrSetNum (UT_ErrUnexpEof, "fread");
        } else {
            UT_ErrSetNum (UT_ErrFromOs (), "fread");
        }
        return UT_False;
    }
    *f = UT_PortByteToFloat32 (buf);
    return UT_True;
}


/* Read the file format version from a file. */

static UT_Bool read_version (FILE * file, char buf[])
{
    if (!fread (buf, FF_ImgMaxNameLen, 1, file)) {
        if (feof (file)) {
            UT_ErrSetNum (UT_ErrUnexpEof, "fread");
        } else {
            UT_ErrSetNum (UT_ErrFromOs (), "fread");
        }
        return UT_False;
    }
    return UT_True;
}


/* All channels of scan line number y are read from pixel file "px_f".
   Note that the poSoft file format allows the scan lines to be read
   in any order. */

static UT_Bool read_scan (FF_ImgFileDesc * px_f, Int32 y)
{
    pixfile *pf;
    poSoftImgFile *spf;
    Int32 chn, len, f_y;
    Int8 *buf = NULL;
    Float32 *fbuf = NULL;

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

    /* Check if the parameters supplied are reasonable. */

    if (!pf->readfile) {
        UT_ErrFatal ("read_scan", "pixel file is write-only");
    }

    /* Find out where in the file the scan line is. */

    if (!spf->offsets[y]) {
        UT_ErrSetNum (FF_ImgErrSyntax, str_no_scan);
        return UT_False;
    }
    if (0 > fseek (spf->file, spf->offsets[y], 0)) {
        UT_ErrSetNum (UT_ErrFromOs (), "fseek");
        return UT_False;
    }

    /* Read the scan line data and perform some consistency checks. */

    if (!read_Int32 (spf->file, &f_y)) {
        return UT_False;
    }
    if (y != f_y) {
        UT_ErrSetNum (FF_ImgErrSyntax, str_scan_number);
        return UT_False;
    }
    for (chn = 0; chn < FF_NumImgChanTypes; ++chn) {
        if (px_f->channel[chn]) {
            switch (px_f->channel[chn]) {
                case FF_ImgFmtTypeUByte:
                    switch (px_f->compression[chn]) {
                        case FF_ImgComprTypeNone:
                            if (!read_Int32 (spf->file, &len)) {
                                return UT_False;
                            }
                            if (len != px_f->desc.width) {
                                UT_ErrSetNum (FF_ImgErrSyntax, str_scan_len);
                                return UT_False;
                            }
                            buf = (Int8 *) pf->buf[chn].ubytes;
                            break;
                        case FF_ImgComprTypeRle:
                            if (!read_Int32 (spf->file, &len)) {
                                return UT_False;
                            }
                            if ((len <= 0) || (len > px_f->desc.width)) {
                                UT_ErrSetNum (FF_ImgErrSyntax, str_scan_len);
                                return UT_False;
                            }
                            if (len < px_f->desc.width) {
                                buf = spf->tmpbuf;
                            } else {
                                buf = (Int8 *) pf->buf[chn].ubytes;
                            }
                            break;
                        default:
                            UT_ErrFatal ("read_scan",
                                         "unknown compression type");
                    }
                    break;
                case FF_ImgFmtTypeFloat:
                    if (!read_Int32 (spf->file, &len)) {
                        return UT_False;
                    }
                    if (len != 4 * px_f->desc.width) {
                        UT_ErrSetNum (FF_ImgErrSyntax, str_scan_len);
                        return UT_False;
                    }
                    buf = spf->tmpbuf;
                    break;
                default:
                    UT_ErrFatal ("read_scan", "unknown pixel format");
            }
            if (len && !fread (buf, 1, len, spf->file)) {
                if (feof (spf->file)) {
                    UT_ErrSetNum (UT_ErrUnexpEof, "fread");
                } else {
                    UT_ErrSetNum (UT_ErrFromOs (), "fread");
                }
                return UT_False;
            }
            switch (px_f->channel[chn]) {
                case FF_ImgFmtTypeUByte:
                    switch (px_f->compression[chn]) {
                        case FF_ImgComprTypeNone:
                            break;
                        case FF_ImgComprTypeRle:
                            if (len < px_f->desc.width &&
                                !rleUncompress (len, px_f->desc.width,
                                                buf, pf->buf[chn].ubytes)) {
                                return UT_False;
                            }
                            break;
                        default:
                            UT_ErrFatal ("read_scan",
                                         "unknown compression scheme");
                    }
                    break;
                case FF_ImgFmtTypeFloat:
                    fbuf = pf->buf[chn].floats;
                    while (len) {
                        *fbuf++ = UT_PortByteToFloat32 (buf);
                        buf += 4;
                        len -= 4;
                    }
                    break;
                default:
                    UT_ErrFatal ("read_scan", "unknown pixel format");
            }
        }
    }
    return UT_True;
}


/* Read the scan line offset table from a pixel file. */

static UT_Bool read_offsets (FF_ImgFileDesc * px_f)
{
    poSoftImgFile *spf;
    Int32 i;

    spf = ((pixfile *) px_f->pixprivate)->fmtprivate;
    if (!fread (spf->tmpbuf, 4, px_f->desc.height, spf->file)) {
        if (feof (spf->file)) {
            UT_ErrSetNum (UT_ErrUnexpEof, "fread");
        } else {
            UT_ErrSetNum (UT_ErrFromOs (), "fread");
        }
        return UT_False;
    }
    for (i = 0; i < px_f->desc.height; ++i) {
        spf->offsets[i] = UT_PortByteToInt32 (spf->tmpbuf + (4 * i));
    }
    return UT_True;
}


/* Read the beginning of a pixel file: First read the file header which tells
   us if the file is actually a pixel file or not. Then read the pixel file
   header containing the size, aspect ratio etc. of the image stored in the
   file.
   Allocate memory for the scan line offset table and for the data conversion
   buffer. Finally read the scan line offset table from the file. */

static UT_Bool read_header (FF_ImgFileDesc * px_f)
{
    Int32 chn, nchan, tmp;
    poSoftImgFile *spf;
    UT_FileHeader fileHdr;

    spf = ((pixfile *) px_f->pixprivate)->fmtprivate;
    if (!UT_FileHeaderRead (spf->file, &fileHdr)) {
        return UT_False;
    }
    if (fileHdr.fileType != UT_FileTypeImage) {
        UT_ErrSetNum (FF_ImgErrNoImgFile, str_FileType, 
                     UT_GetFileTypeName (fileHdr.fileType));
        return UT_False;
    }
    if (fileHdr.fileSubType != UT_FileSubTypePoImage) {
        UT_ErrSetNum (FF_ImgErrNoImgFile, str_FileSubType, 
                     UT_GetFileSubTypeName (fileHdr.fileSubType));
        return UT_False;
    }
    if (!read_Int32 (spf->file, &px_f->desc.width) ||
        !read_Int32 (spf->file, &px_f->desc.height) ||
        !read_Float32 (spf->file, &px_f->desc.aspect) ||
        !read_Float32 (spf->file, &px_f->desc.red.x) ||
        !read_Float32 (spf->file, &px_f->desc.red.y) ||
        !read_Float32 (spf->file, &px_f->desc.green.x) ||
        !read_Float32 (spf->file, &px_f->desc.green.y) ||
        !read_Float32 (spf->file, &px_f->desc.blue.x) ||
        !read_Float32 (spf->file, &px_f->desc.blue.y) ||
        !read_Float32 (spf->file, &px_f->desc.white.x) ||
        !read_Float32 (spf->file, &px_f->desc.white.y) ||
        !read_Float32 (spf->file, &px_f->desc.gamma) ||
        !read_Int32 (spf->file, &nchan) ||
        !read_version (spf->file, px_f->version)) {
        return UT_False;
    }
    if (px_f->desc.width < 2 ||
        px_f->desc.height < 2 ||
        px_f->desc.aspect == 0.0 ||
        nchan > FF_NumImgChanTypes) {
        UT_ErrSetNum (FF_ImgErrSyntax, str_header);
        return UT_False;
    }
    for (chn = 0; chn < nchan; ++chn) {
        if (!read_Int32 (spf->file, &tmp)) {
            return UT_False;
        }
        if (tmp < (Int32) FF_ImgFmtTypeNone || 
            tmp > (Int32) FF_NumImgFmtTypes) {
            UT_ErrSetNum (FF_ImgErrSyntax, str_pixformat);
            return UT_False;
        }
        px_f->channel[chn] = (FF_ImgFmtType) tmp;
        if (!read_Int32 (spf->file, &tmp)) {
            return UT_False;
        }
        if (!px_f->channel[chn]) {
            tmp = (Int32) FF_ImgComprTypeNone;
        }
        if (tmp < (Int32) FF_ImgComprTypeNone ||
            tmp > (Int32) FF_NumImgComprTypes) {
            UT_ErrSetNum (FF_ImgErrSyntax, str_compression);
            return UT_False;
        }
        px_f->compression[chn] = (FF_ImgComprType) tmp;
    }
    for (chn = nchan; chn < (Int32) FF_NumImgChanTypes; ++chn) {
        px_f->channel[chn] = FF_ImgFmtTypeNone;
        px_f->compression[chn] = FF_ImgComprTypeNone;
    }
    if (!allocTables (px_f) || !read_offsets (px_f)) {
        return UT_False;
    }
    return UT_True;
}


/* Close a pixel file previously opened with FF_ImgOpenReadPoSoft: Close the
   standard I/O library file associated with the pixel file. Then deallocate
   the memory used to hold information about the file. */

static void close_read (FF_ImgFileDesc * px_f)
{
    poSoftImgFile *spf;

    spf = ((pixfile *) px_f->pixprivate)->fmtprivate;
    UT_FileClose (spf->file);
    freeImgFile (spf);
    return;
}


/***************************************************************************
 *[@e
 *      Name:           FF_ImgOpenWritePoSoft
 *
 *      Usage:          Open a poSoft image file for writing.
 *
 *      Synopsis:       UT_Bool FF_ImgOpenWritePoSoft
 *                              (const char *name, FF_ImgFileDesc *px_f)
 *
 *      Description:    Image file "name" is opened for writing.
 *
 *      Return value:   UT_True if successful, else UT_False.
 *
 *      See also:       FF_ImgOpenReadPoSoft
 *
 ***************************************************************************/

UT_Bool FF_ImgOpenWritePoSoft (const char *name, FF_ImgFileDesc *px_f)
{
    FILE *file;
    pixfile *pf;
    poSoftImgFile *spf;

    if (!(file = UT_FileOpen (name, "wb"))) {
        return UT_False;
    }
    if (!allocImgFile (px_f)) {
        UT_FileClose (file);
        return UT_False;
    }
    pf = px_f->pixprivate;
    pf->readscan = NULL;
    pf->writescan = write_scan;
    pf->close = close_write;
    spf = pf->fmtprivate;
    spf->file = file;
    if (!allocTables (px_f) ||
        !write_header (px_f)) {
        UT_FileClose (spf->file);
        freeImgFile (spf);
        return UT_False;
    }
    px_f->scanorder = FF_ImgOrderTypeTopDown;
    return UT_True;
}

/***************************************************************************
 *[@e
 *      Name:           FF_ImgOpenReadPoSoft
 *
 *      Usage:          Open a poSoft image file for reading.
 *
 *      Synopsis:       UT_Bool FF_ImgOpenReadPoSoft
 *                              (const char *name,
 *                              FF_ImgFileDesc *px_f)
 *
 *      Description:    Image file "name" is opened for reading.
 *
 *      Return value:   UT_True if successful, else UT_False.
 *
 *      See also:       FF_ImgOpenWritePoSoft
 *
 ***************************************************************************/

UT_Bool FF_ImgOpenReadPoSoft (const char *name, FF_ImgFileDesc *px_f)
{
    FILE *file;
    pixfile *pf;
    poSoftImgFile *spf;

    if (!(file = UT_FileOpen (name, "rb"))) {
        return UT_False;
    }
    if (!allocImgFile (px_f)) {
        UT_FileClose (file);
        return UT_False;
    }
    pf = px_f->pixprivate;
    pf->readscan = read_scan;
    pf->writescan = NULL;
    pf->close = close_read;
    spf = pf->fmtprivate;
    spf->file = file;
    if (!read_header (px_f)) {
        UT_FileClose (spf->file);
        freeImgFile (spf);
        return UT_False;
    }
    px_f->scanorder = FF_ImgOrderTypeTopDown;
    return UT_True;
}
