/* ilbmtoppm.c - read an IFF ILBM file and produce a PPM
**
** 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.
**
** Modified by Mark Thompson on 10/4/90 to accommodate 24-bit IFF files
** as used by ASDG, NewTek, etc.
**
** Modified by Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de)
**  20/Jun/93:
**  - row-by-row operation
**  - better de-interleave algorithm
**  - colormap files
**  - direct color
**  04/Oct/93:
**  - multipalette capability (PCHG chunk)
**  - options -ignore, -isham, -isehb and -adjustcolors
**  22/May/94:
**  - minor change: check first for 24 planes, then for HAM
**  21/Sep/94:
**  - write mask plane to a file if -maskfile option used
**  - write colormap file
**  - added sliced HAM/dynamic HAM/dynamic Hires multipalette formats (SHAM, CTBL chunk)
**  - added color lookup tables (CLUT chunk)
**  - major rework of colormap/multipalette handling
**  - now uses numeric IFF IDs
**  24/Oct/94:
**  - transparentColor capability
**  - added RGBN/RGB8 image types
**  - 24-bit & direct color modified to n-bit deep ILBM
**  22/Feb/95:
**  - direct color (DCOL) reimplemented
**  29/Mar/95
**  - added IFF-PBM format
*/

#include <string.h>

#include "pm_c_util.h"
#include "mallocvar.h"
#include "intcode.h"
#include "ilbm.h"
#include "ppm.h"

typedef struct {
    int reg;            /* color register to change */
    pixval r, g, b;     /* new colors for register */
} PaletteChange;

typedef struct {
    pixel *color;
    int    ncolors;
    /* lookup tables */
    unsigned char *redlut;
    unsigned char *greenlut;
    unsigned char *bluelut;
    unsigned char *monolut;
    /* multipalette stuff */
    PaletteChange *mp_init;
    PaletteChange **mp_change;
    int mp_rows;                /* # of rows in change array */
    int mp_type;                /* see below, higher types preferred */
    int mp_flags;
    IFF_ID  mp_id;
} ColorMap;

#define HAS_COLORMAP(cmap)      ((cmap) && (cmap)->color)
#define HAS_COLORLUT(cmap)      ((cmap) && ((cmap)->redlut || (cmap)->greenlut || (cmap)->bluelut))
#define HAS_MONOLUT(cmap)       ((cmap) && (cmap)->monolut)
#define HAS_MULTIPALETTE(cmap)  (HAS_COLORMAP(cmap) && (cmap)->mp_type)
#define MP_TYPE_SHAM        1
#define MP_TYPE_CTBL        2
#define MP_TYPE_PCHG        3
#define MP_REG_IGNORE       -1
#define MP_REG_END          -2
#define MP_FLAGS_SKIPLACED   (1<<0)

#define FACTOR_4BIT     17      /* scale factor maxval 15 -> maxval 255 */

static short verbose = 0;
static short adjustcolors = 0;
static unsigned char *ilbmrow;
static pixel *pixelrow;
static FILE *maskfile = NULL;
static bit *maskrow = NULL;
static bool wrotemask;
static IFF_ID typeid;       /* ID_ILBM, ID_RGBN, ID_RGB8 */

static char *transpName = NULL;  /* -transparent option value */

static bool debug = FALSE;



static char *
ID2string(id)
    IFF_ID id;
{
    static char str[] = "abcd";

    str[0] = (char)(id >> 24 & 0xff);
    str[1] = (char)(id >> 16 & 0xff);
    str[2] = (char)(id >>  8 & 0xff);
    str[3] = (char)(id >>  0 & 0xff);

    return str;
}


/****************************************************************************
 Memory allocation
 ****************************************************************************/
static ColorMap *
alloc_cmap(void) {

    ColorMap * cmap;

    MALLOCVAR_NOFAIL(cmap);

    cmap->color     = NULL;
    cmap->ncolors   = 0;
    cmap->monolut   = NULL;
    cmap->redlut    = NULL;
    cmap->greenlut  = NULL;
    cmap->bluelut   = NULL;
    cmap->mp_init   = NULL;
    cmap->mp_change = NULL;
    cmap->mp_rows   = 0;
    cmap->mp_type   = 0;
    cmap->mp_flags  = 0;

    return cmap;
}



static rawtype *
alloc_rawrow(cols)
    int cols;
{
    rawtype *r;
    int i;

    MALLOCARRAY_NOFAIL(r, cols);

    for( i = 0; i < cols; i++ )
        r[i] = 0;

    return r;
}


/****************************************************************************
 Basic I/O functions
 ****************************************************************************/

static void
readerr(f, iffid)
    FILE *f;
    IFF_ID iffid;
{
    if( ferror(f) )
        pm_error("read error");
    else
        pm_error("premature EOF in %s chunk", ID2string(iffid));
}


static void
read_bytes(FILE *          const ifP,
           int             const bytes,
           unsigned char * const buffer,
           IFF_ID          const iffid,
           unsigned long * const counterP) {

    if (counterP) {
        if (*counterP < bytes)
            pm_error("insufficient data in %s chunk", ID2string(iffid));
        *counterP -= bytes;
    }
    if (fread(buffer, 1, bytes, ifP) != bytes)
        readerr(ifP, iffid);
}



static unsigned char
get_byte(ifP, iffid, counter)
    FILE* ifP;
    IFF_ID iffid;
    long *counter;
{
    int i;

    if( counter ) {
        if( *counter == 0 )
            pm_error("insufficient data in %s chunk", ID2string(iffid));
        --(*counter);
    }
    i = getc(ifP);
    if( i == EOF )
        readerr(ifP, iffid);

    return (unsigned char) i;
}

static long
get_big_long(FILE *          const ifP,
             IFF_ID          const iffid,
             unsigned long * const counterP) {

    long l;
    
    if (counterP) {
        if (*counterP < 4)
            pm_error("insufficient data in %s chunk", ID2string(iffid));
        *counterP -= 4;
    }
    if (pm_readbiglong(ifP, &l) == -1)
        readerr(ifP, iffid);

    return l;
}



static short
get_big_short(FILE *          const ifP,
              IFF_ID          const iffid,
              unsigned long * const counterP) {

    short s;

    if (counterP) {
        if (*counterP < 2)
            pm_error("insufficient data in %s chunk", ID2string(iffid));
        *counterP -= 2;
    }
    if (pm_readbigshort(ifP, &s) == -1)
        readerr(ifP, iffid);

    return s;
}



/****************************************************************************
 Chunk reader
 ****************************************************************************/

static void
chunk_end(FILE *        const ifP,
          IFF_ID        const iffid,
          unsigned long const chunksize) {

    if (chunksize > 0) {
        unsigned long remainingChunksize;
        pm_message("warning - %ld extraneous byte%s in %s chunk",
                    chunksize, (chunksize == 1 ? "" : "s"), ID2string(iffid));
        remainingChunksize = chunksize;  /* initial value */
        while (remainingChunksize > 0)
            get_byte(ifP, iffid, &remainingChunksize);
    }
}



static void
skip_chunk(FILE *        const ifP,
           IFF_ID        const iffid,
           unsigned long const chunksize) {
    unsigned long remainingChunksize;

    remainingChunksize = chunksize;  /* initial value */

    while (remainingChunksize > 0)
        get_byte(ifP, iffid, &remainingChunksize);
}



static void
display_chunk(FILE *        const ifP,
              IFF_ID        const iffid,
              unsigned long const chunksize) {

    int byte;
    unsigned long remainingChunksize;

    pm_message("contents of %s chunk:", ID2string(iffid));

    remainingChunksize = chunksize;  /* initial value */
    byte = '\0';

    while (remainingChunksize > 0) {
        byte = get_byte(ifP, iffid, &remainingChunksize);
        if (fputc(byte, stderr) == EOF)
            pm_error("write error");
    }
    if (byte != '\n')
        if (fputc('\n', stderr) == EOF)
            pm_error("write error");
}


static void
read_cmap(FILE *     const ifP,
          IFF_ID     const iffid,
          long       const chunksize,
          ColorMap * const cmap) {

    long colors;

    colors = chunksize / 3;
    if( colors == 0 ) {
        pm_error("warning - empty %s colormap", ID2string(iffid));
        skip_chunk(ifP, iffid, chunksize);
    } else {
        unsigned int i;
        if( cmap->color )               /* prefer CMAP-chunk over CMYK-chunk */
            ppm_freerow(cmap->color);
        cmap->color = ppm_allocrow(colors);
        cmap->ncolors = colors;
        
        for( i = 0; i < colors; ++i ) {
            int r, g, b;
            r = get_byte(ifP, iffid, &chunksize);
            g = get_byte(ifP, iffid, &chunksize);
            b = get_byte(ifP, iffid, &chunksize);
            PPM_ASSIGN(cmap->color[i], r, g, b);
        }
        chunk_end(ifP, iffid, chunksize);
    }
}



static void
read_cmyk(FILE *     const ifP,
          IFF_ID     const iffid,
          long       const chunksize,
          ColorMap * const cmap) {

    if( HAS_COLORMAP(cmap) ) {      /* prefer RGB color map */
        skip_chunk(ifP, iffid, chunksize);
    } else {
        long const colors = chunksize/4;
        if( colors == 0 ) {
            pm_error("warning - empty %s colormap", ID2string(iffid));
            skip_chunk(ifP, iffid, chunksize);
        } else {
            unsigned int i;
            cmap->color = ppm_allocrow(colors);
            cmap->ncolors = colors;
            
            for( i = 0; i < colors; ++i ) {
                int c, m, y, k;
                c = get_byte(ifP, iffid, &chunksize);
                m = get_byte(ifP, iffid, &chunksize);
                y = get_byte(ifP, iffid, &chunksize);
                k = get_byte(ifP, iffid, &chunksize);

                {
                    pixval const red = 
                        MAXCOLVAL - MIN(MAXCOLVAL,
                                        c*(MAXCOLVAL-k)/MAXCOLVAL+k); 
                    pixval const green = 
                        MAXCOLVAL - MIN(MAXCOLVAL,
                                        m*(MAXCOLVAL-k)/MAXCOLVAL+k);
                    pixval const blue = 
                        MAXCOLVAL - MIN(MAXCOLVAL,
                                        y*(MAXCOLVAL-k)/MAXCOLVAL+k);

                    PPM_ASSIGN(cmap->color[i], red, green, blue);
                }
            }
            chunk_end(ifP, iffid, chunksize);
        }
    }
}



static void
read_clut(FILE *        const ifP,
          IFF_ID        const iffid,
          unsigned long const chunksize,
          ColorMap *    const cmap) {

    if (chunksize != CLUTSize) {
        pm_message("invalid size for %s chunk - skipping it", 
                   ID2string(iffid));
        skip_chunk(ifP, iffid, chunksize);
    } else {
        long type;
        unsigned char * lut;
        unsigned long remainingChunksize;
        unsigned int i;

        type = get_big_long(ifP, iffid, &remainingChunksize);
        get_big_long(ifP, iffid, &remainingChunksize); /* skip reserved fld */

        MALLOCARRAY_NOFAIL(lut, 256);
        for( i = 0; i < 256; ++i )
            lut[i] = get_byte(ifP, iffid, &remainingChunksize);

        switch( type ) {
        case CLUT_MONO:
            cmap->monolut  = lut;
            break;
        case CLUT_RED:
            cmap->redlut   = lut;
            break;
        case CLUT_GREEN:
            cmap->greenlut = lut;
            break;
        case CLUT_BLUE:
            cmap->bluelut  = lut;
            break;
        default:
            pm_message("warning - %s type %ld not recognized",
                       ID2string(iffid), type);
            free(lut);
        }
    }
}



static void
warnNonsquarePixels(uint8_t const xAspect,
                    uint8_t const yAspect) {

    if (xAspect != yAspect) {
        const char * const baseMsg = "warning - non-square pixels";

        if (pm_have_float_format())
            pm_message("%s; to fix do a 'pamscale -%cscale %g'",
                       baseMsg,
                       xAspect > yAspect ? 'x' : 'y',
                       xAspect > yAspect ? 
                       (float)xAspect/yAspect : 
                       (float)yAspect/xAspect);
        else
            pm_message("%s", baseMsg);
    }
}



static BitMapHeader *
read_bmhd(FILE *        const ifP,
          IFF_ID        const iffid,
          unsigned long const chunksize) {

    BitMapHeader * bmhdP;

    if (chunksize != BitMapHeaderSize) {
        pm_message("invalid size for %s chunk - skipping it", 
                   ID2string(iffid));
        skip_chunk(ifP, iffid, chunksize);
        bmhdP = NULL;
    } else {
        unsigned long remainingChunksize;

        MALLOCVAR_NOFAIL(bmhdP);

        remainingChunksize = chunksize;  /* initial value */
        
        bmhdP->w = get_big_short(ifP, iffid, &remainingChunksize);
        bmhdP->h = get_big_short(ifP, iffid, &remainingChunksize);
        bmhdP->x = get_big_short(ifP, iffid, &remainingChunksize);
        bmhdP->y = get_big_short(ifP, iffid, &remainingChunksize);
        bmhdP->nPlanes = get_byte(ifP, iffid, &remainingChunksize);
        bmhdP->masking = get_byte(ifP, iffid, &remainingChunksize);
        bmhdP->compression = get_byte(ifP, iffid, &remainingChunksize);
        bmhdP->flags = get_byte(ifP, iffid, &remainingChunksize);
        bmhdP->transparentColor =
            get_big_short(ifP, iffid, &remainingChunksize);
        bmhdP->xAspect = get_byte(ifP, iffid, &remainingChunksize);
        bmhdP->yAspect = get_byte(ifP, iffid, &remainingChunksize);
        bmhdP->pageWidth  = get_big_short(ifP, iffid, &remainingChunksize);
        bmhdP->pageHeight = get_big_short(ifP, iffid, &remainingChunksize);

        if (verbose) {
            if (typeid == ID_ILBM)
                pm_message("dimensions: %dx%d, %d planes", 
                           bmhdP->w, bmhdP->h, bmhdP->nPlanes);
            else
                pm_message("dimensions: %dx%d", bmhdP->w, bmhdP->h);

            if (typeid == ID_ILBM || typeid == ID_PBM) {
                pm_message("compression: %s",
                           bmhdP->compression <= cmpMAXKNOWN ?
                           cmpNAME[bmhdP->compression] : "unknown");

                switch(bmhdP->masking) {
                case mskNone:
                    break;
                case mskHasMask:
                case mskHasTransparentColor:
                    if (!maskfile)
                        pm_message("use '-maskfile <filename>' "
                                   "to generate a PBM mask file from %s", 
                                   mskNAME[bmhdP->masking]);
                    break;
                case mskLasso:
                    pm_message("warning - masking type '%s' not recognized", 
                               mskNAME[bmhdP->masking]);
                    break;
                default:
                    pm_error("unknown masking type %d", bmhdP->masking);
                }
            }
            else    /* RGBN/RGB8 */
                if (!maskfile)
                    pm_message("use '-maskfile <filename>' "
                               "to generate a PBM mask file "
                               "from genlock bits");
        }

        /* fix aspect ratio */
        if (bmhdP->xAspect == 0 || bmhdP->yAspect == 0) {
            pm_message("warning - illegal aspect ratio %d:%d, using 1:1", 
                       bmhdP->xAspect, bmhdP->yAspect);
            bmhdP->xAspect = bmhdP->yAspect = 1;
        }

        warnNonsquarePixels(bmhdP->xAspect, bmhdP->yAspect);
    }
    return bmhdP;
}


/****************************************************************************
 ILBM functions
 ****************************************************************************/


static void
read_ilbm_plane(FILE *          const ifP,
                unsigned long * const remainingChunksizeP,
                int             const bytes,
                int             const compression) {

    unsigned char *ubp;
    int j, byte;
    int bytesRemaining;

    bytesRemaining = bytes;  /* initial value */

    switch(compression) {
        case cmpNone:
            read_bytes(ifP, bytesRemaining, ilbmrow,
                       ID_BODY, remainingChunksizeP);
            break;
        case cmpByteRun1:
            ubp = ilbmrow;
            do {
                byte = (int)get_byte(ifP, ID_BODY, remainingChunksizeP);
                if( byte <= 127 ) {
                    j = byte;
                    bytesRemaining -= (j+1);
                    if( bytesRemaining < 0 )
                        pm_error("error doing ByteRun1 decompression");
                    for( ; j >= 0; j-- )
                        *ubp++ = get_byte(ifP, ID_BODY, remainingChunksizeP);
                }
                else
                if ( byte != 128 ) {
                    j = 256 - byte;
                    bytesRemaining -= (j+1);
                    if( bytesRemaining < 0 )
                        pm_error("error doing ByteRun1 decompression");
                    byte = (int)get_byte(ifP, ID_BODY, remainingChunksizeP);
                    for( ; j >= 0; j-- )
                        *ubp++ = (unsigned char)byte;
                }
                /* 128 is a NOP */
            }
            while( bytesRemaining > 0 );
            break;
        default:
            pm_error("unknown compression type %d", compression);
    }
}


static const unsigned char bit_mask[] = {1, 2, 4, 8, 16, 32, 64, 128};

static void
decode_row(FILE *          const ifP,
           unsigned long * const remainingChunksizeP,
           rawtype *       const chunkyrow,
           int             const nPlanes,
           BitMapHeader *  const bmhdP) {

    int plane, col, cols, cbit, bytes;
    unsigned char *ilp;
    rawtype *chp;

    cols = bmhdP->w;
    overflow_add(cols, 15);
    bytes = RowBytes(cols);
    for( plane = 0; plane < nPlanes; plane++ ) {
        int mask;

        mask = 1 << plane;
        read_ilbm_plane(ifP, remainingChunksizeP, bytes, bmhdP->compression);

        ilp = ilbmrow;
        chp = chunkyrow;

        cbit = 7;
        for( col = 0; col < cols; col++, cbit--, chp++ ) {
            if( cbit < 0 ) {
                cbit = 7;
                ilp++;
            }
            if( *ilp & bit_mask[cbit] )
                *chp |= mask;
            else
                *chp &= ~mask;
        }
    }
}


static void
decode_mask(FILE *          const ifP,
            unsigned long * const remainingChunksizeP,
            rawtype *       const chunkyrow,
            BitMapHeader *  const bmhdP) {

    int col, cols, cbit;
    unsigned char *ilp;

    cols = bmhdP->w;
    switch (bmhdP->masking) {
    case mskNone:
        break;
    case mskHasMask:        /* mask plane */
        read_ilbm_plane(ifP, remainingChunksizeP, RowBytes(cols), 
                        bmhdP->compression);
        if (maskfile) {
            ilp = ilbmrow;
            cbit = 7;
            for (col = 0; col < cols; ++col, --cbit) {
                if (cbit < 0) {
                    cbit = 7;
                    ++ilp;
                }
                if (*ilp & bit_mask[cbit])
                    maskrow[col] = PBM_BLACK;
                else
                    maskrow[col] = PBM_WHITE;
            }
            pbm_writepbmrow(maskfile, maskrow, cols, 0);
            wrotemask = true;
        }
        break;
    case mskHasTransparentColor:
        if (!chunkyrow)
            pm_error("decode_mask(): chunkyrow == NULL - can't happen");
        
        if (maskfile) {
            for (col = 0; col < cols; ++col) {
                if (chunkyrow[col] == bmhdP->transparentColor)
                    maskrow[col] = PBM_WHITE;
                else
                    maskrow[col] = PBM_BLACK;
            }
            pbm_writepbmrow(maskfile, maskrow, cols, 0);
                wrotemask = true;
        }
        break;
    case mskLasso:
        pm_error("This program does not know how to process Lasso masking");
        break;
    default:
        pm_error("decode_mask(): unknown masking type %d - can't happen", 
                 bmhdP->masking);
    }
}


/****************************************************************************
 Multipalette handling
 ****************************************************************************/

static void *
xmalloc2(x, y)
    int x;
    int y;
{
    void *mem;

    overflow2(x,y);
    if( x * y == 0 )
        return NULL;

    mem = malloc2(x,y);
    if( mem == NULL )
        pm_error("out of memory allocating %d bytes", x * y);
    return mem;
}


static void
multi_adjust(cmap, row, palchange)
    ColorMap *cmap;
    int row;
    PaletteChange *palchange;
{
    int i, reg;

    for( i = 0; palchange[i].reg != MP_REG_END; i++ ) {
        reg = palchange[i].reg;
        if( reg >= cmap->ncolors ) {
            pm_message("warning - palette change register out of range");
            pm_message("    row %d  change structure %d  reg=%d (max %d)", 
                       row, i, reg, cmap->ncolors-1);
            pm_message("    ignoring it...  "
                       "colors might get messed up from here");
        }
        else
        if( reg != MP_REG_IGNORE ) {
            PPM_ASSIGN(cmap->color[reg], 
                       palchange[i].r, palchange[i].g, palchange[i].b);
        }
    }
}

static void
multi_init(cmap, viewportmodes)
    ColorMap *cmap;
    long viewportmodes;
{
    if( cmap->mp_init )
        multi_adjust(cmap, -1, cmap->mp_init);
    if( !(viewportmodes & vmLACE) )
        cmap->mp_flags &= ~(MP_FLAGS_SKIPLACED);
}

static void
multi_update(cmap, row)
    ColorMap *cmap;
    int row;
{
    if( cmap->mp_flags & MP_FLAGS_SKIPLACED ) {
        if( ODD(row) )
            return;
        if( row/2 < cmap->mp_rows && cmap->mp_change[row/2] )
            multi_adjust(cmap, row, cmap->mp_change[row/2]);
    }
    else {
        if( row < cmap->mp_rows && cmap->mp_change[row] )
            multi_adjust(cmap, row, cmap->mp_change[row]);
    }
}

static void
multi_free(cmap)
    ColorMap *cmap;
{
    int i;

    if( cmap->mp_init ) {
        free(cmap->mp_init);
        cmap->mp_init = NULL;
    }
    if( cmap->mp_change ) {
        for( i = 0; i < cmap->mp_rows; i++ ) {
            if( cmap->mp_change[i] )
                free(cmap->mp_change[i]);
        }
        free(cmap->mp_change);
        cmap->mp_change = NULL;
    }
    cmap->mp_rows = 0;
    cmap->mp_type = 0;
    cmap->mp_flags = 0;
}



/****************************************************************************
 Colormap handling
 ****************************************************************************/



static void
analyzeCmapSamples(const ColorMap * const cmapP,
                   pixval *         const maxSampleP,
                   bool *           const shiftedP) {

    pixval       maxSample;
    bool         shifted;
    unsigned int i;
        
    for (i = 0, maxSample = 0, shifted = true; i < cmapP->ncolors; ++i) {
        pixval const r = PPM_GETR(cmapP->color[i]);
        pixval const g = PPM_GETG(cmapP->color[i]);
        pixval const b = PPM_GETB(cmapP->color[i]);

        maxSample = MAX(maxSample, MAX(r, MAX(g, b)));

        if (r & 0x0f || g & 0x0f || b & 0x0f)
            shifted = false;
    }
    *shiftedP   = shifted;
    *maxSampleP = maxSample;
}



static void
transformCmap(ColorMap * const cmapP) {

    pixval maxSample;
        /* The maximum sample value in *cmapP input */
    bool shifted;
        /* Samples in the *cmapP input appear to be 4 bit (maxval 15) original
           values shifted left 4 places to make 8 bit (maxval 255) samples.
        */

    analyzeCmapSamples(cmapP, &maxSample, &shifted);

    if (maxSample == 0)
        pm_message("warning - black colormap");
    else if (shifted || maxSample <= 15) {
        if (!adjustcolors) {
            pm_message("warning - probably %s4-bit colormap",
                       shifted ? "shifted " : "");
            pm_message("Use '-adjustcolors' to scale colormap to 8 bits");
        } else {
            unsigned int i;
            pm_message("scaling colormap to 8 bits");
            for (i = 0; i < cmapP->ncolors; ++i) {
                pixval r, g, b;
                r = PPM_GETR(cmapP->color[i]);
                g = PPM_GETG(cmapP->color[i]);
                b = PPM_GETB(cmapP->color[i]);
                if (shifted) {
                    r >>= 4;
                    g >>= 4;
                    b >>= 4;
                }
                r *= FACTOR_4BIT;
                g *= FACTOR_4BIT;
                b *= FACTOR_4BIT;
                PPM_ASSIGN(cmapP->color[i], r, g, b);
            }
        }
    }
}



static pixel *
transpColor(const BitMapHeader * const bmhdP,
            ColorMap *           const cmapP,
            const char *         const transpName,
            pixval               const maxval) {

    pixel * transpColorP;

    if (bmhdP) {
        if (bmhdP->masking == mskHasTransparentColor || 
            bmhdP->masking == mskLasso) {
            MALLOCVAR_NOFAIL(transpColorP);

            if (transpName)
                *transpColorP = ppm_parsecolor(transpName, maxval);
            else {
                unsigned short const transpIdx = bmhdP->transparentColor;
                if (HAS_COLORMAP(cmapP)) {
                    if (transpIdx >= cmapP->ncolors) {
                        pm_message("using default transparent color (black)");
                        PPM_ASSIGN(*transpColorP, 0, 0, 0);
                    } else
                        *transpColorP = cmapP->color[transpIdx];
                } else {
                    /* The color index is just a direct gray level */
                    PPM_ASSIGN(*transpColorP, transpIdx, transpIdx, transpIdx);
                }
            }
        } else
            transpColorP = NULL;
    } else
        transpColorP = NULL;

    return transpColorP;
}



static void
prepareCmap(const BitMapHeader * const bmhdP,
            ColorMap *           const cmapP) {
/*----------------------------------------------------------------------------
   This is a really ugly subroutine that 1) analyzes a colormap and its
   context (returning the analysis in global variables); and 2) modifies that
   color map, because it's really one type of data structure as input and
   another as output.
-----------------------------------------------------------------------------*/
    bool bmhdCmapOk;

    if (bmhdP)
        bmhdCmapOk = (bmhdP->flags & BMHD_FLAGS_CMAPOK);
    else
        bmhdCmapOk = false;

    if (HAS_COLORMAP(cmapP) && !bmhdCmapOk)
        transformCmap(cmapP);
}



static pixval
lookup_red(cmap, oldval)
    ColorMap *cmap;
    int oldval;
{
    if( cmap && cmap->redlut && oldval < 256 )
        return cmap->redlut[oldval];
    else
        return oldval;
}

static pixval
lookup_green(cmap, oldval)
    ColorMap *cmap;
    int oldval;
{
    if( cmap && cmap->greenlut && oldval < 256 )
        return cmap->greenlut[oldval];
    else
        return oldval;
}

static pixval
lookup_blue(cmap, oldval)
    ColorMap *cmap;
    int oldval;
{
    if( cmap && cmap->bluelut && oldval < 256 )
        return cmap->bluelut[oldval];
    else
        return oldval;
}

static pixval
lookup_mono(cmap, oldval)
    ColorMap *cmap;
    int oldval;
{
    if( cmap && cmap->monolut && oldval < 256 )
        return cmap->monolut[oldval];
    else
        return oldval;
}

static ColorMap *
ehbcmap(cmap)
    ColorMap *cmap;
{
    pixel *tempcolor = NULL;
    int i, col;

    col = cmap->ncolors;

    tempcolor = ppm_allocrow(col * 2);
    for( i = 0; i < col; i++ ) {
        tempcolor[i] = cmap->color[i];
        PPM_ASSIGN(tempcolor[col + i],  PPM_GETR(cmap->color[i]) / 2,
                                        PPM_GETG(cmap->color[i]) / 2,
                                        PPM_GETB(cmap->color[i]) / 2 );
    }
    ppm_freerow(cmap->color);
    cmap->color = tempcolor;
    cmap->ncolors *= 2;

    return cmap;
}



static pixval
lut_maxval(ColorMap * const cmap, 
           pixval     const maxval) {

    pixval retval;
    
    if (maxval >= 255)
        retval = maxval;
    else {
        if (!HAS_COLORLUT(cmap))
            retval = maxval;
        else {
            unsigned int i;
            unsigned char maxlut;
            maxlut = maxval;
            for( i = 0; i < maxval; i++ ) {
                if( cmap->redlut   && cmap->redlut[i]   > maxlut ) 
                    maxlut = cmap->redlut[i];
                if( cmap->greenlut && cmap->greenlut[i] > maxlut ) 
                    maxlut = cmap->greenlut[i];
                if( cmap->bluelut  && cmap->bluelut[i]  > maxlut ) 
                    maxlut = cmap->bluelut[i];
            }
            pm_message("warning - "
                       "%d-bit index into 8-bit color lookup table, "
                       "table maxval=%d", 
                       pm_maxvaltobits(maxval), maxlut);
            if( maxlut != maxval )
                retval = 255;
            else
                retval = maxval;
            pm_message("    assuming image maxval=%d", retval);
        }
    }
    return retval;
}



static void
get_color(cmap, idx, red, green, blue)
    ColorMap *cmap;
    int idx;
    pixval *red, *green, *blue;
{
    if( HAS_COLORMAP(cmap) ) {
        pixval r, g, b;

        if( idx >= cmap->ncolors )
            pm_error("color index out of range: %d (max %d)", 
                     idx, cmap->ncolors);
        r = PPM_GETR(cmap->color[idx]);
        g = PPM_GETG(cmap->color[idx]);
        b = PPM_GETB(cmap->color[idx]);

        *red    = lookup_red(cmap, r);
        *green  = lookup_green(cmap, g);
        *blue   = lookup_blue(cmap, b);
    }
    else {
        *red = *green = *blue = lookup_mono(cmap, idx);
    }
}


/****************************************************************************
 Conversion functions
 ****************************************************************************/

static void
std_to_ppm(FILE *         const ifP, 
           long           const chunksize, 
           BitMapHeader * const bmhdP, 
           ColorMap *     const cmap, 
           long           const viewportmodes);



static void
ham_to_ppm(FILE *         const ifP, 
           long           const chunksize, 
           BitMapHeader * const bmhdP, 
           ColorMap *     const cmap, 
           long           const viewportmodes) {

    int cols, rows, hambits, hammask, hamshift, hammask2, col, row;
    pixval maxval;
    rawtype *rawrow;
    unsigned char hamlut[256];

    cols = bmhdP->w;
    rows = bmhdP->h;
    hambits = bmhdP->nPlanes - 2;
    hammask = (1 << hambits) - 1;
    hamshift = 8 - hambits;
    hammask2 = (1 << hamshift) - 1;

    if( hambits > 8 || hambits < 1 ) {
        int const assumed_viewportmodes = viewportmodes & ~(vmHAM);

        pm_message("%d-plane HAM?? - interpreting image as a normal ILBM", 
                   bmhdP->nPlanes);
        std_to_ppm(ifP, chunksize, bmhdP, cmap, assumed_viewportmodes);
        return;
    } else {
        unsigned long remainingChunksize;
        pixel * transpColorP;

        pm_message("input is a %sHAM%d file", 
                   HAS_MULTIPALETTE(cmap) ? "multipalette " : "", 
                   bmhdP->nPlanes);

        if( HAS_COLORLUT(cmap) || HAS_MONOLUT(cmap) ) {
            pm_message("warning - color lookup tables ignored in HAM");
            if( cmap->redlut )      free(cmap->redlut);
            if( cmap->greenlut )    free(cmap->greenlut);
            if( cmap->bluelut )     free(cmap->bluelut);
            if( cmap->monolut )     free(cmap->monolut);
            cmap->redlut = cmap->greenlut = cmap->bluelut = 
                cmap->monolut = NULL;
        }
        if( !HAS_COLORMAP(cmap) ) {
            pm_message("no colormap - interpreting values as grayscale");
            maxval = pm_bitstomaxval(hambits);
            for( col = 0; col <= maxval; col++ )
                hamlut[col] = col * MAXCOLVAL / maxval;
            cmap->monolut = hamlut;
        }

        transpColorP = transpColor(bmhdP, cmap, transpName, MAXCOLVAL);

        if( HAS_MULTIPALETTE(cmap) )
            multi_init(cmap, viewportmodes);

        rawrow = alloc_rawrow(cols);

        ppm_writeppminit(stdout, cols, rows, MAXCOLVAL, 0);

        remainingChunksize = chunksize;  /* initial value */

        for (row = 0; row < rows; ++row) {
            pixval r, g, b;

            if( HAS_MULTIPALETTE(cmap) )
                multi_update(cmap, row);

            decode_row(ifP, &remainingChunksize, rawrow, bmhdP->nPlanes, bmhdP);
            decode_mask(ifP, &remainingChunksize, rawrow, bmhdP);

            r = g = b = 0;
            for( col = 0; col < cols; col++ ) {
                int idx = rawrow[col] & hammask;

                if( transpColorP && maskrow && maskrow[col] == PBM_WHITE )
                    pixelrow[col] = *transpColorP;
                else {
                    switch((rawrow[col] >> hambits) & 0x03) {
                    case HAMCODE_CMAP:
                        get_color(cmap, idx, &r, &g, &b);
                        break;
                    case HAMCODE_BLUE:
                        b = ((b & hammask2) | (idx << hamshift));
                        /*b = hamlut[idx];*/
                        break;
                    case HAMCODE_RED:
                        r = ((r & hammask2) | (idx << hamshift));
                        /*r = hamlut[idx];*/
                        break;
                    case HAMCODE_GREEN:
                        g = ((g & hammask2) | (idx << hamshift));
                        /*g = hamlut[idx];*/
                        break;
                    default:
                        pm_error("ham_to_ppm(): "
                                 "impossible HAM code - can't happen");
                    }
                    PPM_ASSIGN(pixelrow[col], r, g, b);
                }
            }
            ppm_writeppmrow(stdout, pixelrow, cols, MAXCOLVAL, 0);
        }
        chunk_end(ifP, ID_BODY, remainingChunksize);
    }
}



static void
std_to_ppm(FILE *         const ifP, 
           long           const chunksize, 
           BitMapHeader * const bmhdP, 
           ColorMap *     const cmap, 
           long           const viewportmodes) {

    if (viewportmodes & vmHAM) {
        ham_to_ppm(ifP, chunksize, bmhdP, cmap, viewportmodes);
    } else {
        unsigned int const cols = bmhdP->w;
        unsigned int const rows = bmhdP->h;

        rawtype *rawrow;
        unsigned int row, col;
        pixval maxval;
        unsigned long remainingChunksize;
        pixel * transpColorP;

        pm_message("input is a %d-plane %s%sILBM", bmhdP->nPlanes,
                   HAS_MULTIPALETTE(cmap) ? "multipalette " : "",
                   viewportmodes & vmEXTRA_HALFBRITE ? "EHB " : ""
            );

        if( bmhdP->nPlanes > MAXPLANES )
            pm_error("too many planes (max %d)", MAXPLANES);

        if( HAS_COLORMAP(cmap) ) {
            if( viewportmodes & vmEXTRA_HALFBRITE )
                ehbcmap(cmap);  /* Modifies *cmap */
            maxval = MAXCOLVAL;
        }
        else {
            pm_message("no colormap - interpreting values as grayscale");
            maxval = lut_maxval(cmap, pm_bitstomaxval(bmhdP->nPlanes));
            if( maxval > PPM_OVERALLMAXVAL )
                pm_error("nPlanes is too large");
        }

        transpColorP = transpColor(bmhdP, cmap, transpName, maxval);

        rawrow = alloc_rawrow(cols);

        if( HAS_MULTIPALETTE(cmap) )
            multi_init(cmap, viewportmodes);

        ppm_writeppminit( stdout, cols, rows, maxval, 0 );

        remainingChunksize = chunksize;  /* initial value */
    
        for (row = 0; row < rows; ++row) {

            if( HAS_MULTIPALETTE(cmap) )
                multi_update(cmap, row);

            decode_row(ifP, &remainingChunksize, rawrow, bmhdP->nPlanes, bmhdP);
            decode_mask(ifP, &remainingChunksize, rawrow, bmhdP);

            for( col = 0; col < cols; col++ ) {
                pixval r, g, b;
                if( transpColorP && maskrow && maskrow[col] == PBM_WHITE )
                    pixelrow[col] = *transpColorP;
                else {
                    get_color(cmap, rawrow[col], &r, &g, &b);
                    PPM_ASSIGN(pixelrow[col], r, g, b);
                }
            }
            ppm_writeppmrow(stdout, pixelrow, cols, maxval, 0);
        }
        chunk_end(ifP, ID_BODY, remainingChunksize);
    }
}



static void
deep_to_ppm(FILE *         const ifP, 
            long           const chunksize, 
            BitMapHeader * const bmhdP, 
            ColorMap *     const cmap) {

    unsigned int const cols = bmhdP->w;
    unsigned int const rows = bmhdP->h;
    unsigned int const planespercolor = bmhdP->nPlanes / 3;

    unsigned int col, row;
    rawtype *Rrow, *Grow, *Brow;
    pixval maxval;
    unsigned long remainingChunksize;
    pixel * transpColorP;

    pm_message("input is a deep (%d-bit) ILBM", bmhdP->nPlanes);
    if( planespercolor > MAXPLANES )
        pm_error("too many planes (max %d)", MAXPLANES * 3);

    if( bmhdP->masking == mskHasTransparentColor || 
        bmhdP->masking == mskLasso ) {
        pm_message("masking type '%s' in a deep ILBM?? - ignoring it", 
                   mskNAME[bmhdP->masking]);
        bmhdP->masking = mskNone;
    }

    maxval = lut_maxval(cmap, pm_bitstomaxval(planespercolor));
    if( maxval > PPM_OVERALLMAXVAL )
        pm_error("nPlanes is too large");

    transpColorP = transpColor(bmhdP, cmap, transpName, maxval);
        
    Rrow = alloc_rawrow(cols);
    Grow = alloc_rawrow(cols);
    Brow = alloc_rawrow(cols);

    ppm_writeppminit(stdout, cols, rows, maxval, 0);

    remainingChunksize = chunksize;  /* initial value */

    for( row = 0; row < rows; row++ ) {
        decode_row(ifP, &remainingChunksize, Rrow, planespercolor, bmhdP);
        decode_row(ifP, &remainingChunksize, Grow, planespercolor, bmhdP);
        decode_row(ifP, &remainingChunksize, Brow, planespercolor, bmhdP);
        decode_mask(ifP, &remainingChunksize, NULL, bmhdP);

        for( col = 0; col < cols; col++ ) {
            if( transpColorP && maskrow && maskrow[col] == PBM_WHITE )
                pixelrow[col] = *transpColorP;
            else
                PPM_ASSIGN(pixelrow[col],   lookup_red(cmap, Rrow[col]),
                                            lookup_green(cmap, Grow[col]),
                                            lookup_blue(cmap, Brow[col]) );
        }
        ppm_writeppmrow(stdout, pixelrow, cols, maxval, 0);
    }
    chunk_end(ifP, ID_BODY, remainingChunksize);
}



static void
dcol_to_ppm(FILE *         const ifP,
            long           const chunksize,
            BitMapHeader * const bmhdP,
            ColorMap *     const cmap,
            DirectColor *  const dcol) {

    unsigned int const cols = bmhdP->w;
    unsigned int const rows = bmhdP->h;
    unsigned int const redplanes   = dcol->r;
    unsigned int const greenplanes = dcol->g;
    unsigned int const blueplanes  = dcol->b;
    
    int col, row;
    rawtype *Rrow, *Grow, *Brow;
    pixval maxval, redmaxval, greenmaxval, bluemaxval;
    pixval *redtable, *greentable, *bluetable;
    unsigned long remainingChunksize;
    pixel * transpColorP;

    pm_message("input is a %d:%d:%d direct color ILBM",
                redplanes, greenplanes, blueplanes);

    if( redplanes > MAXPLANES || 
        blueplanes > MAXPLANES || 
        greenplanes > MAXPLANES )
        pm_error("too many planes (max %d per color component)", MAXPLANES);

    if( bmhdP->nPlanes != (redplanes + greenplanes + blueplanes) )
        pm_error("%s/%s plane number mismatch", 
                 ID2string(ID_BMHD), ID2string(ID_DCOL));

    if( bmhdP->masking == mskHasTransparentColor || 
        bmhdP->masking == mskLasso ) {
        pm_message("masking type '%s' in a direct color ILBM?? - ignoring it",
                   mskNAME[bmhdP->masking]);
        bmhdP->masking = mskNone;
    }

    if( HAS_COLORLUT(cmap) ) {
        pm_error("This program does not know how to process a %s chunk "
                 "in direct color", ID2string(ID_CLUT));
        cmap->redlut = cmap->greenlut = cmap->bluelut = NULL;
    }

    redmaxval   = pm_bitstomaxval(redplanes);
    greenmaxval = pm_bitstomaxval(greenplanes);
    bluemaxval  = pm_bitstomaxval(blueplanes);
    maxval = MAX(redmaxval, MAX(greenmaxval, bluemaxval));

    if( maxval > PPM_OVERALLMAXVAL )
        pm_error("too many planes");

    if( redmaxval != maxval || greenmaxval != maxval || bluemaxval != maxval )
        pm_message("scaling colors to %d bits", pm_maxvaltobits(maxval));
    
    overflow_add(redmaxval, 1);
    overflow_add(greenmaxval, 1);
    overflow_add(bluemaxval, 1);
    MALLOCARRAY_NOFAIL(redtable,   redmaxval   +1);
    MALLOCARRAY_NOFAIL(greentable, greenmaxval +1);
    MALLOCARRAY_NOFAIL(bluetable,  bluemaxval  +1);

    {
        unsigned int i;
        for (i = 0; i <= redmaxval; ++i)
            redtable[i] = ROUNDDIV(i * maxval, redmaxval);
        for (i = 0; i <= greenmaxval; ++i)
            greentable[i] = ROUNDDIV(i * maxval, greenmaxval);
        for (i = 0; i <= bluemaxval; ++i)
            bluetable[i] = ROUNDDIV(i * maxval, bluemaxval);
    }
    transpColorP = transpColor(bmhdP, cmap, transpName, maxval);

    Rrow = alloc_rawrow(cols);
    Grow = alloc_rawrow(cols);
    Brow = alloc_rawrow(cols);

    ppm_writeppminit(stdout, cols, rows, maxval, 0);

    remainingChunksize = chunksize;  /* initial value */

    for( row = 0; row < rows; row++ ) {
        decode_row(ifP, &remainingChunksize, Rrow, redplanes, bmhdP);
        decode_row(ifP, &remainingChunksize, Grow, greenplanes, bmhdP);
        decode_row(ifP, &remainingChunksize, Brow, blueplanes, bmhdP);
        decode_mask(ifP, &remainingChunksize, NULL, bmhdP);

        for( col = 0; col < cols; col++ ) {
            if( transpColorP && maskrow && maskrow[col] == PBM_WHITE )
                pixelrow[col] = *transpColorP;
            else
                PPM_ASSIGN( pixelrow[col],  redtable[Rrow[col]],
                                            greentable[Grow[col]],
                                            bluetable[Brow[col]]   );
        }
        ppm_writeppmrow(stdout, pixelrow, cols, maxval, 0);
    }
    chunk_end(ifP, ID_BODY, remainingChunksize);
}



static void
cmapToPpm(FILE *     const ofP,
            ColorMap * const cmapP) {

    ppm_colorrowtomapfile(ofP, cmapP->color, cmapP->ncolors, MAXCOLVAL);
}



static void
ipbm_to_ppm(FILE *         const ifP,
            long           const chunksize,
            BitMapHeader * const bmhdP,
            ColorMap *     const cmap,
            long           const viewportmodes) {

    unsigned int const cols = bmhdP->w;
    unsigned int const rows = bmhdP->h;

    int col, row;
    pixval maxval;
    unsigned long remainingChunksize;
    pixel * transpColorP;

    pm_message("input is a %sPBM ", 
               HAS_MULTIPALETTE(cmap) ? "multipalette " : "");

    if( bmhdP->nPlanes != 8 )
        pm_error("invalid number of planes for IFF-PBM: %d (must be 8)", 
                 bmhdP->nPlanes);

    if( bmhdP->masking == mskHasMask )
        pm_error("Image has a maskplane, which is invalid in IFF-PBM");

    if( HAS_COLORMAP(cmap) )
        maxval = MAXCOLVAL;
    else {
        pm_message("no colormap - interpreting values as grayscale");
        maxval = lut_maxval(cmap, pm_bitstomaxval(bmhdP->nPlanes));
    }

    transpColorP = transpColor(bmhdP, cmap, transpName, maxval);

    if( HAS_MULTIPALETTE(cmap) )
        multi_init(cmap, viewportmodes);

    MALLOCARRAY_NOFAIL(ilbmrow, cols);

    ppm_writeppminit(stdout, cols, rows, maxval, 0);

    remainingChunksize = chunksize;  /* initial value */

    for( row = 0; row < rows; row++ ) {
        if( HAS_MULTIPALETTE(cmap) )
            multi_update(cmap, row);
        
        read_ilbm_plane(ifP, &remainingChunksize, cols, bmhdP->compression);

        for( col = 0; col < cols; col++ ) {
            pixval r, g, b;
            if( transpColorP && maskrow && maskrow[col] == PBM_WHITE )
                pixelrow[col] = *transpColorP;
            else {
                get_color(cmap, ilbmrow[col], &r, &g, &b);
                PPM_ASSIGN(pixelrow[col], r, g, b);
            }
        }
        ppm_writeppmrow(stdout, pixelrow, cols, maxval, 0);
    }
    chunk_end(ifP, ID_BODY, remainingChunksize);
}



static void
rgbn_to_ppm(FILE *         const ifP,
            long           const chunksize,
            BitMapHeader * const bmhdP,
            ColorMap *     const cmap  /* for CLUTs */
    ) {

    unsigned int const rows = bmhdP->h;
    unsigned int const cols = bmhdP->w;

    unsigned int row;
    unsigned int count;
    pixval maxval;
    unsigned long remainingChunksize;
    pixel * transpColorP;

    pm_message("input is a %d-bit RGB image", (typeid == ID_RGB8 ? 8 : 4));

    if (bmhdP->compression != 4)
        pm_error("invalid compression mode for %s: %d (must be 4)", 
                 ID2string(typeid), bmhdP->compression);
    
    switch (typeid) {
    case ID_RGBN:
        if (bmhdP->nPlanes != 13)
            pm_error("invalid number of planes for %s: %d (must be 13)", 
                     ID2string(typeid), bmhdP->nPlanes);
        maxval = lut_maxval(cmap, 15);
        break;
    case ID_RGB8:
        if (bmhdP->nPlanes != 25)
            pm_error("invalid number of planes for %s: %d (must be 25)", 
                     ID2string(typeid), bmhdP->nPlanes);
        maxval = 255;
        break;
    default:
        pm_error("rgbn_to_ppm(): invalid IFF ID %s - can't happen", 
                 ID2string(typeid));
    }

    transpColorP = transpColor(bmhdP, cmap, transpName, maxval);

    ppm_writeppminit(stdout, cols, rows, maxval, 0);

    for (row = 0, count = 0, remainingChunksize = chunksize;
         row < rows;
         ++row) {

        unsigned int col;

        for (col = 0; col < cols; ++col) {
            unsigned int tries;
            unsigned int genlock;
            pixval r, g, b;

            tries = 0;
            while (count == 0) {
                if (typeid == ID_RGB8) {
                    r = lookup_red(cmap,   get_byte(ifP, ID_BODY, 
                                                    &remainingChunksize));
                    g = lookup_green(cmap, get_byte(ifP, ID_BODY,
                                                    &remainingChunksize));
                    b = lookup_blue(cmap,  get_byte(ifP, ID_BODY,
                                                    &remainingChunksize));
                    count = get_byte(ifP, ID_BODY, &remainingChunksize);
                    genlock = count & 0x80;
                    count &= 0x7f;
                } else {
                    unsigned int const word =
                        get_big_short(ifP, ID_BODY, &remainingChunksize);
                    r = lookup_red(cmap, (word & 0xf000) >> 12);
                    g = lookup_green(cmap, (word & 0x0f00) >> 8);
                    b = lookup_blue(cmap, (word & 0x00f0) >> 4);
                    genlock = word & 0x0008;
                    count = word & 0x0007;
                }
                if (!count) {
                    count = get_byte(ifP, ID_BODY, &remainingChunksize);
                    if (count == 0)
                        count =
                            get_big_short(ifP, ID_BODY, &remainingChunksize);
                    if (count == 0)
                        ++tries;
                }
            }
            if (tries > 0) {
                pm_message("warning - repeat count 0 at col %u row %u: "
                           "skipped %u RGB entr%s",
                            col, row, tries, (tries == 1 ? "y" : "ies"));
            }
            if (maskfile) {
                /* genlock bit set -> transparent */
                if (genlock)
                    maskrow[col] = PBM_WHITE;
                else
                    maskrow[col] = PBM_BLACK;
            }
            if (transpColorP && maskrow && maskrow[col] == PBM_WHITE)
                pixelrow[col] = *transpColorP;
            else
                PPM_ASSIGN(pixelrow[col], r, g, b);
            --count;
        }
        ppm_writeppmrow(stdout, pixelrow, cols, maxval, 0);
        if (maskfile) {
            pbm_writepbmrow(maskfile, maskrow, cols, 0);
            wrotemask = true;
        }
    }
    chunk_end(ifP, ID_BODY, remainingChunksize);
}

/****************************************************************************
 Multipalette chunk reader

    Currently there are three multipalette formats:
        SHAM - sliced HAM (obselete)
        CTBL - dynamic HAM/Hires (obselete)
        PCHG - palette change
    There is no official documentation available for SHAM and CTBL, so
   this is mostly guesswork from other sources and hexdumps of pictures...

    CTBL format (dynamic HAM/Hires):
        16 bigendian words per row (16 bit: 0000rrrrggggbbbb)
    This is simply an entire 4-bit colormap per row.
    I have no idea what the DYCP chunk is for.

    SHAM format (sliced HAM):
        1 word  - version info (?) - always 0
        16 bigendian words per row (16 bit: 0000rrrrggggbbbb)
    If the picture is laced, then there are only rows/2 changes, don't change
    palette on odd lines.

    PCHG format: A detailed description of this format is available
    from AmiNet as PCHGLib12.lha.

 ****************************************************************************/

/* Turn big-endian 4-byte long and 2-byte short stored at x (unsigned char *)
 * into the native format of the CPU
 */
#define BIG_LONG(x) (   ((unsigned long)((x)[0]) << 24) + \
                        ((unsigned long)((x)[1]) << 16) + \
                        ((unsigned long)((x)[2]) <<  8) + \
                        ((unsigned long)((x)[3]) <<  0) )
#define BIG_WORD(x) (   ((unsigned short)((x)[0]) << 8) + \
                        ((unsigned short)((x)[1]) << 0) )



static void
read_4bit_mp(FILE *     const ifP,
             IFF_ID     const iffid,
             long       const chunksize,
             ColorMap * const cmap) {

    int row, rows, i, type;
    short data;
    unsigned long remainingChunksize;

    type = (iffid == ID_SHAM ? MP_TYPE_SHAM : MP_TYPE_CTBL);

    if( cmap->mp_type >= type ) {
        skip_chunk(ifP, iffid, chunksize);
    } else {
        if( cmap->mp_type )
            multi_free(cmap);
        cmap->mp_type = type;

        remainingChunksize = chunksize;  /* initial value */

        if( type == MP_TYPE_SHAM ) {
            cmap->mp_flags = MP_FLAGS_SKIPLACED;
            get_big_short(ifP, iffid, &remainingChunksize); /* skip first wd */
        }

        cmap->mp_rows = rows = remainingChunksize/32; /* sizeof(word) * 16 */
        MALLOCARRAY_NOFAIL(cmap->mp_change, rows);

        for( row = 0; row < rows; row++ ) {
            MALLOCARRAY_NOFAIL(cmap->mp_change[row], 17);   /* 16 + sentinel */
            for( i = 0; i < 16; i++ ) {
                data = get_big_short(ifP, iffid, &remainingChunksize);
                cmap->mp_change[row][i].reg = i;
                cmap->mp_change[row][i].r =
                    ((data & 0x0f00) >> 8) * FACTOR_4BIT;
                cmap->mp_change[row][i].g =
                    ((data & 0x00f0) >> 4) * FACTOR_4BIT;
                cmap->mp_change[row][i].b =
                    ((data & 0x000f) >> 0) * FACTOR_4BIT;
            }
            cmap->mp_change[row][16].reg = MP_REG_END;   /* sentinel */
        }
        chunk_end(ifP, iffid, remainingChunksize);
    }
}



static void
PCHG_DecompHuff(src, dest, tree, origsize)
    unsigned char *src, *dest;
    short *tree;
    unsigned long origsize;
{
    unsigned long i = 0, bits = 0;
    unsigned char thisbyte;
    short *p;

    p = tree;
    while( i < origsize ) {
        if( bits == 0 ) {
            thisbyte = *src++;
            bits = 8;
        }
        if( thisbyte & (1 << 7) ) {
            if( *p >= 0 ) {
                *dest++ = (unsigned char)*p;
                i++;
                p = tree;
            }
            else
                p += (*p / 2);
        }
        else {
            p--;
            if( *p > 0 && (*p & 0x100) ) {
                *dest++ = (unsigned char )*p;
                i++;
                p = tree;
            }
        }
        thisbyte <<= 1;
        bits--;
    }
}



static void
PCHG_Decompress(PCHGHeader *     const PCHG,
                PCHGCompHeader * const CompHdr,
                unsigned char *  const compdata,
                unsigned long    const compsize,
                unsigned char *  const comptree,
                unsigned char *  const data) {

    switch(PCHG->Compression) {
    case PCHG_COMP_HUFFMAN: {
        unsigned long const treesize = CompHdr->CompInfoSize;
        unsigned long const huffsize = treesize / 2;
        const bigend16 * const bigendComptree = (const void *)comptree;

        short * hufftree;
        unsigned long i;

        /* Convert big-endian 2-byte shorts to C shorts */

        MALLOCARRAY(hufftree, huffsize);

        if (!hufftree)
            pm_error("Couldn't get memory for %lu-byte Huffman tree",
                     huffsize);

        for (i = 0; i < huffsize; ++i)
            hufftree[i] = pm_uintFromBigend16(bigendComptree[i]);

        /* decompress the change structure data */
        PCHG_DecompHuff(compdata, data, &hufftree[huffsize-1], 
                        CompHdr->OriginalDataSize);
        
        free(hufftree);
    } break;
        default:
            pm_error("unknown PCHG compression type %d", PCHG->Compression);
    }
}


static void
PCHG_ConvertSmall(PCHG, cmap, mask, datasize)
    PCHGHeader *PCHG;
    ColorMap *cmap;
    unsigned char *mask;
    unsigned long datasize;
{
    unsigned char *data;
    unsigned char thismask;
    int bits, row, i, changes, masklen, reg;
    unsigned char ChangeCount16, ChangeCount32;
    unsigned short SmallChange;
    unsigned long totalchanges = 0;
    int changedlines = PCHG->ChangedLines;

    masklen = 4 * MaskLongWords(PCHG->LineCount);
    data = mask + masklen; datasize -= masklen;

    bits = 0;
    for( row = PCHG->StartLine; changedlines && row < 0; row++ ) {
        if( bits == 0 ) {
            if( masklen == 0 ) goto fail2;
            thismask = *mask++;
            --masklen;
            bits = 8;
        }
        if( thismask & (1<<7) ) {
            if( datasize < 2 ) goto fail;
            ChangeCount16 = *data++;
            ChangeCount32 = *data++;
            datasize -= 2;

            overflow_add(ChangeCount16, ChangeCount32);
            changes = ChangeCount16 + ChangeCount32;
            overflow_add(changes, 1);
            for( i = 0; i < changes; i++ ) {
                if( totalchanges >= PCHG->TotalChanges ) goto fail;
                if( datasize < 2 ) goto fail;
                SmallChange = BIG_WORD(data); data += 2; datasize -= 2;
                reg = ((SmallChange & 0xf000) >> 12) + 
                    (i >= ChangeCount16 ? 16 : 0);
                cmap->mp_init[reg - PCHG->MinReg].reg = reg;
                cmap->mp_init[reg - PCHG->MinReg].r = 
                    ((SmallChange & 0x0f00) >> 8) * FACTOR_4BIT;
                cmap->mp_init[reg - PCHG->MinReg].g = 
                    ((SmallChange & 0x00f0) >> 4) * FACTOR_4BIT;
                cmap->mp_init[reg - PCHG->MinReg].b = 
                    ((SmallChange & 0x000f) >> 0) * FACTOR_4BIT;
                ++totalchanges;
            }
            --changedlines;
        }
        thismask <<= 1;
        bits--;
    }

    for( row = PCHG->StartLine; changedlines && row < cmap->mp_rows; row++ ) {
        if( bits == 0 ) {
            if( masklen == 0 ) goto fail2;
            thismask = *mask++;
            --masklen;
            bits = 8;
        }
        if( thismask & (1<<7) ) {
            if( datasize < 2 ) goto fail;
            ChangeCount16 = *data++;
            ChangeCount32 = *data++;
            datasize -= 2;

            changes = ChangeCount16 + ChangeCount32;
            MALLOCARRAY_NOFAIL(cmap->mp_change[row], changes + 1);
            for( i = 0; i < changes; i++ ) {
                if( totalchanges >= PCHG->TotalChanges ) goto fail;
                if( datasize < 2 ) goto fail;
                SmallChange = BIG_WORD(data); data += 2; datasize -= 2;
                reg = ((SmallChange & 0xf000) >> 12) + 
                    (i >= ChangeCount16 ? 16 : 0);
                cmap->mp_change[row][i].reg = reg;
                cmap->mp_change[row][i].r = 
                    ((SmallChange & 0x0f00) >> 8) * FACTOR_4BIT;
                cmap->mp_change[row][i].g = 
                    ((SmallChange & 0x00f0) >> 4) * FACTOR_4BIT;
                cmap->mp_change[row][i].b = 
                    ((SmallChange & 0x000f) >> 0) * FACTOR_4BIT;
                ++totalchanges;
            }
            cmap->mp_change[row][changes].reg = MP_REG_END;
            --changedlines;
        }
        thismask <<= 1;
        bits--;
    }
    if( totalchanges != PCHG->TotalChanges )
        pm_message("warning - got %ld change structures, "
                   "chunk header reports %ld", 
                   totalchanges, PCHG->TotalChanges);
    return;
fail:
    pm_error("insufficient data in SmallLineChanges structures");
fail2:
    pm_error("insufficient data in line mask");
}



static void
PCHG_ConvertBig(PCHGHeader *    const PCHG,
                ColorMap *      const cmap,
                unsigned char * const maskStart,
                unsigned long   const datasize) {

    unsigned char * data;
    unsigned char thismask;
    int bits;
    unsigned int row;
    int changes;
    int masklen;
    int reg;
    unsigned long totalchanges;
    int changedlines;
    unsigned long dataRemaining;
    unsigned char * mask;

    mask = maskStart;  /* initial value */
    dataRemaining = datasize;  /* initial value */
    changedlines = PCHG->ChangedLines;  /* initial value */
    totalchanges = 0;  /* initial value */

    masklen = 4 * MaskLongWords(PCHG->LineCount);
    data = mask + masklen; dataRemaining -= masklen;

    for (row = PCHG->StartLine, bits = 0; changedlines && row < 0; ++row) {
        if (bits == 0) {
            if (masklen == 0)
                pm_error("insufficient data in line mask");
            thismask = *mask++;
            --masklen;
            bits = 8;
        }
        if (thismask & (1<<7)) {
            unsigned int i;

            if (dataRemaining < 2)
                pm_error("insufficient data in BigLineChanges structures");

            changes = BIG_WORD(data); data += 2; dataRemaining -= 2;

            for (i = 0; i < changes; ++i) {
                if (totalchanges >= PCHG->TotalChanges)
                    pm_error("insufficient data in BigLineChanges structures");

                if (dataRemaining < 6)
                    pm_error("insufficient data in BigLineChanges structures");

                reg = BIG_WORD(data); data += 2;
                cmap->mp_init[reg - PCHG->MinReg].reg = reg;
                ++data; /* skip alpha */
                cmap->mp_init[reg - PCHG->MinReg].r = *data++;
                cmap->mp_init[reg - PCHG->MinReg].b = *data++;  /* yes, RBG */
                cmap->mp_init[reg - PCHG->MinReg].g = *data++;
                dataRemaining -= 6;
                ++totalchanges;
            }
            --changedlines;
        }
        thismask <<= 1;
        --bits;
    }

    for (row = PCHG->StartLine; changedlines && row < cmap->mp_rows; ++row) {
        if (bits == 0) {
            if (masklen == 0)
                pm_error("insufficient data in line mask");

            thismask = *mask++;
            --masklen;
            bits = 8;
        }
        if (thismask & (1<<7)) {
            unsigned int i;

            if (dataRemaining < 2)
                pm_error("insufficient data in BigLineChanges structures");

            changes = BIG_WORD(data); data += 2; dataRemaining -= 2;

            MALLOCARRAY_NOFAIL(cmap->mp_change[row], changes + 1);
            for (i = 0; i < changes; ++i) {
                if (totalchanges >= PCHG->TotalChanges)
                    pm_error("insufficient data in BigLineChanges structures");

                if (dataRemaining < 6)
                    pm_error("insufficient data in BigLineChanges structures");

                reg = BIG_WORD(data); data += 2;
                cmap->mp_change[row][i].reg = reg;
                ++data; /* skip alpha */
                cmap->mp_change[row][i].r = *data++;
                cmap->mp_change[row][i].b = *data++;    /* yes, RBG */
                cmap->mp_change[row][i].g = *data++;
                dataRemaining -= 6;
                ++totalchanges;
            }
            cmap->mp_change[row][changes].reg = MP_REG_END;
            --changedlines;
        }
        thismask <<= 1;
        --bits;
    }
    if (totalchanges != PCHG->TotalChanges)
        pm_message("warning - got %ld change structures, "
                   "chunk header reports %ld", 
                   totalchanges, PCHG->TotalChanges);
}



static void
read_pchg(FILE *     const ifP,
          IFF_ID     const iffid,
          long       const chunksize,
          ColorMap * const cmap) {

    if( cmap->mp_type >= MP_TYPE_PCHG ) {
        skip_chunk(ifP, iffid, chunksize);
    } else {
        PCHGHeader      PCHG;
        unsigned char   *data;
        unsigned long   datasize;
        unsigned long   remainingChunksize;
        int i;

        if( cmap->mp_type )
            multi_free(cmap);
        cmap->mp_type = MP_TYPE_PCHG;

        remainingChunksize = chunksize;  /* initial value */

        PCHG.Compression = get_big_short(ifP, iffid, &remainingChunksize);
        PCHG.Flags       = get_big_short(ifP, iffid, &remainingChunksize);
        PCHG.StartLine   = get_big_short(ifP, iffid, &remainingChunksize);
        PCHG.LineCount   = get_big_short(ifP, iffid, &remainingChunksize);
        PCHG.ChangedLines= get_big_short(ifP, iffid, &remainingChunksize);
        PCHG.MinReg      = get_big_short(ifP, iffid, &remainingChunksize);
        PCHG.MaxReg      = get_big_short(ifP, iffid, &remainingChunksize);
        PCHG.MaxChanges  = get_big_short(ifP, iffid, &remainingChunksize);
        PCHG.TotalChanges= get_big_long(ifP, iffid, &remainingChunksize);

#ifdef DEBUG
        pm_message("PCHG StartLine   : %d", PCHG.StartLine);
        pm_message("PCHG LineCount   : %d", PCHG.LineCount);
        pm_message("PCHG ChangedLines: %d", PCHG.ChangedLines);
        pm_message("PCHG TotalChanges: %d", PCHG.TotalChanges);
#endif

        if( PCHG.Compression != PCHG_COMP_NONE ) {
            PCHGCompHeader  CompHdr;
            unsigned char *compdata, *comptree;
            long treesize, compsize;

            CompHdr.CompInfoSize     =
                get_big_long(ifP, iffid, &remainingChunksize);
            CompHdr.OriginalDataSize =
                get_big_long(ifP, iffid, &remainingChunksize);

            treesize = CompHdr.CompInfoSize;
            MALLOCARRAY_NOFAIL(comptree, treesize);
            read_bytes(ifP, treesize, comptree, iffid, &remainingChunksize);

            compsize = remainingChunksize;
            MALLOCARRAY_NOFAIL(compdata, compsize);
            read_bytes(ifP, compsize, compdata, iffid, &remainingChunksize);

            datasize = CompHdr.OriginalDataSize;
            MALLOCARRAY_NOFAIL(data, datasize);
            PCHG_Decompress(&PCHG, &CompHdr, compdata, 
                            compsize, comptree, data);

            free(comptree);
            free(compdata);
        } else {
#ifdef DEBUG
            pm_message("uncompressed PCHG");
#endif
            datasize = remainingChunksize;
            MALLOCARRAY_NOFAIL(data, datasize);
            read_bytes(ifP, datasize, data, iffid, &remainingChunksize);
        }

        if( PCHG.Flags & PCHGF_USE_ALPHA )
            pm_message("warning - ignoring PCHG alpha channel because "
                       "this program doesn't know what to do with it");

        cmap->mp_rows = PCHG.StartLine + PCHG.LineCount;
        MALLOCARRAY_NOFAIL(cmap->mp_change, cmap->mp_rows);
        for( i = 0; i < cmap->mp_rows; i++ )
            cmap->mp_change[i] = NULL;
        if( PCHG.StartLine < 0 ) {
            int nch;
            if(PCHG.MaxReg < PCHG.MinReg)
                pm_error("assert: MinReg > MaxReg");
            overflow_add(PCHG.MaxReg-PCHG.MinReg, 2);
            nch = PCHG.MaxReg - PCHG.MinReg +1;
            MALLOCARRAY_NOFAIL(cmap->mp_init, nch + 1);
            for( i = 0; i < nch; i++ )
                cmap->mp_init[i].reg = MP_REG_IGNORE;
            cmap->mp_init[nch].reg = MP_REG_END;
        }

        if( PCHG.Flags & PCHGF_12BIT ) {
#ifdef DEBUG
            pm_message("SmallLineChanges");
#endif
            PCHG_ConvertSmall(&PCHG, cmap, data, datasize);
        }
        else {
            if( PCHG.Flags & PCHGF_32BIT ) {
#ifdef DEBUG
                pm_message("BigLineChanges");
#endif
                PCHG_ConvertBig(&PCHG, cmap, data, datasize);
            }
            else
                pm_error("unknown palette changes structure "
                         "format in %s chunk", 
                         ID2string(iffid));
        }
        free(data);
        chunk_end(ifP, iffid, remainingChunksize);
    }
}



static bool
ignored_iffid(IFF_ID       const iffid,
              IFF_ID       const ignorelist[],
              unsigned int const ignorecount) {

    bool ignore;

    unsigned int i;
    ignore = FALSE;  /* initial assumption */
    for( i = 0; i < ignorecount && !ignore; i++ ) {
        if( iffid == ignorelist[i] )
            ignore = TRUE;
    }
    return ignore;
}



static void 
process_body( FILE *          const ifP,
              long            const chunksize,
              BitMapHeader *  const bmhdP,
              ColorMap *      const cmap,
              FILE *          const maskfile,
              int             const fakeviewport,
              int             const isdeepopt,
              DirectColor *   const dcol,
              int *           const viewportmodesP) {
    
    if (bmhdP == NULL)
        pm_error("%s chunk without %s chunk", 
                 ID2string(ID_BODY), ID2string(ID_BMHD));

    prepareCmap(bmhdP, cmap);

    pixelrow = ppm_allocrow(bmhdP->w);
    if (maskfile) {
        maskrow = pbm_allocrow(bmhdP->w);
        pbm_writepbminit(maskfile, bmhdP->w, bmhdP->h, 0);
    }

    if (typeid == ID_ILBM) {
        int isdeep;

        overflow_add(bmhdP->w, 15);
        MALLOCARRAY_NOFAIL(ilbmrow, RowBytes(bmhdP->w));
        *viewportmodesP |= fakeviewport;      /* -isham/-isehb */

        if( isdeepopt > 0 && (bmhdP->nPlanes % 3 != 0) ) {
            pm_message("cannot interpret %d-plane image as 'deep' "
                       "(# of planes must be divisible by 3)", 
                       bmhdP->nPlanes);
            isdeep = 0;
        } else
            isdeep = isdeepopt;
        
        if (isdeep > 0)
            deep_to_ppm(ifP, chunksize, bmhdP, cmap);
        else if (dcol)
            dcol_to_ppm(ifP, chunksize, bmhdP, cmap, dcol);
        else if (bmhdP->nPlanes > 8) {
            if (bmhdP->nPlanes <= 16 && HAS_COLORMAP(cmap))
                std_to_ppm(ifP, chunksize, bmhdP, cmap, *viewportmodesP);
            else if (isdeep >= 0 && (bmhdP->nPlanes % 3 == 0))
                deep_to_ppm(ifP, chunksize, bmhdP, cmap);
            else if (bmhdP->nPlanes <= 16)
                /* will be interpreted as grayscale */
                std_to_ppm(ifP, chunksize, bmhdP, cmap, *viewportmodesP);
            else
                pm_error("don't know how to interpret %d-plane image", 
                         bmhdP->nPlanes);
        } else
            std_to_ppm(ifP, chunksize, bmhdP, cmap, *viewportmodesP);
    } else if( typeid == ID_PBM )
        ipbm_to_ppm(ifP, chunksize, bmhdP, cmap, *viewportmodesP);
    else   /* RGBN or RGB8 */
        rgbn_to_ppm(ifP, chunksize, bmhdP, cmap);
}



static void 
processChunk(FILE *          const ifP,
             long            const formsize,
             IFF_ID          const ignorelist[],
             unsigned int    const ignorecount,
             int             const fakeviewport,
             int             const viewportmask,
             int             const isdeepopt,
             bool            const cmaponly,
             bool *          const bodyChunkProcessedP,
             bool *          const endchunkP,
             BitMapHeader ** const bmhdP,
             ColorMap *      const cmap,
             DirectColor **  const dcolP,
             int *           const viewportmodesP,
             long *          const bytesReadP
    ) {

    IFF_ID iffid;
    long chunksize;
    long bytesread;

    bytesread = 0;

    iffid = get_big_long(ifP, ID_FORM, NULL);
    chunksize = get_big_long(ifP, iffid, NULL);
    bytesread += 8;

    if (debug)
        pm_message("reading %s chunk: %ld bytes", ID2string(iffid), chunksize);

    if (ignored_iffid(iffid, ignorelist, ignorecount)) {
        pm_message("ignoring %s chunk", ID2string(iffid));
        skip_chunk(ifP, iffid, chunksize);
    } else if (iffid == ID_END) {
        /* END chunks are not officially valid in IFF, but
           suggested as a future expansion for stream-writing,
           see Amiga RKM Devices, 3rd Ed, page 376 
        */
        if (chunksize != 0 ) {
            pm_message("warning - non-0 %s chunk", ID2string(iffid));
            skip_chunk(ifP, iffid, chunksize);
        }
        if (formsize != 0xffffffff)
            pm_message("warning - %s chunk with FORM size 0x%08lx "
                       "(should be 0x%08x)",
                       ID2string(iffid), formsize, 0xffffffff);
        *endchunkP = 1;
    } else if (*bodyChunkProcessedP) {
        pm_message("%s chunk found after %s chunk - skipping", 
                   ID2string(iffid), ID2string(ID_BODY));
        skip_chunk(ifP, iffid, chunksize);
    } else
        switch (iffid) {
        case ID_BMHD:
            *bmhdP = read_bmhd(ifP, iffid, chunksize);
            break;
        case ID_CMAP:
            read_cmap(ifP, iffid, chunksize, cmap);
            break;
        case ID_CMYK:
            read_cmyk(ifP, iffid, chunksize, cmap);
            break;
        case ID_CLUT:
            read_clut(ifP, iffid, chunksize, cmap);
            break;
        case ID_CAMG:
            if (chunksize != CAMGChunkSize)
                pm_error("%s chunk size mismatch", ID2string(iffid));
            *viewportmodesP = get_big_long(ifP, ID_CAMG, NULL);
            *viewportmodesP &= viewportmask;      /* -isnotham/-isnotehb */
            break;
        case ID_PCHG:
            read_pchg(ifP, iffid, chunksize, cmap);
            break;
        case ID_CTBL:
        case ID_SHAM:
            read_4bit_mp(ifP, iffid, chunksize, cmap);
            break;
        case ID_DCOL:
            if (chunksize != DirectColorSize)
                pm_error("%s chunk size mismatch", ID2string(iffid));
            MALLOCVAR_NOFAIL(*dcolP);
            (*dcolP)->r = get_byte(ifP, iffid, NULL);
            (*dcolP)->g = get_byte(ifP, iffid, NULL);
            (*dcolP)->b = get_byte(ifP, iffid, NULL);
            get_byte(ifP, iffid, NULL);       /* skip pad byte */
            break;
        case ID_BODY: 
            if (cmaponly || (*bmhdP && (*bmhdP)->nPlanes == 0))
                skip_chunk(ifP, ID_BODY,  chunksize);
            else {
                process_body(ifP, chunksize, *bmhdP, cmap, 
                             maskfile, fakeviewport, isdeepopt, *dcolP,
                             viewportmodesP);

                *bodyChunkProcessedP = TRUE;
            } break;
        case ID_GRAB:   case ID_DEST:   case ID_SPRT:   case ID_CRNG:
        case ID_CCRT:   case ID_DYCP:   case ID_DPPV:   case ID_DRNG:
        case ID_EPSF:   case ID_JUNK:   case ID_CNAM:   case ID_PRVW:
        case ID_TINY:   case ID_DPPS:
            skip_chunk(ifP, iffid, chunksize);
            break;
        case ID_copy:   case ID_AUTH:   case ID_NAME:   case ID_ANNO:
        case ID_TEXT:   case ID_FVER:
            if (verbose)
                display_chunk(ifP, iffid, chunksize);
            else
                skip_chunk(ifP, iffid, chunksize);
            break;
        case ID_DPI: {
            int x, y;

            x = get_big_short(ifP, ID_DPI, NULL);
            y = get_big_short(ifP, ID_DPI, NULL);
            if (verbose)
                pm_message("%s chunk:  dpi_x = %d    dpi_y = %d", 
                           ID2string(ID_DPI), x, y);
        } break;
        default:
            pm_message("unknown chunk type %s - skipping", 
                       ID2string(iffid));
            skip_chunk(ifP, iffid, chunksize);
            break;
        }

    bytesread += chunksize;

    if (ODD(chunksize)) {
        get_byte(ifP, iffid, NULL);
        bytesread += 1;
    } 
    *bytesReadP = bytesread;
}



static void
maybeWriteColorMap(FILE *               const ofP,
                   const BitMapHeader * const bmhdP,
                   ColorMap *           const cmapP,
                   bool                 const bodyChunkProcessed,
                   bool                 const cmaponly) {
/*----------------------------------------------------------------------------
   Write to file *ofP the color map *cmapP as a PPM, if appropriate.

   The logic (not just here -- in the program as a whole) for deciding whether
   to write the image or the colormap is quite twisted.  If I thought anyone
   was actually using this program, I would take the time to straighten it
   out.

   What's really sick about this subroutine is that in choosing to write
   a color map, it has to know that Caller hasn't already written
   the image.  Huge modularity violation.
-----------------------------------------------------------------------------*/
    if (cmaponly) {
        if (HAS_COLORMAP(cmapP)) {
            prepareCmap(bmhdP, cmapP);
            cmapToPpm(ofP, cmapP);
        } else
            pm_error("You specified -cmaponly, but the ILBM "
                     "has no colormap");
    } else if (bmhdP && bmhdP->nPlanes == 0) {
        if (HAS_COLORMAP(cmapP)) {
            prepareCmap(bmhdP, cmapP);
            cmapToPpm(ofP, cmapP);
        } else
            pm_error("ILBM has neither a color map nor color planes");
    } else if (!bodyChunkProcessed) {
        if (HAS_COLORMAP(cmapP)) {
            pm_message("input is a colormap file");
            prepareCmap(bmhdP, cmapP);
            cmapToPpm(ofP, cmapP);
        } else
            pm_error("ILBM has neither %s or %s chunk", 
                     ID2string(ID_BODY), ID2string(ID_CMAP));
    }
}



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

    FILE * ifP;
    int argn;
    short cmaponly = 0, isdeepopt = 0;
    bool endchunk;
    bool bodyChunkProcessed;
    long formsize, bytesread;
    int viewportmodes = 0, fakeviewport = 0, viewportmask;
    IFF_ID firstIffid;
    BitMapHeader *bmhdP = NULL;
    ColorMap * cmap;
    DirectColor *dcol = NULL;
#define MAX_IGNORE  16
    IFF_ID ignorelist[MAX_IGNORE];
    int ignorecount = 0;
    char *maskname;
    const char * const usage =
        "[-verbose] [-ignore <chunkID> [-ignore <chunkID>] ...] "
        "[-isham|-isehb|-isdeep|-isnotham|-isnotehb|-isnotdeep] "
        "[-cmaponly] [-adjustcolors] "
        "[-transparent <color>] [-maskfile <filename>] [ilbmfile]";
    ppm_init(&argc, argv);

    viewportmask = 0xffffffff;

    argn = 1;
    while( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' ) {
        if( pm_keymatch(argv[argn], "-verbose", 2) )
            verbose = 1;
        else if( pm_keymatch(argv[argn], "-noverbose", 4) )
            verbose = 0;
        else if( pm_keymatch(argv[argn], "-isham", 4) )
            fakeviewport |= vmHAM;
        else if( pm_keymatch(argv[argn], "-isehb", 4) )
            fakeviewport |= vmEXTRA_HALFBRITE;
        else if( pm_keymatch(argv[argn], "-isdeep", 4) )
            isdeepopt = 1;
        else if( pm_keymatch(argv[argn], "-isnotham", 7) )
            viewportmask &= ~(vmHAM);
        else if( pm_keymatch(argv[argn], "-isnotehb", 7) )
            viewportmask &= ~(vmEXTRA_HALFBRITE);
        else if( pm_keymatch(argv[argn], "-isnotdeep", 7) )
            isdeepopt = -1;
        else if( pm_keymatch(argv[argn], "-cmaponly", 2) )
            cmaponly = 1;
        else if( pm_keymatch(argv[argn], "-adjustcolors", 2) )
            adjustcolors = 1;
        else if( pm_keymatch(argv[argn], "-noadjustcolors", 4) )
            adjustcolors = 0;
        else  if( pm_keymatch(argv[argn], "-transparent", 2) ) {
            if( ++argn >= argc )
                pm_usage(usage);
            transpName = argv[argn];
        } else if( pm_keymatch(argv[argn], "-maskfile", 2) ) {
            if( ++argn >= argc )
                pm_usage(usage);
            maskname = argv[argn];
            maskfile = pm_openw(maskname);
        } else if( pm_keymatch(argv[argn], "-ignore", 2) ) {
            if( ++argn >= argc )
                pm_usage(usage);
            if( strlen(argv[argn]) != 4 )
                pm_error("'-ignore' option needs a 4 byte chunk ID string "
                         "as argument");
            if( ignorecount >= MAX_IGNORE )
                pm_error("max %d chunk IDs to ignore", MAX_IGNORE);
            ignorelist[ignorecount++] = 
                MAKE_ID(argv[argn][0], argv[argn][1], argv[argn][2], 
                        argv[argn][3]);
        } else
            pm_usage(usage);
        ++argn;
    }    

    if( argn < argc ) {
        ifP = pm_openr( argv[argn] );
        argn++;
    } else
        ifP = stdin;

    if( argn != argc )
        pm_usage(usage);

    wrotemask = false;  /* initial value */

    /* Read in the ILBM file. */

    firstIffid = get_big_long(ifP, ID_FORM, NULL);
    if (firstIffid != ID_FORM)
        pm_error("input is not a FORM type IFF file");
    formsize = get_big_long(ifP, ID_FORM, NULL);
    typeid = get_big_long(ifP, ID_FORM, NULL);
    if (typeid != ID_ILBM && typeid != ID_RGBN && typeid != ID_RGB8 && 
        typeid != ID_PBM)
        pm_error("input is not an ILBM, RGBN, RGB8 or PBM "
                 "type FORM IFF file");
    bytesread = 4;  /* FORM and formsize do not count */

    cmap = alloc_cmap();

    /* Main loop, parsing the IFF FORM. */
    bodyChunkProcessed = FALSE;
    endchunk = FALSE;
    while (!endchunk && formsize-bytesread >= 8) {
        long bytesReadForChunk;

        processChunk(ifP, formsize, ignorelist, ignorecount,
                     fakeviewport, viewportmask,
                     isdeepopt, cmaponly,
                     &bodyChunkProcessed,
                     &endchunk, &bmhdP, cmap, &dcol,
                     &viewportmodes, &bytesReadForChunk);

        bytesread += bytesReadForChunk;
    }

    if (maskfile) {
        pm_close(maskfile);
        if (!wrotemask)
            remove(maskname);
        pbm_freerow(maskrow);
    }

    maybeWriteColorMap(stdout, bmhdP, cmap, bodyChunkProcessed, cmaponly);

    {
        unsigned int skipped;
        
        for (skipped = 0; fgetc(ifP) != EOF; ++skipped)
            ++bytesread;

        if (skipped > 0)
            pm_message("skipped %u extraneous byte%s after last chunk",
                       skipped, (skipped == 1 ? "" : "s"));
    }
    pm_close(ifP);

    if (!endchunk && bytesread != formsize) {
        pm_message("warning - file length/FORM size field mismatch "
                   "(%ld != %ld+8)",
                   bytesread+8 /* FORM+size */, formsize);
    }

    return 0;
}
