/*=============================================================================
                              pamtogif
===============================================================================
  Convert a Netpbm image to GIF

  History and copyright information is at the end of the file.
=============================================================================*/

#include <assert.h>
#include <string.h>

#include "pm_c_util.h"
#include "mallocvar.h"
#include "shhopt.h"
#include "nstring.h"
#include "pam.h"
#include "pammap.h"

#define MAXCMAPSIZE 256

static unsigned int const gifMaxval = 255;

static bool verbose;


typedef int stringCode;
    /* A code to be place in the GIF raster.  It represents
       a string of one or more pixels.  You interpret this in the context
       of a current code size.  The lower half of the values representable
       in the current code size represent singleton strings and the value
       is simply the value of the one pixel in the string.  The first two
       values in the upper half of the range are the clear code and EOF
       code, respectively.  The rest of the values represent multi-pixel
       strings.  The mapping between value and the sequence of pixels
       changes throughout the image.

       A variable of this type sometimes has the value -1 instead of
       a string code because of cheesy programming.

       Ergo, this data structure must be signed and at least BITS bits
       wide plus sign bit.
    */


struct cmap {
    /* This is the information for the GIF colormap (aka palette). */

    struct pam pam;
        /* Gives depth and maxval for colors in color[] */
    tuple color[MAXCMAPSIZE];
        /* Maps a color index, as is found in the raster part of the
           GIF, to color.
        */
    unsigned int cmapSize;
        /* Number of entries in the GIF colormap.  I.e. number of colors
           in the image, plus possibly one fake transparency color.
        */
    bool haveTransparent;
        /* The colormap contains an entry for transparent pixels */
    unsigned int transparent;
        /* color index number in GIF palette of the color that is to
           be transparent.

           Meaningful only if 'haveTransparent' is true.
        */
    tuplehash tuplehash;
        /* A hash table to translate color to GIF colormap index. */
};

struct cmdlineInfo {
    /* All the information the user supplied in the command line,
       in a form easy for the program to use.
    */
    const char *input_filespec; /* Filespec of input file */
    const char *alphacolor;     /* -alphacolor option value or default */
    unsigned int interlace;     /* -interlace option value */
    unsigned int sort;          /* -sort option value */
    const char *mapfile;        /* -mapfile option value.  NULL if none. */
    const char *transparent;    /* -transparent option value.  NULL if none. */
    const char *comment;        /* -comment option value; NULL if none */
    unsigned int nolzw;         /* -nolzw option */
    float aspect;               /* -aspect option value (the ratio).  */
    unsigned int verbose;
};





static unsigned int
pamAlphaPlane(struct pam * const pamP) {

    unsigned int alphaPlane;

    if (streq(pamP->tuple_type, "RGB_ALPHA"))
        alphaPlane = 3;
    else if (streq(pamP->tuple_type, "GRAYSCALE_ALPHA"))
        alphaPlane = 2;
    else if (streq(pamP->tuple_type, "BLACKANDWHITE_ALPHA"))
        alphaPlane = 2;
    else
        alphaPlane = 0;
    
    if (alphaPlane >= pamP->depth)
        pm_error("Tuple type is '%s', but depth (%u) is less than %u",
                 pamP->tuple_type, pamP->depth, alphaPlane + 1);

    return alphaPlane;
}



static void
parseCommandLine(int argc, char ** argv,
                 struct cmdlineInfo * const cmdlineP) {
/*----------------------------------------------------------------------------
   Parse the program arguments (given by argc and argv) into a form
   the program can deal with more easily -- a cmdline_info structure.
   If the syntax is invalid, issue a message and exit the program via
   pm_error().

   Note that the file spec array we return is stored in the storage that
   was passed to us as the argv array.
-----------------------------------------------------------------------------*/
    optEntry * option_def;  /* malloc'ed */
    optStruct3 opt;  /* set by OPTENT3 */
    unsigned int option_def_index;

    unsigned int aspectSpec;

    MALLOCARRAY_NOFAIL(option_def, 100);

    option_def_index = 0;   /* incremented by OPTENT3 */
    OPTENT3(0,   "interlace",   OPT_FLAG,   
            NULL,                       &cmdlineP->interlace, 0);
    OPTENT3(0,   "sort",        OPT_FLAG,   
            NULL,                       &cmdlineP->sort, 0);
    OPTENT3(0,   "nolzw",       OPT_FLAG,   
            NULL,                       &cmdlineP->nolzw, 0);
    OPTENT3(0,   "mapfile",     OPT_STRING, 
            &cmdlineP->mapfile,        NULL, 0);
    OPTENT3(0,   "transparent", OPT_STRING, 
            &cmdlineP->transparent,    NULL, 0);
    OPTENT3(0,   "comment",     OPT_STRING, 
            &cmdlineP->comment,        NULL, 0);
    OPTENT3(0,   "alphacolor",  OPT_STRING, 
            &cmdlineP->alphacolor,     NULL, 0);
    OPTENT3(0,   "aspect",      OPT_FLOAT, 
            &cmdlineP->aspect,         &aspectSpec, 0);
    OPTENT3(0,   "verbose",     OPT_FLAG, 
            NULL,                      &cmdlineP->verbose, 0);
    
    /* Set the defaults */
    cmdlineP->mapfile = NULL;
    cmdlineP->transparent = NULL;  /* no transparency */
    cmdlineP->comment = NULL;      /* no comment */
    cmdlineP->alphacolor = "rgb:0/0/0";      
        /* We could say "black" here, but then we depend on the color names
           database existing.
        */

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

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

    if (argc-1 == 0) 
        cmdlineP->input_filespec = "-";
    else if (argc-1 != 1)
        pm_error("Program takes zero or one argument (filename).  You "
                 "specified %d", argc-1);
    else
        cmdlineP->input_filespec = argv[1];
        
    if (aspectSpec) { 
        if (cmdlineP->aspect < 0.25  || cmdlineP->aspect > 4.21875)
            pm_error("Invalid -aspect value: %f.  "
                     "GIF allows only the range 0.25-4.0 .",
                     cmdlineP->aspect);
        else if (cmdlineP->aspect > 4.0)
            pm_message("Warning: "
                       "You specified an aspect ratio over 4.0: %f.  "
                       "This will result in an invalid GIF.",
                       cmdlineP->aspect);
    } else
        cmdlineP->aspect = 1.0;
}



/*
 * Write out a word to the GIF file
 */
static void
Putword(int const w, FILE * const fp) {

    fputc( w & 0xff, fp );
    fputc( (w / 256) & 0xff, fp );
}



static int
closestColor(tuple         const color,
             struct pam *  const pamP,
             struct cmap * const cmapP) {
/*----------------------------------------------------------------------------
   Return the colormap index of the color in the colormap *cmapP
   that is closest to the color 'color', whose format is specified by 
   *pamP.

   Also add 'color' to the colormap hash, with the colormap index we
   are returning.  Caller must ensure that the color is not already in
   there.
-----------------------------------------------------------------------------*/
    unsigned int const nComp = pamP->depth >= 3 ? 3 : 1;
        /* Number of color components (not alpha) in 'color' */
    
    unsigned int i;
    unsigned int imin, dmin;
    int fits;

    dmin = UINT_MAX;
    imin = 0;
    for (i = 0; i < cmapP->cmapSize; ++i) {
        unsigned int distance;
        unsigned int plane;

        for (distance = 0, plane = 0; plane < nComp; ++plane)
            /* Divide by 4 is to avoid arithmetic overflow */
            distance += SQR(color[plane] - cmapP->color[i][plane]) / 4;

        if (distance < dmin) {
            dmin = distance;
            imin = i; 
        } 
    }
    pnm_addtotuplehash(pamP, cmapP->tuplehash, color, imin, &fits);

    return imin;
}



enum pass {MULT8PLUS0, MULT8PLUS4, MULT4PLUS2, MULT2PLUS1};


typedef struct {
    struct pam pam;
        /* Description of input file/image.  The position of the file
           is also part of the state of this rowReader.
        */
    pm_filepos rasterPos;
        /* Position in file fileP of the start of the raster */
    bool interlace;
        /* We're accessing the image in interlace fashion */
    bool eof;
        /* The image is at EOF (we have returned all of the rows) */
    unsigned int nextRow;
        /* Number of row to which input file is positioned;
           meaningless if 'eof'.
        */
    enum pass pass;
        /* The interlace pass.  Undefined if !interlace */
    tuple * discardBuffer;
        /* A bitbucket for rows we read in order to advance the file
           position.
        */
} rowReader;



static rowReader *
rowReader_create(struct pam * const pamP,
                 pm_filepos   const rasterPos,
                 bool         const interlace) {

    rowReader * rdrP;

    MALLOCVAR_NOFAIL(rdrP);

    rdrP->pam         = *pamP;
    rdrP->rasterPos   = rasterPos;
    rdrP->interlace   = interlace;
    rdrP->eof         = FALSE;
    rdrP->pass        = MULT8PLUS0;

    pm_seek2(rdrP->pam.file, &rasterPos, sizeof(rasterPos));
    rdrP->nextRow = 0;

    rdrP->discardBuffer = pnm_allocpamrow(&rdrP->pam);

    return rdrP;
}



static void
rowReader_destroy(rowReader * const rdrP) {

    pnm_freepamrow(rdrP->discardBuffer);
    free(rdrP);
}



static void
rowReaderSkipRows(rowReader *  const rdrP,
                  unsigned int const rowCount,
                  bool *       const eofP) {
/*----------------------------------------------------------------------------
   Skip over the next 'rowCount' rows of the input file.

   Iff there aren't at least 'rowCount' rows left, return *eofP == TRUE.
-----------------------------------------------------------------------------*/
    if (rdrP->nextRow + rowCount >= rdrP->pam.height)
        *eofP = TRUE;
    else {
        /* This could be made faster if need be by adding a libnetpbm
           row skip function.  Except with the plain formats, that could
           just compute the next row position and fseek() to it.
           pnm_readpamrow() with NULL for the output pointer would be a
           good interface for a row skip function.
        */
        unsigned int i;

        *eofP = FALSE;

        for (i = 0; i < rowCount; ++i)
            pnm_readpamrow(&rdrP->pam, rdrP->discardBuffer);

        rdrP->nextRow += rowCount;
    }
}



static void
rowReaderGotoNextInterlaceRow(rowReader * const rdrP) {
/*----------------------------------------------------------------------------
  Position reader to the next row in the interlace pattern, assuming it
  is now positioned immediately after the current row.
-----------------------------------------------------------------------------*/
    bool endOfPass;

    /* There are 4 passes:
       MULT8PLUS0: Rows 0, 8, 16, 24, 32, etc.
       MULT8PLUS4: Rows 4, 12, 20, 28, etc.
       MULT4PLUS2: Rows 2, 6, 10, 14, etc.
       MULT2PLUS1: Rows 1, 3, 5, 7, 9, etc.
    */
    
    switch (rdrP->pass) {
    case MULT8PLUS0:
        rowReaderSkipRows(rdrP, 7, &endOfPass);
        break;
    case MULT8PLUS4:
        rowReaderSkipRows(rdrP, 7, &endOfPass);
        break;
    case MULT4PLUS2:
        rowReaderSkipRows(rdrP, 3, &endOfPass);
        break;
    case MULT2PLUS1:
        rowReaderSkipRows(rdrP, 1, &endOfPass);
        break;
    }

    /* Note that if there are more than 4 rows, the sequence of passes
       is sequential, but when there are fewer than 4, reading may skip
       e.g. from MULT8PLUS0 to MULT4PLUS2.
    */
    while (endOfPass && !rdrP->eof) {
        pm_seek2(rdrP->pam.file, &rdrP->rasterPos, sizeof(rdrP->rasterPos));
        rdrP->nextRow = 0;

        switch (rdrP->pass) {
        case MULT8PLUS0:
            rdrP->pass = MULT8PLUS4;
            rowReaderSkipRows(rdrP, 4, &endOfPass);
            break;
        case MULT8PLUS4:
            rdrP->pass = MULT4PLUS2;
            rowReaderSkipRows(rdrP, 2, &endOfPass);
            break;
        case MULT4PLUS2:
            rdrP->pass = MULT2PLUS1;
            rowReaderSkipRows(rdrP, 1, &endOfPass);
            break;
        case MULT2PLUS1:
            rdrP->eof = TRUE;
            break;
        }
    }
}




static void
rowReaderGotoNextStraightRow(rowReader * const rdrP) {
/*----------------------------------------------------------------------------
  Position reader to the next row in a straight, non-interlace
  pattern, assuming the file is now positioned immediately after the
  current row.

  This is trivial, since the next row _is_ immediately after the current
  row, except in the case that there are no more rows.
-----------------------------------------------------------------------------*/
    if (rdrP->nextRow >= rdrP->pam.height)
        rdrP->eof = TRUE;
}



static void
rowReader_read(rowReader * const rdrP,
               tuple *     const tuplerow) {

    if (rdrP->eof)
        pm_error("INTERNAL ERROR: rowReader attempted to read beyond end "
                 "of image");

    pnm_readpamrow(&rdrP->pam, tuplerow);
    ++rdrP->nextRow;
    
    if (rdrP->interlace)
        rowReaderGotoNextInterlaceRow(rdrP);
    else
        rowReaderGotoNextStraightRow(rdrP);
}



static unsigned int
gifPixel(struct pam *   const pamP,
         tuple          const tuple,
         unsigned int   const alphaPlane,
         sample         const alphaThreshold, 
         struct cmap *  const cmapP) {
/*----------------------------------------------------------------------------
   Return as *colorIndexP the colormap index of the tuple 'tuple',
   whose format is described by *pamP, using colormap *cmapP.

   'alphaThreshold' is the alpha level below which we consider a
   pixel transparent for GIF purposes.
-----------------------------------------------------------------------------*/
    int colorIndex;

    if (alphaPlane && tuple[alphaPlane] < alphaThreshold &&
        cmapP->haveTransparent)
        colorIndex = cmapP->transparent;
    else {
        int found;

        pnm_lookuptuple(pamP, cmapP->tuplehash, tuple,
                        &found, &colorIndex);
        
        if (!found)
            colorIndex = closestColor(tuple, pamP, cmapP);
    }
    assert(colorIndex >= 0);
    return (unsigned int) colorIndex;
}



static void
writeTransparentColorIndexExtension(FILE *       const ofP,
                                    unsigned int const transColorIndex) {
/*----------------------------------------------------------------------------
   Write out extension for transparent color index.
-----------------------------------------------------------------------------*/
    fputc('!',  ofP);
    fputc(0xf9, ofP);
    fputc(4,    ofP);
    fputc(1,    ofP);
    fputc(0,    ofP);
    fputc(0,    ofP);
    fputc(transColorIndex, ofP);
    fputc(0,    ofP);
}



static void
writeCommentExtension(FILE * const ofP,
                      char   const comment[]) {
/*----------------------------------------------------------------------------
   Write out extension for a comment
-----------------------------------------------------------------------------*/
    unsigned int const maxSegmentSize = 255;

    const char * segment;
    
    fputc('!',  ofP);   /* Identifies an extension */
    fputc(0xfe, ofP);   /* Identifies a comment */

    /* Write it out in segments no longer than 255 characters */
    for (segment = &comment[0]; 
         segment < comment + strlen(comment); 
         segment += maxSegmentSize) {

        unsigned int const lengthThisSegment =
            MIN(maxSegmentSize, strlen(segment));

        fputc(lengthThisSegment, ofP);

        fwrite(segment, 1, lengthThisSegment, ofP);
    }

    fputc(0, ofP);   /* No more comment blocks in this extension */
}



/***************************************************************************
 *
 *  GIFCOMPR.C       - GIF Image compression routines
 *
 *  Lempel-Ziv compression based on 'compress'.  GIF modifications by
 *  David Rowley (mgardi@watdcsu.waterloo.edu)
 *
 ***************************************************************************/

/*
 * General DEFINEs
 */

#define BITS    12

/*
 *
 * GIF Image compression - modified 'compress'
 *
 * Based on: compress.c - File compression ala IEEE Computer, June 1984.
 *
 * By Authors:  Spencer W. Thomas       (decvax!harpo!utah-cs!utah-gr!thomas)
 *              Jim McKie               (decvax!mcvax!jim)
 *              Steve Davies            (decvax!vax135!petsd!peora!srd)
 *              Ken Turkowski           (decvax!decwrl!turtlevax!ken)
 *              James A. Woods          (decvax!ihnp4!ames!jaw)
 *              Joe Orost               (decvax!vax135!petsd!joe)
 *
 */


static stringCode const maxCodeLimitLzw = (stringCode)1 << BITS;
       /* One beyond the largest string code that can exist in GIF */ 
       /* Used only in assertions  */


struct hashTableEntry {
    stringCode fcode;   /* -1 means unused slot */
    unsigned int ent;
};    



/***************************************************************************
*                          BYTE OUTPUTTER                 
***************************************************************************/

typedef struct {
    FILE * fileP;  /* The file to which to output */
    unsigned int count;
        /* Number of bytes so far in the current data block */
    unsigned char buffer[256];
        /* The current data block, under construction */
} byteBuffer;



static byteBuffer *
byteBuffer_create(FILE * const fileP) {

    byteBuffer * byteBufferP;

    MALLOCVAR_NOFAIL(byteBufferP);

    byteBufferP->fileP = fileP;
    byteBufferP->count = 0;

    return byteBufferP;
}



static void
byteBuffer_destroy(byteBuffer * const byteBufferP) {

    free(byteBufferP);
}



static void
byteBuffer_flush(byteBuffer * const byteBufferP) {
/*----------------------------------------------------------------------------
   Write the current data block to the output file, then reset the current 
   data block to empty.
-----------------------------------------------------------------------------*/
    if (byteBufferP->count > 0 ) {
        if (verbose)
            pm_message("Writing %u byte block", byteBufferP->count);
        fputc(byteBufferP->count, byteBufferP->fileP);
        fwrite(byteBufferP->buffer, 1, byteBufferP->count, byteBufferP->fileP);
        byteBufferP->count = 0;
    }
}



static void
byteBuffer_flushFile(byteBuffer * const byteBufferP) {
    
    fflush(byteBufferP->fileP);
    
    if (ferror(byteBufferP->fileP))
        pm_error("error writing output file");
}



static void
byteBuffer_out(byteBuffer *  const byteBufferP,
               unsigned char const c) {
/*----------------------------------------------------------------------------
  Add a byte to the end of the current data block, and if it is now 255
  characters, flush the data block to the output file.
-----------------------------------------------------------------------------*/
    byteBufferP->buffer[byteBufferP->count++] = c;
    if (byteBufferP->count >= 255)
        byteBuffer_flush(byteBufferP);
}



/***************************************************************************
*                          GIF CODE OUTPUTTER                 
***************************************************************************/

typedef struct {
    byteBuffer * byteBufferP;
    unsigned int initBits;
    unsigned int nBits;
        /* Number of bits to put in output for each code */
    stringCode maxCode;                  /* maximum code, given n_bits */
    stringCode maxCodeLimit;
        /* LZW: One beyond the largest string code that can exist in GIF.
           Uncompressed: a ceiling to prevent code size from ratcheting up.
           In either case, output code never reaches this value.
        */  
    unsigned long curAccum;
    int curBits;
    unsigned int codeCount;
        /* Number of codes that have been output to this buffer (doesn't
           matter if they have gone out the other side yet or not) since
           the last flush (or ever, if no last flush).  The main use of this
           is debugging -- when something fails, you can see in a debugger
           where in the image it was, then set a trap for there.
        */
} codeBuffer;



static codeBuffer *
codeBuffer_create(FILE *       const ofP,
                  unsigned int const initBits,
                  bool         const lzw) {

    codeBuffer * codeBufferP;

    MALLOCVAR_NOFAIL(codeBufferP);

    codeBufferP->initBits    = initBits;
    codeBufferP->nBits       = codeBufferP->initBits;
    codeBufferP->maxCode     = (1 << codeBufferP->nBits) - 1;
    codeBufferP->maxCodeLimit = lzw ?
        (stringCode)1 << BITS : (stringCode) (1 << codeBufferP->nBits) - 1; 
    codeBufferP->byteBufferP = byteBuffer_create(ofP);
    codeBufferP->curAccum    = 0;
    codeBufferP->curBits     = 0;
    codeBufferP->codeCount   = 0;

    return codeBufferP;
}



static void
codeBuffer_destroy(codeBuffer * const codeBufferP) {

    byteBuffer_destroy(codeBufferP->byteBufferP);

    free(codeBufferP);
}



static void
codeBuffer_resetCodeSize(codeBuffer * const codeBufferP) {

    codeBufferP->nBits = codeBufferP->initBits;

    assert(codeBufferP->nBits <= BITS);

    codeBufferP->maxCode = (1 << codeBufferP->nBits) - 1;
}



static void
codeBuffer_increaseCodeSize(codeBuffer * const codeBufferP) {

    ++codeBufferP->nBits;

    assert(codeBufferP->nBits <= BITS);

    codeBufferP->maxCode = (1 << codeBufferP->nBits) - 1;
}

static void
codeBuffer_output(codeBuffer * const codeBufferP,
                  stringCode   const code) {
/*----------------------------------------------------------------------------
   Output one GIF code to the file, through the code buffer.

   The code is represented as N bits in the file -- the lower
   N bits of 'code'.  N is a the current code size of *codeBufferP.
   
   Id 'code' is the maximum possible code for the current code size
   for *codeBufferP, increase that code size (unless it's already 
   maxed out).
-----------------------------------------------------------------------------*/
    assert (code <= codeBufferP->maxCode);

    codeBufferP->curAccum &= (1 << codeBufferP->curBits) - 1;

    if (codeBufferP->curBits > 0)
        codeBufferP->curAccum |= ((unsigned long)code << codeBufferP->curBits);
    else
        codeBufferP->curAccum = code;

    codeBufferP->curBits += codeBufferP->nBits;

    while (codeBufferP->curBits >= 8) {
        byteBuffer_out(codeBufferP->byteBufferP,
                       codeBufferP->curAccum & 0xff);
        codeBufferP->curAccum >>= 8;
        codeBufferP->curBits -= 8;
    }

    ++codeBufferP->codeCount;
}



static void
codeBuffer_flush(codeBuffer * const codeBufferP) {

    /* Output the possible partial byte in the buffer */

    if (codeBufferP->curBits > 0) {
        byteBuffer_out(codeBufferP->byteBufferP,
                       codeBufferP->curAccum & 0xff);
        codeBufferP->curBits = 0;
    }
    byteBuffer_flush(codeBufferP->byteBufferP);
    
    byteBuffer_flushFile(codeBufferP->byteBufferP);

    if (verbose)
        pm_message("%u strings of pixels written to file",
                   codeBufferP->codeCount);
    codeBufferP->codeCount = 0;
}



typedef struct {
    codeBuffer * codeBufferP;
        /* The place to which we write our string codes.

           Constant.
        */
    bool lzw;
        /* We're actually doing LZW compression.  False means we follow
           the algorithm enough tht an LZW decompressor will recover the
           proper data, but always using one code per pixel, and therefore
           not effecting any compression and not using the LZW patent.
        */
    unsigned int hsize;
        /* The number of slots in the hash table.  This variable to
           enhance overall performance by reducing memory use when
           encoding smaller gifs. 
         */
        
    unsigned int hshift;
        /* This is how many bits we shift left a string code in forming the
           primary hash of the concatenation of that string with another.
           Constant.
        */

    /* Codes less than 'clearCode' are singleton pixel codes - each
       represents the pixel value equal to it.

       Codes greater than 'eofCode' are multipixel string codes.  Each
       represents a string of pixels that is defined by the preceding
       stream.
    */
    stringCode clearCode;
        /* The code in an LZW stream that means to clear the string
           dictionary and start fresh.

           Constant.
        */
    stringCode eofCode;
        /* The code in an LZW stream that means there's no more coming

           Constant.
        */
    stringCode initCodeLimit;
        /* The value of 'codeLimit' at the start of a block.

           Constant.
        */

    stringCode codeLimit;
        /* One beyond the maximum code possible with the current code
           size.
        */

    struct hashTableEntry * hashTable;
    stringCode nextUnusedCode;
        /* Numerically next code available to assign to a a multi-pixel
           string.  Note that codes for multi-pixel strings are in the
           upper half of the range of codes, always greater than
           'clearCode'.
        */

    stringCode stringSoFar;
        /* The code for the string we have built so far.  This code indicates
           one or more pixels that we have encoded but not yet output
           because we're hoping to match an even longer string.

           Valid only when 'buildingString' is true.

           In the non-lzw case the single pixel to output.
        */
    bool buildingString;
        /* We are in the middle of building a string; 'stringSoFar' describes
           the pixels in it so far.  The only time this is false is at the
           very beginning of the stream.
 
           Ignored in the non-lzw case. 
        */
} lzwCompressor;




static unsigned int
nSignificantBits( unsigned int const arg ){

#if HAVE_GCC_BITCOUNT

    return (arg == 0) ? 0 : 8 * sizeof(unsigned int) - __builtin_clz(arg);

#else

    unsigned int i = 0;
    while (arg >> i != 0)
        ++i;

    return i;
#endif
}



static lzwCompressor *
lzw_create(FILE *       const ofP,
           unsigned int const initBits,
           bool         const lzw,
           unsigned int const pixelCount) {

    unsigned int const hsizeTable[] = {257, 521, 1031, 2053, 4099, 5003};
    /* If the image has 4096 or fewer pixels we use prime numbers slightly
       above powers of two between 8 and 12.  In this case the hash table
       never fills up; clear code is never emitted.
    
       Above that we use a table with 4096 slots plus 20% extra.
       When this is not enough the clear code is emitted.
       Because of the extra 20% the table itself never fills up.
       
       lzw.hsize and lzw.hshift stay constant through the image.

       Variable hsize is a performance enhancement based on the fact that
       the encoder never needs more codes than the number of pixels in
       the image.  Typically, the ratio of pixels to codes is around
       10:1 to 20:1.
   
       Logic works with fixed values lzw.hsize=5003 and t=13.
    */

    lzwCompressor * lzwP;
       
    MALLOCVAR_NOFAIL(lzwP);

    /* Constants */
    lzwP->lzw = lzw;

    lzwP->clearCode     = 1 << (initBits - 1);
    lzwP->eofCode       = lzwP->clearCode + 1;
    lzwP->initCodeLimit = 1 << initBits;

    if (lzw) {
        unsigned int const t =
            MIN(13, MAX(8, nSignificantBits(pixelCount +lzwP->eofCode - 2)));
            /* Index into hsizeTable */
    
        lzwP->hsize = hsizeTable[t-8];

        lzwP->hshift = (t == 13 ? 12 : t) - nSignificantBits(MAXCMAPSIZE-1);

        MALLOCARRAY(lzwP->hashTable, lzwP->hsize);
        
        if (lzwP->hashTable == NULL)
            pm_error("Couldn't get memory for %u-entry hash table.",
                     lzwP->hsize);
    } else {
        /* No LZW compression.  We don't need a stringcode hash table */  
        lzwP->hashTable = NULL;
        lzwP->hsize     = 0;
    }

    lzwP->buildingString = FALSE;

    lzwP->codeBufferP = codeBuffer_create(ofP, initBits, lzw);

    return lzwP;
}



static void
lzw_destroy(lzwCompressor * const lzwP) {

    codeBuffer_destroy(lzwP->codeBufferP);

    free(lzwP->hashTable);

    free(lzwP);
}



static void
lzwHashClear(lzwCompressor * const lzwP) {

    /* Empty the code table */

    unsigned int i;

    for (i = 0; i < lzwP->hsize; ++i)
        lzwP->hashTable[i].fcode = -1;

    lzwP->nextUnusedCode = lzwP->clearCode + 2;
}



static void
lzw_clearBlock(lzwCompressor * const lzwP) {
/*----------------------------------------------------------------------------
  
-----------------------------------------------------------------------------*/
    lzwHashClear(lzwP);

    codeBuffer_output(lzwP->codeBufferP, lzwP->clearCode);

    codeBuffer_resetCodeSize(lzwP->codeBufferP);

    lzwP->codeLimit = lzwP->initCodeLimit;
}



static void
lzwAdjustCodeSize(lzwCompressor * const lzwP,
                  stringCode      const newCode) {
/*----------------------------------------------------------------------------
   Assuming we just defined code 'newCode', increase the code size as
   required so that this code fits.

   The decompressor is mimicking our assignment of that code, so knows that
   we are making this adjustment, so expects codes of the new size.
-----------------------------------------------------------------------------*/
    assert(newCode <= lzwP->codeLimit);

    if (newCode == lzwP->codeLimit) {
        lzwP->codeLimit *= 2;
        codeBuffer_increaseCodeSize(lzwP->codeBufferP);

        assert(lzwP->codeLimit <= maxCodeLimitLzw);
    }
}



static void
lzwOutputCurrentString(lzwCompressor * const lzwP) {
/*----------------------------------------------------------------------------
   Put a code for the currently built-up string in the output stream.

   Doing this causes a new string code to be defined (code is
   lzwP->nextUnusedCode), so Caller must add that to the hash.  If
   that code's size is beyond the overall limit, we reset the hash
   (which means future codes will start back at the minimum size) and
   put a clear code in the stream to tell the decompressor to do the
   same.  So Caller must add it to the hash _before_ calling us.

   Note that in the non-compressing case, the overall limit is small
   enough to prevent us from ever defining string codes; we'll always
   reset the hash.

   There's an odd case that always screws up any attempt to make this
   code cleaner: At the end of the LZW stream, you have to output the
   code for the final string even though you don't have a following
   pixel that would make a longer string.  So there's nothing to add
   to the hash table and no point in allocating a new string code.
   But the decompressor doesn't know that we're done, so he allocates
   the next string code and may therefore increase his code length.
   If we don't do the same, we will write our one last code -- the EOF
   code -- in a code length smaller than what the decompressor is
   expecting, and he will have a premature end of stream.

   So this subroutine does run for that final code flush and does some
   of the motions of defining a new string code, but this subroutine
   can't update the hash because in that particular case, there's
   nothing to add.
-----------------------------------------------------------------------------*/
    codeBuffer_output(lzwP->codeBufferP, lzwP->stringSoFar);
    if (lzwP->nextUnusedCode < lzwP->codeBufferP->maxCodeLimit) {
        /* Allocate the code for the extended string, which Caller
           should have already put in the hash so he can use it in the
           future.  Decompressor knows when it sees the code output
           above to define a string on its end too, using the same
           string code we do.
        */
        stringCode const newCode = lzwP->nextUnusedCode++;

        /* This code may be too big to fit in the current code size, in
           which case we have to increase the code size (and decompressor
           will do the same).
        */
        lzwAdjustCodeSize(lzwP, newCode);
    } else {
        /* Forget all the strings so far; start building again; tell
           decompressor to do the same.
        */
        lzw_clearBlock(lzwP);
    }
}



static void
lzw_flush(lzwCompressor * const lzwP) {

    if (lzwP->lzw)
        lzwOutputCurrentString(lzwP);
        /* Put out the code for the final string. */

    codeBuffer_output(lzwP->codeBufferP, lzwP->eofCode);

    codeBuffer_flush(lzwP->codeBufferP);
}



static unsigned int
primaryHash(stringCode   const baseString,
            stringCode   const additionalPixel,
            unsigned int const hshift) {

    unsigned int hash;

    assert(baseString < maxCodeLimitLzw);
    assert(additionalPixel < MAXCMAPSIZE);

    hash = (additionalPixel << hshift) ^ baseString;
    
    return hash;
}

    

static void
lookupInHash(lzwCompressor *  const lzwP,
             unsigned int     const gifPixel,
             stringCode       const fcode,
             bool *           const foundP,
             unsigned short * const codeP,
             unsigned int *   const hashP) {

    int disp;
        /* secondary hash stride (after G. Knott) */
    int i;
        /* Index into hash table */

    i = primaryHash(lzwP->stringSoFar, gifPixel, lzwP->hshift);
    disp = (i == 0) ? 1 : lzwP->hsize - i;

    while (lzwP->hashTable[i].fcode != fcode &&
           lzwP->hashTable[i].fcode >= 0) {
        i -= disp;
        if (i < 0)
            i += lzwP->hsize;
    }

    if (lzwP->hashTable[i].fcode == fcode) {
        /* Found fcode in hash table */
        *foundP = TRUE;
        *codeP = lzwP->hashTable[i].ent;
    } else {
        /* Found where it _should_ be (but it's not) with primary hash */
        *foundP = FALSE;
        *hashP = i;
    }
}



static void
lzw_encodePixel(lzwCompressor * const lzwP,
                unsigned int    const gifPixel) {

    bool found;
    unsigned short code;
    unsigned int hash;
        /* Index into hash table where the value should go */
    
    assert(gifPixel < 256);

    if (!lzwP->buildingString) {
        /* Start a new string with just this pixel */
        lzwP->stringSoFar = gifPixel;
        lzwP->buildingString = TRUE;
    } else {
        stringCode const fcode =
            ((stringCode) gifPixel << BITS) + lzwP->stringSoFar;
            /* The encoding of the string we've already recognized plus the
               instant pixel, to be looked up in the hash of known strings.
            */
    
        lookupInHash(lzwP, gifPixel, fcode, &found, &code, &hash);

        if (found)
            /* With this new pixel, it is still a known string; 'code' is
               its code
            */
            lzwP->stringSoFar = code;
        else {
            /* It's no longer a known string.  Output the code for the
               known prefix of the string, thus defining a new string
               code for possible later use.  Warning:
               lzwOutputCurrentString() does more than you think. 
            */

            lzwP->hashTable[hash].ent   = lzwP->nextUnusedCode;
            lzwP->hashTable[hash].fcode = fcode;

            lzwOutputCurrentString(lzwP);

            /* This singleton pixel starts the next string */
            lzwP->stringSoFar = gifPixel;
        }
    }
}



/*
 * compress stdin to stdout
 *
 * Algorithm:  use open addressing double hashing (no chaining) on the
 * prefix code / next character combination.  We do a variant of Knuth's
 * algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
 * secondary probe.  Here, the modular division first probe is gives way
 * to a faster exclusive-or manipulation.  Also do block compression with
 * an adaptive reset, whereby the code table is cleared when the compression
 * ratio decreases, but after the table fills.  The variable-length output
 * codes are re-sized at this point, and a special CLEAR code is generated
 * for the decompressor.  Late addition:  construct the table according to
 * file size for noticeable speed improvement on small files.  Please direct
 * questions about this implementation to ames!jaw.
 */

static void
writePixelUncompressed(lzwCompressor * const lzwP,
                       unsigned int    const gifPixel) {
                      
    lzwP->stringSoFar = gifPixel;
    lzwOutputCurrentString(lzwP);

}    

static void
writeRaster(struct pam *  const pamP,
            rowReader *   const rowReaderP,
            unsigned int  const alphaPlane,
            unsigned int  const alphaThreshold,
            struct cmap * const cmapP, 
            unsigned int  const initBits,
            FILE *        const ofP,
            bool          const lzw) {
/*----------------------------------------------------------------------------
   Write the raster to file 'ofP'.

   Get the raster to write from 'rowReaderP', which gives tuples whose
   format is described by 'pamP'.

   Use the colormap 'cmapP' to generate the raster ('rowReaderP' gives
   pixel values as RGB samples; the GIF raster is colormap indices).

   Write the raster using LZW compression, or uncompressed depending
   on 'lzw'.
-----------------------------------------------------------------------------*/
    lzwCompressor * lzwP;
    tuple * tuplerow;
    unsigned int nRowsDone;
        /* Number of rows we have read so far from the the input (the
           last of which is the one we're working on now).  Note that
           in case of interlace, this is not the same thing as the row
           number of the current row.
        */
    
    lzwP = lzw_create(ofP, initBits, lzw, pamP->height * pamP->width);

    tuplerow = pnm_allocpamrow(pamP);

    lzw_clearBlock(lzwP);

    nRowsDone = 0;
    
    while (nRowsDone < pamP->height) {
        unsigned int col;

        rowReader_read(rowReaderP, tuplerow);

        for (col = 0; col < pamP->width; ++col) {
            unsigned int const colorIndex =
                gifPixel(pamP, tuplerow[col], alphaPlane, alphaThreshold,
                         cmapP);
            
                /* The value for the pixel in the GIF image.  I.e. the colormap
                   index.
                */
            if (lzw)
                lzw_encodePixel(lzwP, colorIndex);
            else
                writePixelUncompressed(lzwP, colorIndex);    
        }
        ++nRowsDone;
    }
    /* Gif is no good with no pixels; fortunately, that's impossible: */
    assert(nRowsDone > 0);

    lzw_flush(lzwP);

    pnm_freepamrow(tuplerow);
    
    lzw_destroy(lzwP);
}



static void
writeGlobalColorMap(FILE *              const ofP,
                    const struct cmap * const cmapP,
                    unsigned int        const bitsPerPixel) {
/*----------------------------------------------------------------------------
  Write out the Global Color Map

  Note that the Global Color Map is always a power of two colors
  in size, but *cmapP could be smaller than that.  So we pad with
  black.
-----------------------------------------------------------------------------*/
    unsigned int const colorMapSize = 1 << bitsPerPixel;

    struct pam pam;
    unsigned int i;
    tuple tupleRgb255;

    if (verbose)
        pm_message("Writing %u-entry global colormap for %u colors",
                   colorMapSize, cmapP->cmapSize);

    pam = cmapP->pam;
    pam.size = PAM_STRUCT_SIZE(allocation_depth);
    pam.len = pam.size;
    pnm_setminallocationdepth(&pam, 3);

    tupleRgb255 = pnm_allocpamtuple(&pam);

    for (i = 0; i < colorMapSize; ++i) {
        if (i < cmapP->cmapSize) {
            tuple const color = cmapP->color[i];

            assert(i < cmapP->cmapSize);

            pnm_scaletuple(&pam, tupleRgb255, color, 255);
            pnm_maketuplergb(&pam, tupleRgb255);

            fputc(tupleRgb255[PAM_RED_PLANE], ofP);
            fputc(tupleRgb255[PAM_GRN_PLANE], ofP);
            fputc(tupleRgb255[PAM_BLU_PLANE], ofP);
        } else {
            fputc(0, ofP);
            fputc(0, ofP);
            fputc(0, ofP);
        }
    }
    pnm_freepamtuple(tupleRgb255);
}
        


static void
writeGifHeader(FILE *              const ofP,
               unsigned int        const width,
               unsigned int        const height, 
               unsigned int        const background, 
               unsigned int        const bitsPerPixel,
               const struct cmap * const cmapP,
               char                const comment[],
               float               const aspect) {

    unsigned int const resolution = bitsPerPixel;

    unsigned char b;

    /* Write the Magic header */
    if (cmapP->haveTransparent || comment || aspect != 1.0 )
        fwrite("GIF89a", 1, 6, ofP);
    else
        fwrite("GIF87a", 1, 6, ofP);

    /* Write out the screen width and height */
    Putword(width,  ofP);
    Putword(height, ofP);

    /* Indicate that there is a global color map */
    b = 0x80;       /* Yes, there is a color map */

    /* OR in the resolution */
    b |= (resolution - 1) << 4;

    /* OR in the Bits per Pixel */
    b |= (bitsPerPixel - 1);

    /* Write it out */
    fputc(b, ofP);

    /* Write out the Background color */
    assert((unsigned char)background == background);
    fputc(background, ofP);

    {
        int const aspectValue = aspect == 1.0 ? 0 : ROUND(aspect * 64) - 15;
        assert(0 <= aspectValue && aspectValue <= 255); 
        fputc(aspectValue, ofP);
    }
    writeGlobalColorMap(ofP, cmapP, bitsPerPixel);

    if (cmapP->haveTransparent) 
        writeTransparentColorIndexExtension(ofP, cmapP->transparent);

    if (comment)
        writeCommentExtension(ofP, comment);
}



static void
writeImageHeader(FILE *       const ofP,
                 unsigned int const leftOffset,
                 unsigned int const topOffset,
                 unsigned int const gWidth,
                 unsigned int const gHeight,
                 bool         const gInterlace,
                 unsigned int const initCodeSize) {

    Putword(leftOffset, ofP);
    Putword(topOffset,  ofP);
    Putword(gWidth,     ofP);
    Putword(gHeight,    ofP);

    /* Write out whether or not the image is interlaced */
    if (gInterlace)
        fputc(0x40, ofP);
    else
        fputc(0x00, ofP);

    /* Write out the initial code size */
    fputc(initCodeSize, ofP);
}



static void
reportImageInfo(bool         const interlace,
                unsigned int const background,
                unsigned int const bitsPerPixel) {

    if (verbose) {
        if (interlace)
            pm_message("interlaced");
        else
            pm_message("not interlaced");
        pm_message("Background color index = %u", background);
        pm_message("%u bits per pixel", bitsPerPixel);
    }
}



static void
gifEncode(struct pam *  const pamP,
          FILE *        const ofP, 
          pm_filepos    const rasterPos,
          bool          const gInterlace,
          int           const background, 
          unsigned int  const bitsPerPixel,
          struct cmap * const cmapP,
          char          const comment[],
          float         const aspect,
          bool          const lzw) {

    unsigned int const leftOffset = 0;
    unsigned int const topOffset  = 0;

    unsigned int const initCodeSize = bitsPerPixel <= 1 ? 2 : bitsPerPixel;
        /* The initial code size */

    sample const alphaThreshold = (pamP->maxval + 1) / 2;
        /* Levels below this in the alpha plane indicate transparent
           pixels in the output image.
        */

    unsigned int const alphaPlane = pamAlphaPlane(pamP);

    rowReader * rowReaderP;

    reportImageInfo(gInterlace, background, bitsPerPixel);

    if (pamP->width > 65535)
        pm_error("Image width %u too large for GIF format.  (Max 65535)",
                 pamP->width);
     
    if (pamP->height > 65535)
        pm_error("Image height %u too large for GIF format.  (Max 65535)",
                 pamP->height);

    writeGifHeader(ofP, pamP->width, pamP->height, background,
                   bitsPerPixel, cmapP, comment, aspect);

    /* Write an Image separator */
    fputc(',', ofP);

    writeImageHeader(ofP, leftOffset, topOffset, pamP->width, pamP->height,
                     gInterlace, initCodeSize);

    rowReaderP = rowReader_create(pamP, rasterPos, gInterlace);

    /* Write the actual raster */

    writeRaster(pamP, rowReaderP, alphaPlane, alphaThreshold,
                cmapP, initCodeSize + 1, ofP, lzw);

    rowReader_destroy(rowReaderP);

    /* Write out a zero length data block (to end the series) */
    fputc(0, ofP);

    /* Write the GIF file terminator */
    fputc(';', ofP);
}



static void
reportTransparent(struct cmap * const cmapP) {

    if (verbose) {
        if (cmapP->haveTransparent) {
            tuple const color = cmapP->color[cmapP->transparent];
            pm_message("Color %u (%lu, %lu, %lu) is transparent",
                       cmapP->transparent,
                       color[PAM_RED_PLANE],
                       color[PAM_GRN_PLANE],
                       color[PAM_BLU_PLANE]);
        } else
            pm_message("No transparent color");
    }
}



static void
computeTransparent(char          const colorarg[], 
                   bool          const usingFakeTrans,
                   unsigned int  const fakeTransparent,
                   struct cmap * const cmapP) {
/*----------------------------------------------------------------------------
   Figure out the color index (index into the colormap) of the color
   that is to be transparent in the GIF.

   colorarg[] is the string that specifies the color the user wants to
   be transparent (e.g. "red", "#fefefe").  Its maxval is the maxval
   of the colormap.  'cmap' is the full colormap except that its
   'transparent' component isn't valid.

   colorarg[] is a standard Netpbm color specification, except that
   may have a "=" prefix, which means it specifies a particular exact
   color, as opposed to without the "=", which means "the color that
   is closest to this and actually in the image."

   colorarg[] null means the color didn't ask for a particular color
   to be transparent.

   Establish no transparent color if colorarg[] specifies an exact
   color and that color is not in the image.  Also issue an
   informational message.

   'usingFakeTrans' means pixels will be transparent because of something
   other than their foreground color, and 'fakeTransparent' is the
   color map index for transparent colors.
-----------------------------------------------------------------------------*/
    if (colorarg) {
        const char * colorspec;
        bool exact;
        tuple transcolor;
        int found;
        int colorindex;
        
        if (colorarg[0] == '=') {
            colorspec = &colorarg[1];
            exact = TRUE;
        } else {
            colorspec = colorarg;
            exact = FALSE;
        }

        transcolor = pnm_parsecolor(colorspec, cmapP->pam.maxval);
        pnm_lookuptuple(&cmapP->pam, cmapP->tuplehash, transcolor, &found,
                        &colorindex);
        
        if (found) {
            cmapP->haveTransparent = TRUE;
            cmapP->transparent = colorindex;
        } else if (!exact) {
            cmapP->haveTransparent = TRUE;
            cmapP->transparent = closestColor(transcolor, &cmapP->pam, cmapP);
        } else {
            cmapP->haveTransparent = FALSE;
            pm_message("Warning: specified transparent color "
                       "does not occur in image.");
        }
    } else if (usingFakeTrans) {
        cmapP->haveTransparent = TRUE;
        cmapP->transparent = fakeTransparent;
    } else
        cmapP->haveTransparent = FALSE;

    reportTransparent(cmapP);
}



static unsigned int
sortOrderColor(tuple const tuple) {

    return ((tuple[PAM_RED_PLANE] * MAXCMAPSIZE) +
            tuple[PAM_GRN_PLANE]) * MAXCMAPSIZE +
           tuple[PAM_BLU_PLANE];
}



#ifndef LITERAL_FN_DEF_MATCH
static qsort_comparison_fn sortCompareColor;
#endif

static int
sortCompareColor(const void * const entry1P,
                 const void * const entry2P) {

    struct tupleint * const * const tupleint1PP = entry1P;
    struct tupleint * const * const tupleint2PP = entry2P;

    return (sortOrderColor((*tupleint1PP)->tuple) 
            - sortOrderColor((*tupleint2PP)->tuple));
}



#ifndef LITERAL_FN_DEF_MATCH
static qsort_comparison_fn sortCompareGray;
#endif

static int
sortCompareGray(const void * const entry1P,
                const void * const entry2P){

    struct tupleint * const * const tupleint1PP = entry1P;
    struct tupleint * const * const tupleint2PP = entry2P;

    return ((*tupleint1PP)->tuple[0] - (*tupleint2PP)->tuple[0]);
}



static void
sortTupletable(struct pam * const mapPamP,
               unsigned int const colors,
               tupletable   const tuplefreq) {
/*----------------------------------------------------------------------------
   Sort the colormap *cmapP.

   Sort the colormap by red intensity, then by green intensity,
   then by blue intensity.
-----------------------------------------------------------------------------*/

    pm_message("sorting colormap");

    if (mapPamP->depth < 3)
        qsort(tuplefreq, colors, sizeof(tuplefreq[0]), sortCompareGray);
    else
        qsort(tuplefreq, colors, sizeof(tuplefreq[0]), sortCompareColor); 

}



static void
addToColormap(struct cmap *  const cmapP, 
              const char *   const colorspec, 
              unsigned int * const newIndexP) {
/*----------------------------------------------------------------------------
  Add a new entry to the colormap.  Make the color that specified by
  'colorspec', and return the index of the new entry as *newIndexP.

  'colorspec' is a color specification given by the user, e.g.
  "red" or "rgb:ff/03/0d".  The maxval for this color specification is
  that for the colormap *cmapP.
-----------------------------------------------------------------------------*/
    tuple const transcolor = pnm_parsecolor(colorspec, cmapP->pam.maxval);

    unsigned int const colorIndex = cmapP->cmapSize++;

    cmapP->color[colorIndex] = pnm_allocpamtuple(&cmapP->pam);

    if (cmapP->pam.depth < 3) {
        if (!pnm_rgbtupleisgray(transcolor))
            pm_error("Image is grayscale, but color '%s' is not gray.  "
                     "It is (%lu, %lu, %lu)",
                     colorspec,
                     transcolor[PAM_RED_PLANE],
                     transcolor[PAM_GRN_PLANE],
                     transcolor[PAM_BLU_PLANE]);
        else
            cmapP->color[colorIndex][0] = transcolor[0];
    } else {
        pnm_assigntuple(&cmapP->pam, cmapP->color[colorIndex], transcolor);
    }
    *newIndexP = colorIndex;
}



static void
colormapFromFile(char               const filespec[],
                 unsigned int       const maxcolors,
                 tupletable *       const tupletableP, 
                 struct pam *       const mapPamP,
                 unsigned int *     const colorCountP) {
/*----------------------------------------------------------------------------
   Read a colormap from the Netpbm file filespec[].  Return a
   tupletable of the colors in it (which is practically a colormap) as
   *tupletableP and the format of those tuples as *mapPamP.  Return
   the number of colors as *colorsCountP.
-----------------------------------------------------------------------------*/
    FILE * mapfileP;
    tuple ** colors;
    unsigned int colorCount;

    mapfileP = pm_openr(filespec);
    colors = pnm_readpam(mapfileP, mapPamP, PAM_STRUCT_SIZE(tuple_type));
    pm_close(mapfileP);

    pm_message("computing other colormap ...");
    
    *tupletableP = 
        pnm_computetuplefreqtable(mapPamP, colors, maxcolors, &colorCount);

    *colorCountP = colorCount;

    pnm_freepamarray(colors, mapPamP); 
}



static void
readAndValidateColormapFromFile(char           const filename[],
                                unsigned int   const maxcolors,
                                tupletable *   const tuplefreqP, 
                                struct pam *   const mapPamP,
                                unsigned int * const colorCountP,
                                unsigned int   const nInputComp,
                                sample         const inputMaxval) {
/*----------------------------------------------------------------------------
   Read the colormap from a separate colormap file named filename[],
   and make sure it's consistent with an image with 'nInputComp'
   color components (e.g. 3 for RGB) and a maxval of 'inputMaxval'.
-----------------------------------------------------------------------------*/
    colormapFromFile(filename, maxcolors, tuplefreqP, mapPamP, colorCountP);

    if (mapPamP->depth != nInputComp)
        pm_error("Depth of map file (%u) does not match number of "
                 "color components in input file (%u)",
                 mapPamP->depth, nInputComp);
    if (mapPamP->maxval != inputMaxval)
        pm_error("Maxval of map file (%lu) does not match maxval of "
                 "input file (%lu)", mapPamP->maxval, inputMaxval);
}



static void
computeColormapBw(struct pam *   const pamP,
                  struct pam *   const mapPamP,
                  unsigned int * const colorCountP,
                  tupletable   * const tuplefreqP) {
/*----------------------------------------------------------------------------
  Shortcut for black and white (e.g. PBM).  We know that there are
  only two colors.  Users who know that only one color is present in
  the image should specify -sort at the command line.  Example:

   $ pbmmake -w 600 400 | pamtogif -sort > canvas.gif
-----------------------------------------------------------------------------*/
    tupletable const colormap = pnm_alloctupletable(pamP, 2);
    
    *mapPamP = *pamP;
    mapPamP->depth = 1;

    colormap[0]->value = 1;
    colormap[0]->tuple[0] = PAM_BLACK;
    colormap[1]->value = 1;
    colormap[1]->tuple[0] = PAM_BW_WHITE;
    
    *tuplefreqP  = colormap;
    *colorCountP = 2;
}
  
    

static void
computeColormapFromInput(struct pam *   const pamP,
                         unsigned int   const maxcolors,
                         unsigned int   const nInputComp,
                         struct pam *   const mapPamP,
                         unsigned int * const colorCountP,
                         tupletable *   const tuplefreqP) {
    
    tupletable tuplefreq;

    pm_message("computing colormap...");

    tuplefreq = pnm_computetuplefreqtable3(
        pamP, NULL, maxcolors, nInputComp, pamP->maxval, colorCountP);

    *mapPamP = *pamP;
    mapPamP->depth = nInputComp;

    *tuplefreqP = tuplefreq;
}



static void
computeLibnetpbmColormap(struct pam *   const pamP,
                         bool           const haveAlpha,
                         const char *   const mapfile,
                         tuple *        const color,
                         tuplehash *    const tuplehashP,
                         struct pam *   const mapPamP,
                         unsigned int * const colorCountP,
                         bool           const sort) {
/*----------------------------------------------------------------------------
   Compute a colormap, libnetpbm style, for the image described by
   'pamP', which is positioned to the raster.

   If 'mapfile' is non-null, Use the colors in that (Netpbm) file for
   the color map instead of the colors in 'pamP'.

   Return the colormap as color[] and *tuplehashP.  Return the format
   of those tuples as *mapPamP.

   The tuples of the color map have a meaningful depth of 1 (grayscale) or 3
   (color) and *mapPamP reflects that.

   While we're at it, count the colors and validate that there aren't
   too many.  Return the count as *colorCountP.  In determining if there are
   too many, allow one slot for a fake transparency color if 'haveAlpha'
   is true.  If there are too many, issue an error message and abort the
   program.

   'sort' means to sort the colormap by red intensity, then by green
   intensity, then by blue intensity, as opposed to arbitrary order.
-----------------------------------------------------------------------------*/
    unsigned int const maxcolors = haveAlpha ? MAXCMAPSIZE - 1 : MAXCMAPSIZE;
        /* The most colors we can tolerate in the image.  If we have
           our own made-up entry in the colormap for transparency, it
           isn't included in this count.
        */
    unsigned int const nInputComp = haveAlpha ? pamP->depth - 1 : pamP->depth;
        /* Number of color components (not alpha) in the input image */

    unsigned int i;
    tupletable tuplefreq;
    unsigned int colorCount;

    if (mapfile)
        readAndValidateColormapFromFile(mapfile, maxcolors, &tuplefreq,
                                        mapPamP, &colorCount,
                                        nInputComp, pamP->maxval);
    else if (nInputComp == 1 && pamP->maxval == 1 && !sort &&
             pamP->height * pamP->width > 1)
        computeColormapBw(pamP, mapPamP, &colorCount, &tuplefreq);
    else
        computeColormapFromInput(pamP, maxcolors, nInputComp, 
                                 mapPamP, &colorCount, &tuplefreq);
    
    if (tuplefreq == NULL)
        pm_error("too many colors - try doing a 'pnmquant %u'", maxcolors);

    pm_message("%u colors found", colorCount);

    if (sort)
        sortTupletable(mapPamP, colorCount, tuplefreq);

    for (i = 0; i < colorCount; ++i) {
        color[i] = pnm_allocpamtuple(mapPamP);
        pnm_assigntuple(mapPamP, color[i], tuplefreq[i]->tuple);
    }

    /* And make a hash table for fast lookup. */
    *tuplehashP =
        pnm_computetupletablehash(mapPamP, tuplefreq, colorCount);

    *colorCountP = colorCount;

    pnm_freetupletable(mapPamP, tuplefreq);
}



static void
destroyCmap(struct cmap * const cmapP) {

    unsigned int colorIndex;
    
    for (colorIndex = 0; colorIndex < cmapP->cmapSize; ++colorIndex)
        pnm_freepamtuple(cmapP->color[colorIndex]);

    pnm_destroytuplehash(cmapP->tuplehash);
}



int
main(int argc, char *argv[]) {
    struct cmdlineInfo cmdline;
    FILE * ifP;
    struct pam pam;
    unsigned int bitsPerPixel;
    pm_filepos rasterPos;

    struct cmap cmap;
        /* The colormap, with all its accessories */
    unsigned int fakeTransparent;
        /* colormap index of the fake transparency color we're using to
           implement the alpha mask.  Undefined if we're not doing an alpha
           mask.
        */
    
    pnm_init(&argc, argv);
    
    parseCommandLine(argc, argv, &cmdline);
    
    verbose = cmdline.verbose;
    
    ifP = pm_openr_seekable(cmdline.input_filespec);
    
    pnm_readpaminit(ifP, &pam, PAM_STRUCT_SIZE(tuple_type));
    
    pm_tell2(ifP, &rasterPos, sizeof(rasterPos));
    
    computeLibnetpbmColormap(&pam, !!pamAlphaPlane(&pam), cmdline.mapfile, 
                             cmap.color, &cmap.tuplehash,
                             &cmap.pam, &cmap.cmapSize, cmdline.sort);
    
    assert(cmap.pam.maxval == pam.maxval);

    if (pamAlphaPlane(&pam)) {
        /* Add a fake entry to the end of the colormap for transparency.  
           Make its color black. 
        */
        addToColormap(&cmap, cmdline.alphacolor, &fakeTransparent);
    }

    bitsPerPixel = cmap.cmapSize == 1 ? 1 : nSignificantBits(cmap.cmapSize-1);

    computeTransparent(cmdline.transparent,
                       !!pamAlphaPlane(&pam), fakeTransparent, &cmap);

    /* All set, let's do it. */
    gifEncode(&pam, stdout, rasterPos,
              cmdline.interlace, 0, bitsPerPixel, &cmap, cmdline.comment,
              cmdline.aspect, !cmdline.nolzw);
    
    destroyCmap(&cmap);

    pm_close(ifP);
    pm_close(stdout);
    
    return 0;
}



/*============================================================================
  Original version, named 'ppmgif' was by Jef Poskanzer in 1989, based
  on GIFENCOD by David Rowley <mgardi@watdscu.waterloo.edu>.A Lempel-Zim
  compression based on "compress".

  Switched to use libnetpbm PAM facilities (ergo process PAM images)
  and renamed 'pamtogif' by Bryan Henderson November 2006.

  The non-LZW GIF generation stuff was adapted from the Independent
  JPEG Group's djpeg on 2001.09.29.  In 2006.12 the output subroutines
  were rewritten; now no uncompressed output subroutines are derived from
  the Independent JPEG Group's source code.
  
  2007.01  Changed sort routine to qsort.  (afu)
  2007.03  Implemented variable hash table size, PBM color table
           shortcut and "-aspect" command line option.   (afu)

 
  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.
 
  The Graphics Interchange Format(c) is the Copyright property of
  CompuServe Incorporated.  GIF(sm) is a Service Mark property of
  CompuServe Incorporated.
============================================================================*/

