about summary refs log tree commit diff
path: root/converter/other/giftopnm.c
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2012-05-25 03:10:18 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2012-05-25 03:10:18 +0000
commit2bd3d86f4e356af5143debe2667101896d3e5a9b (patch)
tree86a00e4b5f4e50068e4e8b69ee65489955a0e4b3 /converter/other/giftopnm.c
parent703ed293ca74a3e20430fdc9fb527c53ea866b85 (diff)
downloadnetpbm-mirror-2bd3d86f4e356af5143debe2667101896d3e5a9b.tar.gz
netpbm-mirror-2bd3d86f4e356af5143debe2667101896d3e5a9b.tar.xz
netpbm-mirror-2bd3d86f4e356af5143debe2667101896d3e5a9b.zip
Fix bugs with invalid input; respond better to invalid inputs, speed up, cleanup
git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@1689 9d0c8265-081b-0410-96cb-a4ca84ce46f8
Diffstat (limited to 'converter/other/giftopnm.c')
-rw-r--r--converter/other/giftopnm.c1315
1 files changed, 840 insertions, 475 deletions
diff --git a/converter/other/giftopnm.c b/converter/other/giftopnm.c
index faab1086..e14fb961 100644
--- a/converter/other/giftopnm.c
+++ b/converter/other/giftopnm.c
@@ -8,8 +8,9 @@
 /* |   provided "as is" without express or implied warranty.           | */
 /* +-------------------------------------------------------------------+ */
 
-/* There is a copy of the GIF89 specification, as defined by its
-   inventor, Compuserve, in 1989, at http://members.aol.com/royalef/gif89a.txt
+/* There is a copy of the GIF89 specification, as defined by its inventor,
+   Compuserve, in 1990 at: 
+   http://www.w3.org/Graphics/GIF/spec-gif89a.txt
 
    This covers the high level format, but does not cover how the "data"
    contents of a GIF image represent the raster of color table indices.
@@ -18,7 +19,6 @@
    describe the Lempel-Ziv base.
 */
 
-#define _BSD_SOURCE   /* Make sure strcasecmp() is in string.h */
 
 #include <string.h>
 #include <assert.h>
@@ -48,8 +48,22 @@
   #define LITTLE_ENDIAN 1
 #endif
 
+#ifndef   FASTPBMRENDER
+  #define FASTPBMRENDER TRUE
+#endif
+
+static const bool useFastPbmRender = FASTPBMRENDER;
+
+#ifndef   REPORTLZWCODES
+  #define REPORTLZWCODES FALSE
+#endif
+
+static const bool wantLzwCodes = REPORTLZWCODES;
+    /* In verbose output, include all the LZW codes */
+
 
-static __inline__ bool
+
+static bool
 ReadOK(FILE *          const fileP,
        unsigned char * const buffer,
        size_t          const len) {
@@ -89,23 +103,19 @@ readFile(FILE *          const ifP,
 
 
 
-#define LM_to_uint(a,b)                        (((b)<<8)|(a))
-
-static int const maxnum_lzwCode = (1<<MAX_LZW_BITS);
-
 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;  /* Filespecs of input files */
+    const char * inputFilespec;  /* Filespecs of input files */
     unsigned int verbose;    /* -verbose option */
     unsigned int comments;   /* -comments option */
-    bool all_images;  /* He wants all the images */
-    unsigned int image_no;
+    bool allImages;  /* He wants all the images */
+    unsigned int imageNum;
         /* image number he wants from input, starting at 0.  Undefined
-           if all_images is TRUE
+           if allImages is TRUE
         */
-    const char * alpha_filename;
+    const char * alphaFileName;
     unsigned int quitearly;
     unsigned int repair;
 };
@@ -143,7 +153,7 @@ parseCommandLine(int argc, char ** argv,
             &cmdlineP->repair,          0);
     OPTENT3(0, "image",       OPT_STRING, &image,
             &imageSpec,                 0);
-    OPTENT3(0, "alphaout",    OPT_STRING, &cmdlineP->alpha_filename, 
+    OPTENT3(0, "alphaout",    OPT_STRING, &cmdlineP->alphaFileName, 
             &alphaSpec,                 0);
 
     opt.opt_table = option_def;
@@ -153,110 +163,169 @@ parseCommandLine(int argc, char ** argv,
     pm_optParseOptions3( &argc, argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
+    free(option_def);
+
     if (!imageSpec) {
-        cmdlineP->image_no = 0;
-        cmdlineP->all_images = FALSE;
+        cmdlineP->imageNum = 0;
+        cmdlineP->allImages = FALSE;
     } else {
-        if (strcasecmp(image, "all") == 0) 
-            cmdlineP->all_images = TRUE;
-        else {
+        if (strcaseeq(image, "all")) { 
+            cmdlineP->allImages = TRUE;
+        } else {
             char * tailptr;
 
-            long const imageNo = strtol(image, &tailptr, 10);
+            long const imageNum = strtol(image, &tailptr, 10);
 
             if (*tailptr != '\0')
                 pm_error("Invalid value for '-image' option.  Must be either "
                          "a number or 'all'.  You specified '%s'", image);
-            else if (imageNo < 0)
+            else if (imageNum < 0)
                 pm_error("Invalid value for '-image' option.  Must be "
-                         "positive.  You specified %ld", imageNo);
-            else if (imageNo == 0)
+                         "positive.  You specified %ld", imageNum);
+            else if (imageNum == 0)
                 pm_error("Invalid value for 'image' option.  You specified "
                          "zero.  The first image is 1.");
 
-            cmdlineP->all_images = FALSE;
-            cmdlineP->image_no = (unsigned int) imageNo - 1;
+            cmdlineP->allImages = FALSE;
+            cmdlineP->imageNum = (unsigned int) imageNum - 1;
         }
     }
     
     if (argc-1 == 0) 
-        cmdlineP->input_filespec = "-";
+        cmdlineP->inputFilespec = "-";
     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];
+        cmdlineP->inputFilespec = argv[1];
 
     if (!alphaSpec) 
-        cmdlineP->alpha_filename = NULL;
+        cmdlineP->alphaFileName = NULL;
 }
 
 
-typedef unsigned char gifColorMap[3][MAXCOLORMAPSIZE];
+
+typedef struct {
+    unsigned char map[MAXCOLORMAPSIZE][3];
+    unsigned int size;
+} GifColorMap;
+
+/*----------------------------------------------------------------------
+  On GIF color maps:
+
+  The color map can have any number of colors up to 256.  If the color map
+  size is not a power of 2 the table is padded up.  The LZW "clear" control
+  code is always placed at a power of 2.
+
+  The color map and code table of an image with three colors (black, white
+  and red) will look like this:
+
+  0: black
+  1: white
+  2: red
+  3: (unused)
+  4: clear code
+  5: end code
+  6: first LZW string code
+  7: second LZW string code
+     ...
+  4095: last LZW string code
+
+  Some GIFs have odd color maps.
+
+  (1) Some encoders use fixed color maps.  A GIF image produced by this
+      kind of encoder may have colors in the table which never appear in
+      the image.
+
+      Note that we make the decision on whether the output should be PBM,
+      PGM or PPM by scanning through the color map, not the entire image.
+      Any unused colors will influence our decision.
+
+  (2) There are GIF editors which allow one to rewrite the color map.
+      These programs will produce color maps with multiple entries for the
+      same color.
+
+  (3) Some encoders put the transparent code outside the color map.
+      (In the above example, the unused value 3.)  Around 2000 there were
+      several encoders that did this, including "Animation Gif Maker
+      (GifAnim)".  As of 2012, such images are rare.  We reject them with
+      an error message unless -repair is specified.
+-----------------------------------------------------------------------*/
+
 
 struct gifScreen {
     unsigned int    Width;
     unsigned int    Height;
-    gifColorMap     ColorMap;
-    unsigned int    ColorMapSize;
-        /* Number of colors in the color map. */
+    GifColorMap     ColorMap;
     unsigned int    ColorResolution;
     unsigned int    Background;
     unsigned int    AspectRatio;
         /* Aspect ratio of each pixel, times 64, minus 15.  (i.e. 1 => 1:4).
            But Zero means 1:1.
         */
-    int      hasGray;  
+    bool     hasGray;  
         /* Boolean: global colormap has at least one gray color
            (not counting black and white) 
         */
-    int      hasColor;
+    bool     hasColor;
         /* Boolean: global colormap has at least one non-gray,
            non-black, non-white color 
         */
 };
 
 struct gif89 {
-       int     transparent;
-       int     delayTime;
-       int     inputFlag;
-       int     disposal;
+    bool         haveTransColor;
+        /* The GIF specifies a transparent background color */
+    unsigned int transparentIndex;
+        /* The color index of the color which is the transparent 
+           background color.
+
+           Meaningful only when 'haveTransColor' is true
+        */
+    bool          haveDelayTime;
+    unsigned int  delayTime;
+    bool          haveInputFlag;
+    unsigned char inputFlag;
+    bool          haveDisposal;
+    unsigned char disposal;
 };
 
 static void
 initGif89(struct gif89 * const gif89P) {
-    gif89P->transparent = -1;
-    gif89P->delayTime = -1;
-    gif89P->inputFlag = -1;
-    gif89P->disposal = -1;
+    gif89P->haveTransColor = false;
+    gif89P->haveDelayTime  = false;
+    gif89P->haveInputFlag  = false;
+    gif89P->haveDisposal   = false;
 }       
 
 
-static int verbose;
-int    showComment;
+static bool verbose;
+static bool showComment;
 
 
 
 static void
-readColorMap(FILE *ifP, const int colormapsize, 
-             unsigned char colormap[3][MAXCOLORMAPSIZE],
-             int *hasGrayP, int * const hasColorP) {
+readColorMap(FILE *        const ifP,
+             unsigned int  const cmapSize,
+             GifColorMap * const cmapP,
+             bool *        const hasGrayP,
+             bool *        const hasColorP) {
 
     int             i;
     unsigned char   rgb[3];
 
-    assert(colormapsize <= MAXCOLORMAPSIZE);
+    assert(cmapSize <= MAXCOLORMAPSIZE);
 
     *hasGrayP = FALSE;  /* initial assumption */
     *hasColorP = FALSE;  /* initial assumption */
 
-    for (i = 0; i < colormapsize; ++i) {
+    for (i = 0; i < cmapSize; ++i) {
         if (! ReadOK(ifP, rgb, sizeof(rgb)))
             pm_error("Unable to read Color %d from colormap", i);
 
-        colormap[CM_RED][i] = rgb[0] ;
-        colormap[CM_GRN][i] = rgb[1] ;
-        colormap[CM_BLU][i] = rgb[2] ;
+        cmapP->map[i][CM_RED] = rgb[0] ;
+        cmapP->map[i][CM_GRN] = rgb[1] ;
+        cmapP->map[i][CM_BLU] = rgb[2] ;
 
         if (rgb[0] == rgb[1] && rgb[1] == rgb[2]) {
             if (rgb[0] != 0 && rgb[0] != GIFMAXVAL)
@@ -264,6 +333,7 @@ readColorMap(FILE *ifP, const int colormapsize,
         } else
             *hasColorP = TRUE;
     }
+    cmapP->size = cmapSize;
 }
 
 
@@ -341,7 +411,7 @@ readThroughEod(FILE * const ifP) {
   If there is no EOD marker between the present file position and EOF,
   we read to EOF and issue warning message about a missing EOD marker.
 -----------------------------------------------------------------------------*/
-    unsigned char buf[260];
+    unsigned char buf[256];
     bool eod;
 
     eod = FALSE;  /* initial value */
@@ -364,6 +434,27 @@ readThroughEod(FILE * const ifP) {
 
 
 static void
+doPlainTextExtension(FILE * const ifP) {
+#if 0
+    /* incomplete code fragment, attempt to handle Plain Text Extension */
+    GetDataBlock(ifP, (unsigned char*) buf, &eof, &length);
+    
+    lpos   = LM_to_uint(buf[0], buf[1]);
+    tpos   = LM_to_uint(buf[2], buf[3]);
+    width  = LM_to_uint(buf[4], buf[5]);
+    height = LM_to_uint(buf[6], buf[7]);
+    cellw  = buf[8];
+    cellh  = buf[9];
+    foreground = buf[10];
+    background = buf[11];
+#else
+    readThroughEod(ifP);
+#endif
+}
+
+
+
+static void
 doCommentExtension(FILE * const ifP) {
 /*----------------------------------------------------------------------------
    Read the rest of a comment extension from the input file 'ifP' and handle
@@ -373,7 +464,7 @@ doCommentExtension(FILE * const ifP) {
    it could have nonprintable characters or embedded nulls.  I don't know if
    the GIF spec requires regular text or not.
 -----------------------------------------------------------------------------*/
-    char buf[255+1];
+    char buf[256];
     unsigned int blocklen;  
     bool done;
 
@@ -398,13 +489,22 @@ doCommentExtension(FILE * const ifP) {
 
 
 
+static unsigned int
+LM_to_uint(unsigned char const a,
+           unsigned char const b) {
+
+    return ((unsigned int)b << 8) | ((unsigned int) a << 0);
+}
+
+
+
 static void 
 doGraphicControlExtension(FILE *         const ifP,
                           struct gif89 * const gif89P) {
 
     bool eof;
     unsigned int length;
-    static unsigned char buf[256];
+    unsigned char buf[256];
     const char * error;
 
     getDataBlock(ifP, buf, &eof, &length, &error);
@@ -418,11 +518,16 @@ doGraphicControlExtension(FILE *         const ifP,
                  "It must be at least 4 bytes; it is %d bytes.",
                  length);
     else {
+        gif89P->haveDisposal = true;
         gif89P->disposal = (buf[0] >> 2) & 0x7;
+        gif89P->haveInputFlag = true;
         gif89P->inputFlag = (buf[0] >> 1) & 0x1;
-        gif89P->delayTime = LM_to_uint(buf[1],buf[2]);
-        if ((buf[0] & 0x1) != 0)
-            gif89P->transparent = buf[3];
+        gif89P->haveDelayTime = true;
+        gif89P->delayTime = LM_to_uint(buf[1], buf[2]);
+        if ((buf[0] & 0x1) != 0) {
+            gif89P->haveTransColor = true;
+            gif89P->transparentIndex = buf[3];
+        }
         readThroughEod(ifP);
     }
 }
@@ -430,34 +535,16 @@ doGraphicControlExtension(FILE *         const ifP,
 
 
 static void
-doExtension(FILE * const ifP, int const label, struct gif89 * const gif89P) {
+doExtension(FILE *         const ifP,
+            unsigned char  const label,
+            struct gif89 * const gif89P) {
+
     const char * str;
     
     switch (label) {
     case 0x01:              /* Plain Text Extension */
         str = "Plain Text";
-#ifdef notdef
-        GetDataBlock(ifP, (unsigned char*) buf, &eof, &length);
-        
-        lpos   = LM_to_uint(buf[0], buf[1]);
-        tpos   = LM_to_uint(buf[2], buf[3]);
-        width  = LM_to_uint(buf[4], buf[5]);
-        height = LM_to_uint(buf[6], buf[7]);
-        cellw  = buf[8];
-        cellh  = buf[9];
-        foreground = buf[10];
-        background = buf[11];
-        
-        while (GetDataBlock(ifP, (unsigned char*) buf) != 0) {
-            PPM_ASSIGN(xels[ypos][xpos],
-                       cmap[CM_RED][v],
-                       cmap[CM_GRN][v],
-                       cmap[CM_BLU][v]);
-            ++index;
-        }
-#else
-        readThroughEod(ifP);
-#endif
+        doPlainTextExtension(ifP);
         break;
     case 0xff:              /* Application Extension */
         str = "Application";
@@ -472,16 +559,15 @@ doExtension(FILE * const ifP, int const label, struct gif89 * const gif89P) {
         doGraphicControlExtension(ifP, gif89P);
         break;
     default: {
-        static char buf[256];
-        str = buf;
+        char buf[256];
         sprintf(buf, "UNKNOWN (0x%02x)", label);
+        str = buf;
         pm_message("Ignoring unrecognized extension (type 0x%02x)", label);
         readThroughEod(ifP);
-        }
-        break;
+    } break;
     }
     if (verbose)
-        pm_message(" got a '%s' extension", str );
+        pm_message(" got a '%s' extension", str);
 }
 
 
@@ -661,6 +747,9 @@ getCode_get(struct getCodeState * const gsP,
         } else {
             *codeP = bitsOfLeBuffer(gsP->buf, gsP->curbit, codeSize);
 
+            if (verbose && wantLzwCodes)
+                pm_message("LZW code=0x%03x [%d]", *codeP, codeSize);
+
             gsP->curbit += codeSize;
             *eofP = FALSE;
         }
@@ -669,12 +758,11 @@ getCode_get(struct getCodeState * const gsP,
 
 
 
-
 struct stack {
     /* Stack grows from low addresses to high addresses */
-    int * stack;  /* malloc'ed array */
-    int * sp;     /* stack pointer */
-    int * top;    /* next word above top of stack */
+    unsigned char * stack;  /* malloc'ed array */
+    unsigned char * sp;     /* stack pointer */
+    unsigned char * top;    /* next word above top of stack */
 };
 
 
@@ -692,7 +780,7 @@ initStack(struct stack * const stackP, unsigned int const size) {
 
 
 static void
-pushStack(struct stack * const stackP, int const value) {
+pushStack(struct stack * const stackP, unsigned char const value) {
 
     if (stackP->sp >= stackP->top)
         pm_error("stack overflow");
@@ -709,7 +797,7 @@ stackIsEmpty(const struct stack * const stackP) {
 
 
 
-static int
+static unsigned char
 popStack(struct stack * const stackP) {
 
     if (stackP->sp <= stackP->stack)
@@ -767,34 +855,43 @@ termStack(struct stack * const stackP) {
 
 -----------------------------------------------------------------------------*/
 
+static int const maxLzwCodeCt = (1<<MAX_LZW_BITS);
 
 struct decompressor {
     struct stack stack;
-    int      fresh;
+    bool fresh;
         /* The stream is right after a clear code or at the very beginning */
-    int      codeSize;
+    unsigned int codeSize;
         /* The current code size -- each LZW code in this part of the image
            is this many bits.  Ergo, we read this many bits at a time from
            the stream.
         */
-    int      maxnum_code;
+    unsigned int maxCodeCt;
         /* The maximum number of LZW codes that can be represented with the 
            current code size.  (1 << codeSize)
         */
-    int      next_tableSlot;
+    unsigned int nextTableSlot;
         /* Index in the code translation table of the next free entry */
     unsigned int firstcode;
         /* This is always a true data element code */
-    int      prevcode;
+    unsigned int prevcode;
         /* The code just before, in the image, the one we're processing now */
-    int      table[2][(1 << MAX_LZW_BITS)];
 
     /* The following are constant for the life of the decompressor */
     FILE * ifP;
-    int init_codeSize;
-    int max_dataVal;
-    int clear_code;
-    int end_code; 
+    unsigned int initCodeSize;
+    unsigned int cmapSize;
+    unsigned int maxDataVal;
+    unsigned int clearCode;
+    unsigned int endCode;
+    bool haveTransColor;
+    unsigned int transparentIndex;
+        /* meaningful only when 'haveTransColor' is true */
+    bool tolerateBadInput; 
+        /* We are to tolerate bad input data as best we can, rather than
+           just declaring an error and bailing out.
+        */
+    unsigned int table[(1 << MAX_LZW_BITS)][2];   /* LZW code table */  
 };
 
 
@@ -802,10 +899,35 @@ struct decompressor {
 static void
 resetDecompressor(struct decompressor * const decompP) {
 
-    decompP->codeSize = decompP->init_codeSize+1;
-    decompP->maxnum_code = 1 << decompP->codeSize;
-    decompP->next_tableSlot = decompP->max_dataVal + 3;
-    decompP->fresh = 1;
+    decompP->codeSize      = decompP->initCodeSize+1;
+    decompP->maxCodeCt     = 1 << decompP->codeSize;
+    decompP->nextTableSlot = decompP->maxDataVal + 3;
+    decompP->fresh         = TRUE;
+}
+
+
+
+static void
+validateTransparentIndex(unsigned int const transparentIndex,
+                         bool         const tolerateBadInput,
+                         unsigned int const cmapSize,
+                         unsigned int const maxDataVal) {
+
+    if (transparentIndex >= cmapSize) {
+        if (tolerateBadInput) {
+            if (transparentIndex > maxDataVal)
+                pm_error("Invalid transparent index value: %d",
+                         transparentIndex);
+        } else {
+            pm_error("Invalid transparent index value %d in image with "
+                     "only %u colors.  %s",
+                     transparentIndex, cmapSize,
+                     transparentIndex <= maxDataVal ?
+                     "" :
+                     "Use the -repair option to try to render the "
+                     "image overriding this error.");
+        }
+    }
 }
 
 
@@ -813,46 +935,78 @@ resetDecompressor(struct decompressor * const decompP) {
 static void
 lzwInit(struct decompressor * const decompP, 
         FILE *                const ifP,
-        int                   const init_codeSize) {
+        int                   const initCodeSize,
+        unsigned int          const cmapSize,
+        bool                  const haveTransColor,
+        unsigned int          const transparentIndex,
+        bool                  const tolerateBadInput) {
 
+    unsigned int const maxDataVal = (1 << initCodeSize) - 1;
+    
     if (verbose)
         pm_message("Image says the initial compression code size is "
                    "%d bits", 
-                   init_codeSize);
+                   initCodeSize);
     
-    decompP->ifP = ifP;
-    decompP->init_codeSize = init_codeSize;
-
-    assert(decompP->init_codeSize < sizeof(decompP->max_dataVal) * 8);
-
-    decompP->max_dataVal = (1 << init_codeSize) - 1;
-    decompP->clear_code = decompP->max_dataVal + 1;
-    decompP->end_code = decompP->max_dataVal + 2;
+    decompP->ifP              = ifP;
+    decompP->initCodeSize     = initCodeSize;
+    decompP->cmapSize         = cmapSize;
+    decompP->tolerateBadInput = tolerateBadInput;
+    decompP->maxDataVal       = maxDataVal;
+    decompP->clearCode        = maxDataVal + 1;
+    decompP->endCode          = maxDataVal + 2;
 
     if (verbose)
-        pm_message("Initial code size is %u bits; clear code = 0x%x, "
-                   "end code = 0x%x",
-                   decompP->init_codeSize, 
-                   decompP->clear_code, decompP->end_code);
+        pm_message("Initial code size is %u bits; clear code = 0x%03x, "
+                   "end code = 0x%03x",
+                   decompP->initCodeSize, 
+                   decompP->clearCode, decompP->endCode);
     
     /* The entries in the translation table for true data codes are
-       constant throughout the stream.  We set them now and they never
-       change.
+       constant throughout the image.  For PBM output we make an
+       adjustment later.  Once set entries never change.
     */
     {
         unsigned int i;
-        for (i = 0; i <= decompP->max_dataVal; ++i) {
-            decompP->table[0][i] = 0;
-            decompP->table[1][i] = i;
+        for (i = 0; i <= maxDataVal; ++i) {
+            decompP->table[i][0] = 0;
+            decompP->table[i][1] = i < cmapSize ? i : 0;
         }
     }
+    decompP->haveTransColor   = haveTransColor;
+    decompP->transparentIndex = transparentIndex;
+
+    if (haveTransColor)
+        validateTransparentIndex(transparentIndex, tolerateBadInput,
+                                 cmapSize, maxDataVal);
+
     resetDecompressor(decompP);
 
     getCode_init(&getCodeState);
     
     decompP->fresh = TRUE;
     
-    initStack(&decompP->stack, maxnum_lzwCode * 2);
+    initStack(&decompP->stack, maxLzwCodeCt);
+
+    assert(decompP->initCodeSize < sizeof(decompP->maxDataVal) * 8);
+}
+
+
+
+static void
+lzwAdjustForPBM(struct decompressor * const decompP,
+                GifColorMap           const cmap) {
+/*----------------------------------------------------------------------------
+  In the PBM case we use the table index value directly instead of looking up
+  the color map for each pixel.
+
+  Note that cmap.size is not always 2.
+
+  Similar logic should work for PGM.
+----------------------------------------------------------------------------*/
+    unsigned int i;
+    for (i = 0; i < cmap.size; ++i)
+        decompP->table[i][1] = cmap.map[i][0] == 0 ? PBM_BLACK : PBM_WHITE;
 }
 
 
@@ -866,8 +1020,32 @@ lzwTerm(struct decompressor * const decompP) {
 
 
 static void
+pushWholeStringOnStack(struct decompressor * const decompP,
+                       unsigned int          const code0) {
+/*----------------------------------------------------------------------------
+  Get the whole string that compression code 'code0' represents and push
+  it onto the code stack so the leftmost code is on top.  Set
+  decompP->firstcode to the first (leftmost) code in that string.
+-----------------------------------------------------------------------------*/
+    unsigned int code;
+    unsigned int stringCount;
+
+    for (stringCount = 0, code = code0;
+         code > decompP->maxDataVal;
+         ++stringCount, code = decompP->table[code][0]
+        ) {
+
+        pushStack(&decompP->stack, decompP->table[code][1]);
+    }
+    decompP->firstcode = decompP->table[code][1];
+    pushStack(&decompP->stack, decompP->firstcode);
+}
+
+
+
+static void
 expandCodeOntoStack(struct decompressor * const decompP,
-                    int                   const incode,
+                    unsigned int          const incode,
                     const char **         const errorP) {
 /*----------------------------------------------------------------------------
    'incode' is an LZW string code.  It represents a string of true data
@@ -882,64 +1060,60 @@ expandCodeOntoStack(struct decompressor * const decompP,
    from which it was built is invalid), fail (return text explanation
    as *errorP).
 -----------------------------------------------------------------------------*/
-    int code;
-    const char * error;
-
-    error = NULL; /* Initial value */
-
-    if (incode < decompP->next_tableSlot) 
-        code = incode;
-    else {
-        /* It's a code that isn't in our translation table yet */
-        pushStack(&decompP->stack, decompP->firstcode);
-        code = decompP->prevcode;
+    unsigned int code;
+
+    *errorP = NULL; /* Initial value */
+
+    if (incode <= decompP->maxDataVal) {
+        if (incode < decompP->cmapSize)
+            code = incode;      /* Direct index */
+        else if (decompP->tolerateBadInput &&
+                 decompP->haveTransColor &&
+                 decompP->table[incode][1] == decompP->transparentIndex)
+            /* transparent code outside cmap   exceptional case */
+            code = incode;
+        else
+            pm_asprintf(errorP, "Error in GIF image: invalid color code %u. "
+                        "Valid color values are 0 - %u",
+                        incode, decompP->cmapSize - 1);
     }
-
-    {
-        /* Get the whole string that this compression code
-           represents and push it onto the code stack so the
-           leftmost code is on top.  Set decompP->firstcode to the
-           first (leftmost) code in that string.
+    else if (incode < decompP->nextTableSlot)  
+        /* LZW string, defined */
+        code = incode;
+    else if  (incode == decompP->nextTableSlot && !decompP->fresh) {
+        /* It's a code that isn't in our translation table yet.
+           This does not happen with the decoder in a fresh state.
         */
+        if (wantLzwCodes && verbose)
+            pm_message ("LZW code valid, but not in decoder table");
 
-        unsigned int stringCount;
-        stringCount = 0;
-
-        while (code > decompP->max_dataVal && !error) {
-            if (stringCount > maxnum_lzwCode) {
-                pm_asprintf(&error,
-                            "Error in GIF image: contains LZW string loop");
-            } else {
-                ++stringCount;
-                pushStack(&decompP->stack, decompP->table[1][code]);
-                code = decompP->table[0][code];
-            }
-        }
-        decompP->firstcode = decompP->table[1][code];
         pushStack(&decompP->stack, decompP->firstcode);
-    }
+        code = decompP->prevcode;
+    } else
+        pm_asprintf(errorP, "Error in GIF image: invalid LZW code");
 
-    if (decompP->next_tableSlot < maxnum_lzwCode) {
-        decompP->table[0][decompP->next_tableSlot] = decompP->prevcode;
-        decompP->table[1][decompP->next_tableSlot] = decompP->firstcode;
-        ++decompP->next_tableSlot;
-        if (decompP->next_tableSlot >= decompP->maxnum_code) {
-            /* We've used up all the codes of the current code size.
-               Future codes in the stream will have codes one bit longer.
-               But there's an exception if we're already at the LZW
-               maximum, in which case the codes will simply continue
-               the same size.
-            */
-            if (decompP->codeSize < MAX_LZW_BITS) {
-                ++decompP->codeSize;
-                decompP->maxnum_code = 1 << decompP->codeSize;
+    if (!*errorP) {
+        pushWholeStringOnStack(decompP, code);
+
+        if (decompP->nextTableSlot < maxLzwCodeCt) {
+            decompP->table[decompP->nextTableSlot][0] = decompP->prevcode;
+            decompP->table[decompP->nextTableSlot][1] = decompP->firstcode;
+            ++decompP->nextTableSlot;
+            if (decompP->nextTableSlot >= decompP->maxCodeCt) {
+                /* We've used up all the codes of the current code size.
+                   Future codes in the stream will have codes one bit longer.
+                   But there's an exception if we're already at the LZW
+                   maximum, in which case the codes will simply continue
+                   the same size.
+                */
+                if (decompP->codeSize < MAX_LZW_BITS) {
+                    ++decompP->codeSize;
+                    decompP->maxCodeCt = 1 << decompP->codeSize;
+                }
             }
         }
+        decompP->prevcode = incode;
     }
-
-    *errorP = error;
-
-    decompP->prevcode = incode;
 }
 
 
@@ -948,39 +1122,62 @@ static void
 lzwReadByteFresh(struct getCodeState * const getCodeStateP,
                  struct decompressor * const decompP,
                  bool *                const endOfImageP,
-                 unsigned int *        const dataReadP,
+                 unsigned char *       const dataReadP,
                  const char **         const errorP) {
-                     
-    /* Read off all initial clear codes, read the first non-clear code,
-       and return it.  There are no strings in the table yet, so the next
-       code must be a direct true data code.
-    */
+/*----------------------------------------------------------------------------
+  Read off all initial clear codes, read the first non-clear code, and return
+  it as *dataReadP.
+
+  Iff we hit end of image in so doing, return *endOfImageP true.
+
+  Assume the decompressor is fresh, i.e. there are no strings in the table
+  yet, so the next code must be a direct true data code.
+-----------------------------------------------------------------------------*/
+    unsigned int code;
     bool eof;
+
+    assert(decompP->fresh);  /* Entry requirement */
+
+    decompP->fresh = FALSE;
+
     do {
         getCode_get(getCodeStateP, decompP->ifP, decompP->codeSize,
-                    &eof, &decompP->firstcode, errorP);
-        decompP->prevcode = decompP->firstcode;
-    } while (decompP->firstcode == decompP->clear_code && !*errorP && !eof);
+                    &eof, &code, errorP);
+    } while (!*errorP && !eof && code == decompP->clearCode);
 
     if (!*errorP) {
         if (eof)
             *endOfImageP = TRUE;
-        else if (decompP->firstcode == decompP->end_code) {
+        else if (code == decompP->endCode) {
             if (!zeroDataBlock)
                 readThroughEod(decompP->ifP);
             *endOfImageP = TRUE;
-        } else {
+        } else if(code >= decompP->cmapSize) { 
+            pm_asprintf(errorP, "Error in GIF image: invalid color code %u. "
+                        "Valid color values are: 0 - %u",
+                        code, decompP->cmapSize-1);
+            /* Set these values in order to avoid errors in the -repair
+               case
+            */
+            decompP->prevcode = decompP->firstcode = 0;
+
             *endOfImageP = FALSE;
+        }
+        else {    /* valid code */
+            decompP->prevcode  = code;
+            decompP->firstcode = decompP->table[code][1];
             *dataReadP = decompP->firstcode;
+            *endOfImageP = FALSE;
         }
     }
 }
 
 
 
+
 static void
 lzwReadByte(struct decompressor * const decompP,
-            unsigned int *        const dataReadP,
+            unsigned char *       const dataReadP,
             bool *                const endOfImageP,
             const char **         const errorP) {
 /*----------------------------------------------------------------------------
@@ -1004,8 +1201,6 @@ lzwReadByte(struct decompressor * const decompP,
         *endOfImageP = FALSE;
         *dataReadP = popStack(&decompP->stack);
     } else if (decompP->fresh) {
-        decompP->fresh = FALSE;
-
         lzwReadByteFresh(&getCodeState, decompP, endOfImageP, dataReadP,
                          errorP);
     } else {
@@ -1018,11 +1213,12 @@ lzwReadByte(struct decompressor * const decompP,
                 pm_asprintf(errorP,
                             "Premature end of file; no proper GIF closing");
             else {
-                if (code == decompP->clear_code) {
+                if (code == decompP->clearCode) {
                     resetDecompressor(decompP);
-                    lzwReadByte(decompP, dataReadP, endOfImageP, errorP);
+                    lzwReadByteFresh(&getCodeState, decompP, endOfImageP,
+                    dataReadP, errorP);
                 } else {
-                    if (code == decompP->end_code) {
+                    if (code == decompP->endCode) {
                         if (!zeroDataBlock)
                             readThroughEod(decompP->ifP);
                         *endOfImageP = TRUE;
@@ -1043,8 +1239,8 @@ lzwReadByte(struct decompressor * const decompP,
 enum pass {MULT8PLUS0, MULT8PLUS4, MULT4PLUS2, MULT2PLUS1};
 
 static void
-bumpRowInterlace(unsigned int * const rowP,
-                 unsigned int   const rows,
+bumpRowInterlace(unsigned int   const rows,
+                 unsigned int * const rowP,
                  enum pass *    const passP) {
 /*----------------------------------------------------------------------------
    Move *pixelCursorP to the next row in the interlace pattern.
@@ -1097,47 +1293,57 @@ bumpRowInterlace(unsigned int * const rowP,
 }
 
 
+static void
+renderRow(unsigned char *    const cmapIndexRow,
+          unsigned int       const cols,
+          GifColorMap        const cmap, 
+          bool               const haveTransColor,
+          unsigned int       const transparentIndex,
+          FILE *             const imageOutfile,
+          int                const format,
+          xel *              const xelrow,
+          FILE *             const alphaFileP,
+          bit *              const alphabits) {
+/*----------------------------------------------------------------------------
+  Convert one row of cmap indexes to PPM/PGM/PBM output.
+
+  Render the alpha row to *alphaFileP iff 'alphabits' is non-NULL.  If
+  'haveTransColor' is false, render all zeroes (i.e. the row is
+  opaque).  'alphabits' is otherwise just a one-row buffer for us to use
+  in rendering the alpha row.
+  
+  imageOutfile is NULL if user wants only the alpha file.
+----------------------------------------------------------------------------*/
+    if (alphabits) {
+        unsigned int col;
+
+        for (col=0; col < cols; ++col) {
+            alphabits[col] =
+                (haveTransColor && cmapIndexRow[col] == transparentIndex) ?
+                PBM_BLACK : PBM_WHITE;
+        }
+        pbm_writepbmrow(alphaFileP, alphabits, cols, false);
+    }
+
+    if (imageOutfile) {
+        if (useFastPbmRender && format == PBM_FORMAT && !haveTransColor) {
 
-struct pnmBuffer {
-    xel ** xels;
-    unsigned int col;
-    unsigned int row;
-};
+            bit * const bitrow = cmapIndexRow; 
 
-static void
-addPixelToRaster(unsigned int       const cmapIndex,
-                 struct pnmBuffer * const pnmBufferP,
-                 unsigned int       const cols,
-                 unsigned int       const rows,
-                 gifColorMap              cmap, 
-                 unsigned int       const cmapSize,
-                 bool               const interlace,
-                 int                const transparentIndex,
-                 bit **             const alphabits,
-                 enum pass *        const passP) {
-
-    if (cmapIndex >= cmapSize)
-        pm_error("Invalid color index %u in an image that has only "
-                 "%u colors in the color map.", cmapIndex, cmapSize);
-    
-    assert(cmapIndex < MAXCOLORMAPSIZE);
-    
-    PPM_ASSIGN(pnmBufferP->xels[pnmBufferP->row][pnmBufferP->col], 
-               cmap[CM_RED][cmapIndex],
-               cmap[CM_GRN][cmapIndex],
-               cmap[CM_BLU][cmapIndex]);
-    
-    if (alphabits) 
-        alphabits[pnmBufferP->row][pnmBufferP->col] =
-            (cmapIndex == transparentIndex) ? PBM_BLACK : PBM_WHITE;
-    
-    ++pnmBufferP->col;
-    if (pnmBufferP->col == cols) {
-        pnmBufferP->col = 0;
-        if (interlace)
-            bumpRowInterlace(&pnmBufferP->row, rows, passP);
-        else
-            ++pnmBufferP->row;
+            pbm_writepbmrow(imageOutfile, bitrow, cols, false);
+        } else {
+            /* PPM, PGM and PBM with transparent */
+            unsigned int col;
+            for (col = 0; col < cols; ++col) {
+                unsigned char const cmapIndex = cmapIndexRow[col];
+                const unsigned char * const color = cmap.map[cmapIndex];
+                assert(cmapIndex < cmap.size);
+                PPM_ASSIGN(xelrow[col],
+                           color[CM_RED], color[CM_GRN],color[CM_BLU]);
+            }
+            pnm_writepnmrow(imageOutfile, xelrow, cols,
+                            GIFMAXVAL, format, false);
+        }
     }
 }
 
@@ -1148,19 +1354,18 @@ verifyPixelRead(bool          const endOfImage,
                 const char *  const readError,
                 unsigned int  const cols,
                 unsigned int  const rows,
-                unsigned int  const failedRowNum,
                 const char ** const errorP) {
 
     if (readError)
-        *errorP = strdup(readError);
+        *errorP = pm_strdup(readError);
     else {
         if (endOfImage)
             pm_asprintf(errorP,
                         "Error in GIF image: Not enough raster data to fill "
-                        "%u x %u dimensions.  Ran out of raster data in "
-                        "row %u.  The image has proper ending sequence, so "
+                        "%u x %u dimensions.  "
+                        "The image has proper ending sequence, so "
                         "this is not just a truncated file.",
-                        cols, rows, failedRowNum);
+                        cols, rows);
         else
             *errorP = NULL;
     }
@@ -1168,93 +1373,214 @@ verifyPixelRead(bool          const endOfImage,
 
 
 
+static int
+pnmFormat(bool const hasGray,
+          bool const hasColor) {
+/*----------------------------------------------------------------------------
+  The proper PNM format (PBM, PGM, or PPM) for an image described
+  by 'hasGray' and 'hasColor'.
+-----------------------------------------------------------------------------*/
+    int format;
+    const char * formatName;
+           
+    if (hasColor) {
+        format = PPM_FORMAT;
+        formatName = "PPM";
+    } else if (hasGray) {
+        format = PGM_FORMAT;
+        formatName = "PGM";
+    } else {
+        format = PBM_FORMAT;
+        formatName = "PBM";
+    }
+    if (verbose) 
+        pm_message("writing a %s file", formatName);
+ 
+    return format;
+}
+
+
+
 static void
-readRaster(struct decompressor * const decompP,
-           xel **                const xels, 
+makePnmRow(struct decompressor * const decompP,
            unsigned int          const cols,
            unsigned int          const rows,
-           gifColorMap                 cmap, 
-           unsigned int          const cmapSize,
-           bool                  const interlace,
-           int                   const transparentIndex,
-           bit **                const alphabits,
-           bool                  const tolerateBadInput) {
-                   
-    struct pnmBuffer pnmBuffer;
-    enum pass pass;
-    bool fillingMissingPixels;
+           bool                  const fillWithZero,
+           unsigned char *       const cmapIndexRow,
+           const char **         const errorP) {
 
-    pass = MULT8PLUS0;
-    pnmBuffer.xels = xels;
-    pnmBuffer.col  = 0;
-    pnmBuffer.row  = 0;
-    fillingMissingPixels = false;  /* initial value */
+    bool fillingWithZero;
+    unsigned int col;
+
+    *errorP = NULL;  /* initial value */
 
-    while (pnmBuffer.row < rows) {
-        unsigned int colorIndex;
+    for (col = 0, fillingWithZero = fillWithZero;
+         col < cols;
+         ++col) {
 
-        if (fillingMissingPixels)
-            colorIndex = 0;
-        else {
-            const char * error;
+        unsigned char colorIndex;
 
-            const char * readError;
-            unsigned int readColorIndex;
-            bool endOfImage;
+        if (fillingWithZero)
+            colorIndex = 0;
+        else { 
+            const char *  readError;
+            unsigned char readColorIndex;
+            bool          endOfImage;
 
             lzwReadByte(decompP, &readColorIndex, &endOfImage, &readError);
 
-            verifyPixelRead(endOfImage, readError, cols, rows, pnmBuffer.row,
-                            &error);
+            assert(*errorP == NULL);
+
+            verifyPixelRead(endOfImage, readError, cols, rows, errorP);
 
             if (readError)
                 pm_strfree(readError);
 
-            if (error) {
-                if (tolerateBadInput) {
-                    pm_message("WARNING: %s.  "
-                               "Filling bottom %u rows with arbitrary color",
-                               error, rows - pnmBuffer.row);
-                    fillingMissingPixels = true;
-                } else
-                    pm_error("Unable to read input image.  %s.  Use the "
-                             "-repair option to try to salvage some of "
-                             "the image",
-                             error);
-
+            if (*errorP) {
+                /* Caller may want to try to ignore this error, so we
+                   fill out the row with zeroes.  Note that we can't possibly
+                   have another error while doing that.
+                */
+                fillingWithZero = true;
                 colorIndex = 0;
             } else
                 colorIndex = readColorIndex;
         }
-        addPixelToRaster(colorIndex, &pnmBuffer, cols, rows, cmap, cmapSize,
-                         interlace, transparentIndex, alphabits, &pass);
+        cmapIndexRow[col] = colorIndex;
     }
 }
 
 
 
 static void
+convertRaster(struct decompressor * const decompP,
+              unsigned int          const cols,
+              unsigned int          const rows,
+              GifColorMap           const cmap, 
+              bool                  const interlace,
+              FILE *                const imageOutFileP,
+              FILE *                const alphaFileP,
+              bool                  const hasGray,
+              bool                  const hasColor) {
+/*----------------------------------------------------------------------------
+   Read the raster from the GIF decompressor *decompP, and write it as a
+   complete PNM stream (starting with the header) on *imageOutFileP and
+   *alphaFileP.
+
+   Assume that raster is 'cols' x 'rows', refers to colormap 'cmap', and is
+   interlaced iff 'interlace' is true.
+
+   Assume the image has gray levels and/or color per 'hasGray' and 'hasColor'.
+-----------------------------------------------------------------------------*/
+    int const format = pnmFormat(hasGray, hasColor);
+
+    enum pass pass;
+    bool fillingMissingPixels;
+    unsigned int row;
+    unsigned char ** cmapIndexArray;
+    bit * alphabits;
+    xel * xelrow;
+    unsigned int outrow;
+        /* Non-interlace: outrow is always 0: cmapIndexRow keeps pointing
+           to the single row in array.
+
+           Interlace: outrow is modified with each call to bumpRowInterface().
+        */
+
+    MALLOCARRAY2(cmapIndexArray, interlace ? rows : 1 , cols);
+
+    if (imageOutFileP)
+        pnm_writepnminit(imageOutFileP, cols, rows, GIFMAXVAL, format, FALSE);
+    if (alphaFileP)
+        pbm_writepbminit(alphaFileP, cols, rows, FALSE);
+
+    xelrow = pnm_allocrow(cols);  
+    if (!xelrow)
+        pm_error("couldn't alloc space for image" );
+
+    if (alphaFileP) {
+        alphabits = pbm_allocrow(cols);
+        if (!alphabits)
+            pm_error("couldn't alloc space for alpha image" );
+    } else
+        alphabits = NULL;
+
+    fillingMissingPixels = false;  /* initial value */
+    pass = MULT8PLUS0;
+    outrow = 0;
+
+    for (row = 0; row < rows; ++row) {
+        const char * problem;
+        makePnmRow(decompP, cols, rows, fillingMissingPixels,
+                   cmapIndexArray[outrow], &problem);
+
+        if (problem) {
+            /* makePnmRow() recovered from the problem and produced an output
+               row, stuffed with zeroes as necessary
+            */
+            if (decompP->tolerateBadInput) {
+                pm_message("WARNING: %s.  "
+                           "Filling bottom %u rows with arbitrary color",
+                           problem, rows - row);
+                fillingMissingPixels = true;
+            } else
+                pm_error("Unable to read input image.  %s "
+                         "(Output row: %u).  "
+                         "Use the -repair option to try to salvage "
+                         "some of the image",
+                         problem, interlace ? outrow : row);
+        }
+
+        if (interlace)
+            bumpRowInterlace(rows, &outrow, &pass);
+        else
+            renderRow(cmapIndexArray[outrow], cols, cmap,
+                      decompP->haveTransColor, decompP->transparentIndex,
+                      imageOutFileP, format, xelrow, alphaFileP, alphabits);
+    }
+    /* All rows decompressed (and rendered and output if non-interlaced) */  
+    if (interlace) {
+        unsigned int row;
+        for (row = 0; row < rows; ++row) 
+            renderRow(cmapIndexArray[row], cols, cmap,
+                      decompP->haveTransColor, decompP->transparentIndex,
+                      imageOutFileP, format, xelrow, alphaFileP, alphabits);
+    }
+
+    pnm_freerow(xelrow);
+    if (alphabits)
+        pbm_freerow(alphabits);
+    pm_freearray2((void **)cmapIndexArray);
+}
+
+
+
+static void
 skipExtraneousData(struct decompressor * const decompP) {
 
-    unsigned int byteRead;
+    unsigned char byteRead;
     bool endOfImage;
     const char * error;
 
+    endOfImage = FALSE;  /* initial value */
+
     lzwReadByte(decompP, &byteRead, &endOfImage, &error);
 
     if (error)
         pm_strfree(error);
-    else if (!endOfImage) {
-        pm_message("Extraneous data at end of image.  "
-                   "Skipped to end of image");
+    else {
+        if (!endOfImage) {
+            pm_message("Extraneous data at end of image.  "
+                       "Skipped to end of image");
 
-        while (!endOfImage && !error)
-            lzwReadByte(decompP, &byteRead, &endOfImage, &error);
+            while (!endOfImage && !error)
+                lzwReadByte(decompP, &byteRead, &endOfImage, &error);
 
-        if (error) {
-            pm_message("Error encountered skipping to end of image: %s",
-                       error);
-            pm_strfree(error);
+            if (error) {
+                pm_message("Error encountered skipping to end of image: %s",
+                           error);
+                pm_strfree(error);
+            }
         }
     }
 }
@@ -1262,22 +1588,66 @@ skipExtraneousData(struct decompressor * const decompP) {
 
 
 static void
+issueTransparencyMessage(bool         const haveTransColor,
+                         unsigned int const transparentIndex, 
+                         GifColorMap  const cmap) {
+/*----------------------------------------------------------------------------
+   If user wants verbose output, tell him whether there is a transparent
+   background color ('haveTransColor') and if so what it is
+   ('transparentIndex').
+   
+   Some GIFs put transparentIndex outside the color map.  Allow this only
+   with "-repair", checked in lzwInit().  Here we issue a warning and report
+   the substitute color.
+-----------------------------------------------------------------------------*/
+    if (verbose) {
+        if (haveTransColor) {
+            if (transparentIndex >= cmap.size) {
+                const unsigned char * const color = cmap.map[0];
+                pm_message("WARNING: Transparent index %u "
+                           "is outside color map. "
+                           "substitute background color: rgb:%02x/%02x/%02x ",
+                           transparentIndex,
+                           color[CM_RED],
+                           color[CM_GRN],
+                           color[CM_BLU]
+                    );
+            } else {
+                const unsigned char * const color = cmap.map[transparentIndex];
+                pm_message("transparent background color: rgb:%02x/%02x/%02x "
+                           "Index %u",
+                           color[CM_RED],
+                           color[CM_GRN],
+                           color[CM_BLU],
+                           transparentIndex
+                    );
+            }
+        } else
+            pm_message("no transparency");
+    }
+}
+
+
+
+static void
 readImageData(FILE *       const ifP, 
-              xel **       const xels, 
               unsigned int const cols,
               unsigned int const rows,
-              gifColorMap        cmap, 
-              unsigned int const cmapSize,
+              GifColorMap  const cmap, 
               bool         const interlace,
-              int          const transparentIndex,
-              bit **       const alphabits,
+              bool         const haveTransColor,
+              unsigned int const transparentIndex,
+              FILE *       const imageOutFileP,
+              FILE *       const alphaFileP,
+              bool         const hasGray,
+              bool         const hasColor,
               bool         const tolerateBadInput) {
 
     unsigned char lzwMinCodeSize;      
     struct decompressor decomp;
     bool gotMinCodeSize;
 
-    gotMinCodeSize =  ReadOK(ifP, &lzwMinCodeSize, 1);
+    gotMinCodeSize = ReadOK(ifP, &lzwMinCodeSize, 1);
     if (!gotMinCodeSize)
         pm_error("GIF stream ends (or read error) "
                  "right after an image separator; no "
@@ -1288,10 +1658,19 @@ readImageData(FILE *       const ifP,
                  "Maximum allowable code size in GIF is %u", 
                  lzwMinCodeSize, MAX_LZW_BITS);
 
-    lzwInit(&decomp, ifP, lzwMinCodeSize);
+    lzwInit(&decomp, ifP, lzwMinCodeSize, cmap.size,
+            haveTransColor, transparentIndex, tolerateBadInput);
 
-    readRaster(&decomp, xels, cols, rows, cmap, cmapSize, interlace,
-               transparentIndex, alphabits, tolerateBadInput);
+    issueTransparencyMessage(haveTransColor, transparentIndex, cmap);
+
+    if (useFastPbmRender && !hasGray && ! hasColor && !haveTransColor) {
+        if (verbose)
+            pm_message("Using fast PBM rendering");
+        lzwAdjustForPBM(&decomp, cmap);
+    }
+    convertRaster(&decomp, cols, rows, cmap, interlace,
+                  imageOutFileP, alphaFileP,
+                  hasGray, hasColor);
 
     skipExtraneousData(&decomp);
 
@@ -1301,80 +1680,21 @@ readImageData(FILE *       const ifP,
 
 
 static void
-writePnm(FILE * const outfileP,
-         xel ** const xels, 
-         int    const cols,
-         int    const rows,
-         int    const hasGray,
-         int    const hasColor) {
+readGifHeader(FILE *             const gifFileP,
+              struct gifScreen * const gifScreenP) {
 /*----------------------------------------------------------------------------
-   Write a PNM image to the current position of file *outfileP with
-   dimensions 'cols' x 'rows' and raster 'xels'.
-   
-   Make it PBM, PGM, or PBM according to 'hasGray' and 'hasColor'.
------------------------------------------------------------------------------*/
-    int format;
-    const char * formatName;
-           
-    if (hasColor) {
-        format = PPM_FORMAT;
-        formatName = "PPM";
-    } else if (hasGray) {
-        format = PGM_FORMAT;
-        formatName = "PGM";
-    } else {
-        format = PBM_FORMAT;
-        formatName = "PBM";
-    }
-    if (verbose) 
-        pm_message("writing a %s file", formatName);
-    
-    if (outfileP) 
-        pnm_writepnm(outfileP, xels, cols, rows,
-                     (xelval) GIFMAXVAL, format, FALSE);
-}
-
-
-
-static void
-transparencyMessage(int const transparentIndex, 
-                    gifColorMap cmap) {
-/*----------------------------------------------------------------------------
-   If user wants verbose output, tell him that the color with index
-   'transparentIndex' is supposed to be a transparent background color.
-   
-   If transparentIndex == -1, tell him there is no transparent background
-   color.
------------------------------------------------------------------------------*/
-    if (verbose) {
-        if (transparentIndex == -1)
-            pm_message("no transparency");
-        else
-            pm_message("transparent background color: rgb:%02x/%02x/%02x "
-                       "Index %d",
-                       cmap[CM_RED][transparentIndex],
-                       cmap[CM_GRN][transparentIndex],
-                       cmap[CM_BLU][transparentIndex],
-                       transparentIndex
-                );
-    }
-}
-
-static void
-readGifHeader(FILE * const gifFile, struct gifScreen * const gifScreenP) {
-/*----------------------------------------------------------------------------
-   Read the GIF stream header off the file gifFile, which is present
+   Read the GIF stream header off the file *gifFileP, which is present
    positioned to the beginning of a GIF stream.  Return the info from it
    as *gifScreenP.
 -----------------------------------------------------------------------------*/
-    unsigned char   buf[16];
-    char     version[4];
-
+    unsigned char buf[16];
+    char version[4];
+    unsigned int cmapSize;
 
-    if (! ReadOK(gifFile,buf,6))
+    if (!ReadOK(gifFileP, buf, 6))
         pm_error("error reading magic number" );
     
-    if (strncmp((char *)buf,"GIF",3) != 0)
+    if (!strneq((char *)buf, "GIF", 3))
         pm_error("File does not contain a GIF stream.  It does not start "
                  "with 'GIF'.");
     
@@ -1387,12 +1707,12 @@ readGifHeader(FILE * const gifFile, struct gifScreen * const gifScreenP) {
     if ((!streq(version, "87a")) && (!streq(version, "89a")))
         pm_error("bad version number, not '87a' or '89a'" );
     
-    if (! ReadOK(gifFile,buf,7))
+    if (!ReadOK(gifFileP, buf, 7))
         pm_error("failed to read screen descriptor" );
     
     gifScreenP->Width           = LM_to_uint(buf[0],buf[1]);
     gifScreenP->Height          = LM_to_uint(buf[2],buf[3]);
-    gifScreenP->ColorMapSize    = 1 << ((buf[4] & 0x07) + 1);
+    cmapSize                    = 1 << ((buf[4] & 0x07) + 1);
     gifScreenP->ColorResolution = (buf[4] & 0x70 >> 3) + 1;
     gifScreenP->Background      = buf[5];
     gifScreenP->AspectRatio     = buf[6];
@@ -1405,10 +1725,10 @@ readGifHeader(FILE * const gifFile, struct gifScreen * const gifScreenP) {
                    gifScreenP->AspectRatio == 0 ? 
                    1 : (gifScreenP->AspectRatio + 15) / 64.0);
         pm_message("Colors = %d   Color Resolution = %d",
-                   gifScreenP->ColorMapSize, gifScreenP->ColorResolution);
+                   cmapSize, gifScreenP->ColorResolution);
     }           
     if (BitSet(buf[4], LOCALCOLORMAP)) {    /* Global Colormap */
-        readColorMap(gifFile, gifScreenP->ColorMapSize, gifScreenP->ColorMap,
+        readColorMap(gifFileP, cmapSize, &gifScreenP->ColorMap,
                      &gifScreenP->hasGray, &gifScreenP->hasColor);
         if (verbose) {
             pm_message("Color map %s grays, %s colors", 
@@ -1487,7 +1807,8 @@ readExtensions(FILE*          const ifP,
             } else if (c == ',') 
                 imageStart = TRUE;
             else 
-                pm_message("bogus character 0x%02x, ignoring", (int)c);
+                pm_message("Encountered invalid character 0x%02x while "
+                           "seeking extension block, ignoring", (int)c);
         }
     }
     *eodP = eod;
@@ -1495,20 +1816,94 @@ readExtensions(FILE*          const ifP,
 
 
 
+struct GifImageHeader {
+/*----------------------------------------------------------------------------
+   Information in the header (first 9 bytes) of a GIF image.
+-----------------------------------------------------------------------------*/
+    bool useGlobalColormap;
+
+    /* Position of the image (max 65535) */
+    unsigned int lpos;
+    unsigned int tpos;
+
+    /* Dimensions of the image (max 65535) */
+    unsigned int cols;
+    unsigned int rows;
+    unsigned int localColorMapSize;
+    bool interlaced;
+};
+
+
+
 static void
-reportImageInfo(unsigned int const cols,
-                unsigned int const rows,
-                bool         const useGlobalColormap,
-                unsigned int const localColorMapSize,
-                bool         const interlaced) {
+reportImageHeader(struct GifImageHeader const imageHeader) {
 
     pm_message("reading %u by %u%s GIF image",
-               cols, rows, interlaced ? " interlaced" : "" );
+               imageHeader.cols, imageHeader.rows,
+               imageHeader.interlaced ? " interlaced" : "" );
 
-    if (useGlobalColormap)
+    if (imageHeader.lpos > 0 || imageHeader.tpos > 0)
+        pm_message("  Image left position: %u top position: %u",
+                   imageHeader.lpos, imageHeader.tpos);
+    
+    if (imageHeader.useGlobalColormap)
         pm_message("  Uses global colormap");
     else
-        pm_message("  Uses local colormap of %u colors", localColorMapSize);
+        pm_message("  Uses local colormap of %u colors",
+                   imageHeader.localColorMapSize);
+}
+
+
+
+static void
+readImageHeader(FILE *                  const ifP,
+                struct GifImageHeader * const imageHeaderP) {
+
+    unsigned char buf[16];
+
+    if (!ReadOK(ifP, buf, 9))
+        pm_error("couldn't read left/top/width/height");
+
+    imageHeaderP->useGlobalColormap = ! BitSet(buf[8], LOCALCOLORMAP);
+    imageHeaderP->localColorMapSize = 1u << ((buf[8] & 0x07) + 1);
+    imageHeaderP->lpos              = LM_to_uint(buf[0], buf[1]);
+    imageHeaderP->tpos              = LM_to_uint(buf[2], buf[3]);
+    imageHeaderP->cols              = LM_to_uint(buf[4], buf[5]);
+    imageHeaderP->rows              = LM_to_uint(buf[6], buf[7]);
+    imageHeaderP->interlaced        = !!BitSet(buf[8], INTERLACE);
+
+    if (verbose)
+        reportImageHeader(*imageHeaderP);
+}
+
+
+
+static void
+validateWithinGlobalScreen(struct GifImageHeader const imageHeader,
+                           struct gifScreen      const gifScreen) {
+
+    unsigned long int const rpos = imageHeader.lpos + imageHeader.cols;
+    unsigned long int const bpos = imageHeader.tpos + imageHeader.rows; 
+
+    if (rpos > gifScreen.Width)
+        pm_error("Image right end (%lu) is outside global screen: %u x %u",
+                 rpos, gifScreen.Width, gifScreen.Height);
+    if (bpos > gifScreen.Height)
+        pm_error("Image bottom end (%lu) is outside global screen: "
+                 "%u x %u",
+                 bpos, gifScreen.Width, gifScreen.Height);
+}
+
+
+
+static void
+skipImageData(FILE * const ifP) {
+    unsigned char lzwMinCodeSize;
+
+    if (!ReadOK(ifP, &lzwMinCodeSize, 1))
+        pm_message("EOF or error while skipping image DataBlock" );
+
+    readThroughEod(ifP);
 }
 
 
@@ -1516,85 +1911,47 @@ reportImageInfo(unsigned int const cols,
 static void
 convertImage(FILE *           const ifP, 
              bool             const skipIt, 
-             FILE *           const imageout_file, 
-             FILE *           const alphafile, 
-             struct gifScreen       gifScreen,
+             FILE *           const imageoutFileP, 
+             FILE *           const alphafileP, 
+             struct gifScreen const gifScreen,
              struct gif89     const gif89,
              bool             const tolerateBadInput) {
 /*----------------------------------------------------------------------------
    Read a single GIF image from the current position of file 'ifP'.
 
    If 'skipIt' is TRUE, don't do anything else.  Otherwise, write the
-   image to the current position of files 'imageout_file' and 'alphafile'.
-   If 'alphafile' is NULL, though, don't write any alpha information.
+   image to the current position of files *imageoutFileP and *alphafileP.
+   If *alphafileP is NULL, though, don't write any alpha information.
 -----------------------------------------------------------------------------*/
-    unsigned char buf[16];
-    bool useGlobalColormap;
-    xel **xels;  /* The image raster, in libpnm format */
-    bit **alphabits;  
-        /* The image alpha mask, in libpbm format.  NULL if we aren't computing
-           an alpha mask.
-        */
-    unsigned int cols, rows;  /* Dimensions of the image */
-    gifColorMap localColorMap;
-    unsigned int localColorMapSize;
-    bool interlaced;
+    struct GifImageHeader imageHeader;
+    GifColorMap localColorMap;
+    const GifColorMap * currentColorMapP;
+    bool hasGray, hasColor;
 
-    if (! ReadOK(ifP,buf,9))
-        pm_error("couldn't read left/top/width/height");
+    readImageHeader(ifP, &imageHeader);
 
-    useGlobalColormap = ! BitSet(buf[8], LOCALCOLORMAP);
-    localColorMapSize = 1u << ((buf[8] & 0x07) + 1);
-    cols = LM_to_uint(buf[4], buf[5]);
-    rows = LM_to_uint(buf[6], buf[7]);
-    interlaced = !!BitSet(buf[8], INTERLACE);
+    validateWithinGlobalScreen(imageHeader, gifScreen);
 
-    if (verbose)
-        reportImageInfo(cols, rows, useGlobalColormap, localColorMapSize,
-                        interlaced);
-        
-    xels = pnm_allocarray(cols, rows);
-    if (!xels)
-        pm_error("couldn't alloc space for image" );
-
-    if (alphafile) {
-        alphabits = pbm_allocarray(cols, rows);
-        if (!alphabits)
-            pm_error("couldn't alloc space for alpha image" );
-    } else
-        alphabits = NULL;
-    
-    if (!useGlobalColormap) {
-        int hasGray, hasColor;
-
-        readColorMap(ifP, localColorMapSize, localColorMap, 
-                     &hasGray, &hasColor);
-        transparencyMessage(gif89.transparent, localColorMap);
-        readImageData(ifP, xels, cols, rows, localColorMap, localColorMapSize,
-                      interlaced, gif89.transparent, alphabits,
-                      tolerateBadInput);
-        if (!skipIt) {
-            writePnm(imageout_file, xels, cols, rows,
-                     hasGray, hasColor);
-        }
+    if (imageHeader.useGlobalColormap) {
+        currentColorMapP = &gifScreen.ColorMap;
+        hasGray  = gifScreen.hasGray;
+        hasColor = gifScreen.hasColor;
     } else {
-        transparencyMessage(gif89.transparent, gifScreen.ColorMap);
-        readImageData(ifP, xels, cols, rows, 
-                      gifScreen.ColorMap, gifScreen.ColorMapSize,
-                      interlaced, gif89.transparent, alphabits,
-                      tolerateBadInput);
-        if (!skipIt) {
-            writePnm(imageout_file, xels, cols, rows,
-                     gifScreen.hasGray, gifScreen.hasColor);
-        }
+        readColorMap(ifP, imageHeader.localColorMapSize, &localColorMap, 
+                     &hasGray, &hasColor);
+        currentColorMapP = &localColorMap;
     }
 
-    if (!skipIt && alphafile && alphabits)
-        pbm_writepbm(alphafile, alphabits, cols, rows, FALSE);
-
-    pnm_freearray(xels, rows);
-    if (alphabits)
-        pbm_freearray(alphabits, rows);
+    if (!skipIt) {
+        readImageData(ifP, imageHeader.cols, imageHeader.rows,
+                      *currentColorMapP,
+                      imageHeader.interlaced,
+                      gif89.haveTransColor, gif89.transparentIndex,
+                      imageoutFileP, alphafileP,
+                      hasGray, hasColor,
+                      tolerateBadInput);
+    } else
+        skipImageData(ifP);
 }
 
 
@@ -1620,17 +1977,17 @@ disposeOfReadExtensionsError(const char * const error,
 
 
 static void
-convertImages(FILE * const ifP, 
-              bool   const allImages,
-              int    const requestedImageSeq, 
-              bool   const drainStream,
-              FILE * const imageout_file, 
-              FILE * const alphafile,
-              bool   const tolerateBadInput) {
+convertImages(FILE *       const ifP, 
+              bool         const allImages,
+              unsigned int const requestedImageSeq, 
+              bool         const drainStream,
+              FILE *       const imageOutFileP, 
+              FILE *       const alphaFileP,
+              bool         const tolerateBadInput) {
 /*----------------------------------------------------------------------------
    Read a GIF stream from file 'ifP' and write one or more images from
-   it as PNM images to file 'imageout_file'.  If the images have transparency
-   and 'alphafile' is non-NULL, write PGM alpha masks to file 'alphafile'.
+   it as PNM images to file 'imageOutFileP'.  If the images have transparency
+   and 'alphafile' is non-NULL, write PGM alpha masks to file 'alphaFileP'.
 
    'allImages' means Caller wants all the images in the stream.  
 
@@ -1644,7 +2001,7 @@ convertImages(FILE * const ifP,
    format in the tail of the stream and there may yet be more stuff in
    the file when we return.
 -----------------------------------------------------------------------------*/
-    int imageSeq;
+    unsigned int imageSeq;
         /* Sequence within GIF stream of image we are currently processing.
            First is 0.
         */
@@ -1653,12 +2010,15 @@ convertImages(FILE * const ifP,
     bool eod;
         /* We've read through the GIF terminator character */
 
+    /* Set 'gif89' to initial values, to be updated as we encounter the
+       relevant extensions in the GIF stream.
+    */
     initGif89(&gif89);
 
     readGifHeader(ifP, &gifScreen);
 
     for (imageSeq = 0, eod = FALSE;
-         !eod && (imageSeq <= requestedImageSeq || allImages || drainStream);
+         !eod && (allImages || imageSeq <= requestedImageSeq || drainStream);
          ++imageSeq) {
 
         const char * error;
@@ -1676,9 +2036,10 @@ convertImages(FILE * const ifP,
                          imageSeq, imageSeq > 1 ? "s" : "");
         } else {
             if (verbose)
-                pm_message("Reading Image Sequence %d", imageSeq);
+                pm_message("Reading Image Sequence %u", imageSeq);
+
             convertImage(ifP, !allImages && (imageSeq != requestedImageSeq), 
-                         imageout_file, alphafile, gifScreen, gif89,
+                         imageOutFileP, alphaFileP, gifScreen, gif89,
                          tolerateBadInput);
         }
     }
@@ -1690,8 +2051,9 @@ int
 main(int argc, char **argv) {
 
     struct cmdlineInfo cmdline;
-    FILE *ifP;
-    FILE *alpha_file, *imageout_file;
+    FILE * ifP;
+    FILE * alphaFileP;
+    FILE * imageOutFileP;
 
     pnm_init(&argc, argv);
 
@@ -1699,27 +2061,30 @@ main(int argc, char **argv) {
     verbose = cmdline.verbose;
     showComment = cmdline.comments;
    
-    ifP = pm_openr(cmdline.input_filespec);
+    ifP = pm_openr(cmdline.inputFilespec);
 
-    if (cmdline.alpha_filename == NULL)
-        alpha_file = NULL;
+    if (cmdline.alphaFileName == NULL)
+        alphaFileP = NULL;
     else
-        alpha_file = pm_openw(cmdline.alpha_filename);
+        alphaFileP = pm_openw(cmdline.alphaFileName);
 
-    if (alpha_file && streq(cmdline.alpha_filename, "-"))
-        imageout_file = NULL;
+    if (alphaFileP && streq(cmdline.alphaFileName, "-"))
+        imageOutFileP = NULL;
     else
-        imageout_file = stdout;
+        imageOutFileP = stdout;
 
-    convertImages(ifP, cmdline.all_images, cmdline.image_no, 
-                  !cmdline.quitearly, imageout_file, alpha_file,
+    convertImages(ifP, cmdline.allImages, cmdline.imageNum, 
+                  !cmdline.quitearly, imageOutFileP, alphaFileP,
                   cmdline.repair);
 
     pm_close(ifP);
-    if (imageout_file != NULL) 
-        pm_close(imageout_file);
-    if (alpha_file != NULL)
-        pm_close(alpha_file);
+    if (imageOutFileP != NULL) 
+        pm_close(imageOutFileP);
+    if (alphaFileP != NULL)
+        pm_close(alphaFileP);
 
     return 0;
 }
+
+
+