diff options
Diffstat (limited to 'converter/other/giftopnm.c')
-rw-r--r-- | converter/other/giftopnm.c | 1639 |
1 files changed, 1012 insertions, 627 deletions
diff --git a/converter/other/giftopnm.c b/converter/other/giftopnm.c index 4cba5068..76cf4bff 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,11 +19,12 @@ describe the Lempel-Ziv base. */ -#define _BSD_SOURCE /* Make sure strcasecmp() is in string.h */ - +#define _XOPEN_SOURCE 500 /* Make sure strdup() is in string.h */ +#define _BSD_SOURCE /* for strcaseeq */ #include <string.h> #include <assert.h> +#include "pm_config.h" #include "pm_c_util.h" #include "mallocvar.h" #include "nstring.h" @@ -38,35 +40,18 @@ #define MAX_LZW_BITS 12 -#define INTERLACE 0x40 -#define LOCALCOLORMAP 0x80 -#define BitSet(byte, bit) (((byte) & (bit)) == (bit)) - -#if !defined(BYTE_ORDER) || !defined(LITTLE_ENDIAN) - /* make sure (BYTE_ORDER == LITTLE_ENDIAN) is FALSE */ - #define BYTE_ORDER 0 - #define LITTLE_ENDIAN 1 -#endif - -#if defined(__x86_64__) | defined(__i486__) | defined(__vax__) -# define UNALIGNED_OK 1 -#else -# define UNALIGNED_OK 0 +#ifndef FASTPBMRENDER + #define FASTPBMRENDER TRUE #endif +static const bool useFastPbmRender = FASTPBMRENDER; +#ifndef REPORTLZWCODES + #define REPORTLZWCODES FALSE +#endif -static __inline__ bool -ReadOK(FILE * const fileP, - unsigned char * const buffer, - size_t const len) { - - size_t bytesRead; - - bytesRead = fread(buffer, len, 1, fileP); - - return (bytesRead != 0); -} +static const bool wantLzwCodes = REPORTLZWCODES; + /* In verbose output, include all the LZW codes */ @@ -78,41 +63,37 @@ readFile(FILE * const ifP, size_t bytesRead; - bytesRead = fread(buffer, len, 1, ifP); + bytesRead = fread(buffer, 1, len, ifP); if (bytesRead == len) *errorP = NULL; else { if (ferror(ifP)) - asprintfN(errorP, "Error reading file. errno=%d (%s)", - errno, strerror(errno)); + pm_asprintf(errorP, "Error reading file. errno=%d (%s)", + errno, strerror(errno)); else if (feof(ifP)) - asprintfN(errorP, "End of file encountered"); + pm_asprintf(errorP, "End of file encountered"); else - asprintfN(errorP, "Short read -- %u bytes of %u", - (unsigned)bytesRead, (unsigned)len); + pm_asprintf(errorP, "Short read -- %u bytes of %u", + (unsigned)bytesRead, (unsigned)len); } } -#define LM_to_uint(a,b) (((b)<<8)|(a)) - -static int const maxnum_lzwCode = (1<<MAX_LZW_BITS); - -struct cmdlineInfo { +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; }; @@ -121,13 +102,13 @@ struct cmdlineInfo { static void parseCommandLine(int argc, char ** argv, - struct cmdlineInfo * const cmdlineP) { + struct CmdlineInfo * const cmdlineP) { /*---------------------------------------------------------------------------- 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; - /* Instructions to optParseOptions3 on how to parse our options. + /* Instructions to pm_optParseOptions3 on how to parse our options. */ optStruct3 opt; @@ -150,120 +131,198 @@ 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; opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ opt.allowNegNum = FALSE; /* We have no parms that are negative numbers */ - optParseOptions3( &argc, argv, opt, sizeof(opt), 0); + 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]; -struct gifScreen { - unsigned int Width; - unsigned int Height; - gifColorMap ColorMap; - unsigned int ColorMapSize; - /* Number of colors in the color map. */ - unsigned int ColorResolution; - unsigned int Background; - unsigned int AspectRatio; +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; + bool hasGlobalColorMap; + /* The stream has a global color map, to wit 'colorMap'. + (If the stream doesn't have a global color map, the individual + images must each have a local color map) + */ + GifColorMap colorMap; + /* The global color map for the stream. Meaningful only if + 'hasGlobalColorMap' is true. + */ + 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; +struct Gif89 { + 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; +initGif89(struct Gif89 * const gif89P) { + 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) { +/*---------------------------------------------------------------------------- + Read a color map from a GIF stream, where the stream is on *ifP, + which is positioned to a color map, which is 'cmapSize' bytes long. - int i; - unsigned char rgb[3]; + Return as *cmapP that color map. - assert(colormapsize <= MAXCOLORMAPSIZE); + Furthermore, analyze that color map and return *hasGrayP == true iff it + contains any gray (black and white don't count) and *hasColorP == true iff + it contains anything that is not gray or black or white. +-----------------------------------------------------------------------------*/ + unsigned int i; + unsigned char rgb[3]; + + assert(cmapSize <= MAXCOLORMAPSIZE); *hasGrayP = FALSE; /* initial assumption */ *hasColorP = FALSE; /* initial assumption */ - for (i = 0; i < colormapsize; ++i) { - if (! ReadOK(ifP, rgb, sizeof(rgb))) - pm_error("Unable to read Color %d from colormap", i); + for (i = 0; i < cmapSize; ++i) { + const char * error; + readFile(ifP, rgb, sizeof(rgb), &error); + if (error) + pm_error("Unable to read Color %u from colormap. %s", i, error); - 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) @@ -271,6 +330,7 @@ readColorMap(FILE *ifP, const int colormapsize, } else *hasColorP = TRUE; } + cmapP->size = cmapSize; } @@ -302,13 +362,17 @@ getDataBlock(FILE * const ifP, If we hit EOF or have an I/O error reading the data portion of the DataBlock, we exit the program with pm_error(). -----------------------------------------------------------------------------*/ + long const pos = ftell(ifP); + unsigned char count; - bool successfulRead; + const char * error; - long const pos = ftell(ifP); - successfulRead = ReadOK(ifP, &count, 1); - if (!successfulRead) { - pm_message("EOF or error in reading DataBlock size from file" ); + readFile(ifP, &count, sizeof(count), &error); + + if (error) { + pm_message("EOF or error in reading DataBlock size from file. %s", + error); + pm_strfree(error); *errorP = NULL; *eofP = TRUE; *lengthP = 0; @@ -322,17 +386,18 @@ getDataBlock(FILE * const ifP, *errorP = NULL; zeroDataBlock = TRUE; } else { - bool successfulRead; + const char * error; zeroDataBlock = FALSE; - successfulRead = ReadOK(ifP, buf, count); + readFile(ifP, buf, count, &error); - if (successfulRead) + if (error) { + pm_asprintf(errorP, + "Unable to read data portion of %u byte " + "DataBlock from file. %s", count, error); + pm_strfree(error); + } else *errorP = NULL; - else - asprintfN(errorP, - "EOF or error reading data portion of %u byte " - "DataBlock from file", count); } } } @@ -348,7 +413,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 */ @@ -371,6 +436,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 @@ -380,7 +466,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; @@ -405,13 +491,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) { + 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); @@ -425,11 +520,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); } } @@ -437,34 +537,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"; @@ -479,21 +561,20 @@ 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); } -struct getCodeState { +struct GetCodeState { unsigned char buf[280]; /* This is the buffer through which we read the data from the stream. We must buffer it because we have to read whole data @@ -519,11 +600,11 @@ struct getCodeState { static void getAnotherBlock(FILE * const ifP, - struct getCodeState * const gsP, + struct GetCodeState * const gsP, const char ** const errorP) { unsigned int count; - unsigned int assumed_count; + unsigned int assumedCount; bool eof; /* Shift buffer down so last two bytes are now the @@ -548,22 +629,22 @@ getAnotherBlock(FILE * const ifP, "file is malformed, but we are proceeding " "anyway as if an EOD marker were at the end " "of the file."); - assumed_count = 0; + assumedCount = 0; } else - assumed_count = count; + assumedCount = count; - gsP->streamExhausted = (assumed_count == 0); + gsP->streamExhausted = (assumedCount == 0); - gsP->bufCount += assumed_count; + gsP->bufCount += assumedCount; } } -static struct getCodeState getCodeState; +static struct GetCodeState getCodeState; static void -getCode_init(struct getCodeState * const getCodeStateP) { +getCode_init(struct GetCodeState * const getCodeStateP) { /* Fake a previous data block */ getCodeStateP->buf[0] = 0; @@ -615,7 +696,7 @@ bitsOfLeBuffer(const unsigned char * const buf, static void -getCode_get(struct getCodeState * const gsP, +getCode_get(struct GetCodeState * const gsP, FILE * const ifP, int const codeSize, bool * const eofP, @@ -668,6 +749,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; } @@ -676,18 +760,17 @@ getCode_get(struct getCodeState * const gsP, - -struct stack { +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 */ }; static void -initStack(struct stack * const stackP, unsigned int const size) { +initStack(struct Stack * const stackP, unsigned int const size) { MALLOCARRAY(stackP->stack, size); if (stackP->stack == NULL) @@ -699,7 +782,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"); @@ -710,14 +793,14 @@ pushStack(struct stack * const stackP, int const value) { static bool -stackIsEmpty(const struct stack * const stackP) { +stackIsEmpty(const struct Stack * const stackP) { return stackP->sp == stackP->stack; } -static int -popStack(struct stack * const stackP) { +static unsigned char +popStack(struct Stack * const stackP) { if (stackP->sp <= stackP->stack) pm_error("stack underflow"); @@ -728,7 +811,7 @@ popStack(struct stack * const stackP) { static void -termStack(struct stack * const stackP) { +termStack(struct Stack * const stackP) { free(stackP->stack); stackP->stack = NULL; } @@ -774,98 +857,164 @@ termStack(struct stack * const stackP) { -----------------------------------------------------------------------------*/ +static int const maxLzwCodeCt = (1<<MAX_LZW_BITS); -struct decompressor { - struct stack stack; - int fresh; +struct Decompressor { + struct Stack stack; + 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 */ }; static void -resetDecompressor(struct decompressor * const decompP) { +resetDecompressor(struct Decompressor * const decompP) { + + decompP->codeSize = decompP->initCodeSize+1; + decompP->maxCodeCt = 1 << decompP->codeSize; + decompP->nextTableSlot = decompP->maxDataVal + 3; + decompP->fresh = TRUE; +} - decompP->codeSize = decompP->init_codeSize+1; - decompP->maxnum_code = 1 << decompP->codeSize; - decompP->next_tableSlot = decompP->max_dataVal + 3; - decompP->fresh = 1; + + +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."); + } + } } static void -lzwInit(struct decompressor * const decompP, +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 -lzwTerm(struct decompressor * const decompP) { +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; +} + + + +static void +lzwTerm(struct Decompressor * const decompP) { termStack(&decompP->stack); } @@ -873,8 +1022,32 @@ lzwTerm(struct decompressor * const decompP) { static void -expandCodeOntoStack(struct decompressor * const decompP, - int const incode, +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, + unsigned int const incode, const char ** const errorP) { /*---------------------------------------------------------------------------- 'incode' is an LZW string code. It represents a string of true data @@ -889,119 +1062,125 @@ 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 - - The only thing it could legally be is one higher than the - highest one we've seen so far. - */ - if (code > decompP->next_tableSlot) { - /* We just abort because we added this to stable code to fix - a bug and we don't want to disturb stable code more than we - have to. - */ - pm_error("Error in GIF image: LZW string code %u " - "is neither a previously defined one nor the " - "next in sequence to define (%u)", - code, decompP->next_tableSlot); - } - 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) { - asprintfN(&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; } static void -lzwReadByteFresh(struct getCodeState * const getCodeStateP, - struct decompressor * const decompP, +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 and nothing as + *dataReadP. One way we hit end of image is for that first non-clear code to + be an end code. + + 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, +lzwReadByte(struct Decompressor * const decompP, + unsigned char * const dataReadP, bool * const endOfImageP, const char ** const errorP) { /*---------------------------------------------------------------------------- @@ -1025,8 +1204,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 { @@ -1036,14 +1213,15 @@ lzwReadByte(struct decompressor * const decompP, &eof, &code, errorP); if (!*errorP) { if (eof) - asprintfN(errorP, - "Premature end of file; no proper GIF closing"); + 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; @@ -1065,8 +1243,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. @@ -1119,47 +1297,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 white (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); + } -struct pnmBuffer { - xel ** xels; - unsigned int col; - unsigned int row; -}; + if (imageOutfile) { + if (useFastPbmRender && format == PBM_FORMAT && !haveTransColor) { -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; + bit * const bitrow = cmapIndexRow; + + 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); + } } } @@ -1170,19 +1358,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) - asprintfN(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 " - "this is not just a truncated file.", - cols, rows, failedRowNum); + pm_asprintf(errorP, + "Error in GIF image: Not enough raster data to fill " + "%u x %u dimensions. " + "The image has proper ending sequence, so " + "this is not just a truncated file.", + cols, rows); else *errorP = NULL; } @@ -1190,93 +1377,212 @@ 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; - while (pnmBuffer.row < rows) { - unsigned int colorIndex; + *errorP = NULL; /* initial value */ - if (fillingMissingPixels) - colorIndex = 0; - else { - const char * error; + for (col = 0, fillingWithZero = fillWithZero; + col < cols; + ++col) { - const char * readError; - unsigned int readColorIndex; - bool endOfImage; + unsigned char colorIndex; + + 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); + verifyPixelRead(endOfImage, readError, cols, rows, errorP); if (readError) - 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); - + pm_strfree(readError); + + 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 -skipExtraneousData(struct decompressor * const decompP) { +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" ); - unsigned int byteRead; + 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 char byteRead; bool endOfImage; const char * error; + endOfImage = FALSE; /* initial value */ + lzwReadByte(decompP, &byteRead, &endOfImage, &error); if (error) - strfree(error); - else if (!endOfImage) { - pm_message("Extraneous data at end of image. " - "Skipped to end of image"); + pm_strfree(error); + 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); - strfree(error); + if (error) { + pm_message("Error encountered skipping to end of image: %s", + error); + pm_strfree(error); + } } } } @@ -1284,36 +1590,89 @@ 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; + struct Decompressor decomp; + const char * error; - gotMinCodeSize = ReadOK(ifP, &lzwMinCodeSize, 1); - if (!gotMinCodeSize) - pm_error("GIF stream ends (or read error) " + readFile(ifP, &lzwMinCodeSize, sizeof(lzwMinCodeSize), &error); + if (error) + pm_error("Can't read GIF stream " "right after an image separator; no " - "image data follows."); + "image data follows. %s", error); if (lzwMinCodeSize > MAX_LZW_BITS) pm_error("Invalid minimum code size value in image data: %u. " "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); + + issueTransparencyMessage(haveTransColor, transparentIndex, cmap); - readRaster(&decomp, xels, cols, rows, cmap, cmapSize, interlace, - transparentIndex, alphabits, tolerateBadInput); + 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); @@ -1323,80 +1682,47 @@ 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) { -/*---------------------------------------------------------------------------- - 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); -} +warnUserNotSquare(unsigned int const aspectRatio) { + const char * baseMsg = + "warning - input pixels are not square, " + "but we are rendering them as square pixels " + "in the output"; + if (pm_have_float_format()) { + float const r = ((float)aspectRatio + 15.0 ) / 64.0; -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 - ); - } + pm_message("%s. To fix the output, run it through " + "'pamscale -%cscale %g'", + baseMsg, + r < 1.0 ? 'x' : 'y', + r < 1.0 ? 1.0 / r : r ); + } else + pm_message("%s", baseMsg); } + + static void -readGifHeader(FILE * const gifFile, struct gifScreen * const gifScreenP) { +readGifHeader(FILE * const gifFileP, + 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]; +#define GLOBALCOLORMAP 0x80 + unsigned char buf[16]; + char version[4]; + unsigned int cmapSize; + const char * error; - if (! ReadOK(gifFile,buf,6)) - pm_error("error reading magic number" ); + readFile(gifFileP, buf, 6, &error); + if (error) + pm_error("Error reading magic number. %s", error); - 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'."); @@ -1407,56 +1733,52 @@ readGifHeader(FILE * const gifFile, struct gifScreen * const gifScreenP) { pm_message("GIF format version is '%s'", version); if ((!streq(version, "87a")) && (!streq(version, "89a"))) - pm_error("bad version number, not '87a' or '89a'" ); + pm_error("Bad version number, not '87a' or '89a'" ); - if (! ReadOK(gifFile,buf,7)) - pm_error("failed to read screen descriptor" ); + readFile(gifFileP, buf, 7, &error); + if (error) + pm_error("Failed to read screen descriptor. %s", error); - 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); - gifScreenP->ColorResolution = (buf[4] & 0x70 >> 3) + 1; - gifScreenP->Background = buf[5]; - gifScreenP->AspectRatio = buf[6]; + gifScreenP->width = LM_to_uint(buf[0],buf[1]); + gifScreenP->height = LM_to_uint(buf[2],buf[3]); + cmapSize = 1 << ((buf[4] & 0x07) + 1); + gifScreenP->colorResolution = (buf[4] & 0x70 >> 3) + 1; + gifScreenP->background = buf[5]; + gifScreenP->aspectRatio = buf[6]; if (verbose) { - pm_message("GIF Width = %d GIF Height = %d " - "Pixel aspect ratio = %d (%f:1)", - gifScreenP->Width, gifScreenP->Height, - gifScreenP->AspectRatio, - gifScreenP->AspectRatio == 0 ? - 1 : (gifScreenP->AspectRatio + 15) / 64.0); - pm_message("Colors = %d Color Resolution = %d", - gifScreenP->ColorMapSize, gifScreenP->ColorResolution); + pm_message("GIF Width = %u GIF Height = %u " + "Pixel aspect ratio = %u (%f:1)", + gifScreenP->width, gifScreenP->height, + gifScreenP->aspectRatio, + gifScreenP->aspectRatio == 0 ? + 1 : (gifScreenP->aspectRatio + 15) / 64.0); + pm_message("Global color count = %u Color Resolution = %u", + cmapSize, gifScreenP->colorResolution); } - if (BitSet(buf[4], LOCALCOLORMAP)) { /* Global Colormap */ - readColorMap(gifFile, gifScreenP->ColorMapSize, gifScreenP->ColorMap, + if (buf[4] & GLOBALCOLORMAP) { + gifScreenP->hasGlobalColorMap = true; + readColorMap(gifFileP, cmapSize, &gifScreenP->colorMap, &gifScreenP->hasGray, &gifScreenP->hasColor); if (verbose) { - pm_message("Color map %s grays, %s colors", + pm_message("Global color map %s grays, %s colors", gifScreenP->hasGray ? "contains" : "doesn't contain", gifScreenP->hasColor ? "contains" : "doesn't contain"); } - } + } else + gifScreenP->hasGlobalColorMap = false; - if (gifScreenP->AspectRatio != 0 && gifScreenP->AspectRatio != 49) { - float r; - r = ( (float) gifScreenP->AspectRatio + 15.0 ) / 64.0; - pm_message("warning - input pixels are not square, " - "but we are rendering them as square pixels " - "in the output. " - "To fix the output, run it through " - "'pnmscale -%cscale %g'", - r < 1.0 ? 'x' : 'y', - r < 1.0 ? 1.0 / r : r ); - } + if (gifScreenP->aspectRatio != 0 && gifScreenP->aspectRatio != 49) + warnUserNotSquare(gifScreenP->aspectRatio); + +#undef GLOBALCOLORMAP } static void readExtensions(FILE* const ifP, - struct gif89 * const gif89P, + struct Gif89 * const gif89P, bool * const eodP, const char ** const errorP) { /*---------------------------------------------------------------------------- @@ -1482,13 +1804,13 @@ readExtensions(FILE* const ifP, unsigned char c; const char * error; - readFile(ifP, &c, 1, &error); + readFile(ifP, &c, sizeof(c), &error); if (error) { - asprintfN(errorP, "File read error where start of image " - "descriptor or end of GIF expected. %s", - error); - strfree(error); + pm_asprintf(errorP, "File read error where start of image " + "descriptor or end of GIF expected. %s", + error); + pm_strfree(error); } else { if (c == ';') { /* GIF terminator */ eod = TRUE; @@ -1499,17 +1821,18 @@ readExtensions(FILE* const ifP, readFile(ifP, &functionCode, 1, &error); if (error) { - asprintfN(errorP, "Failed to read function code " - "of GIF extension (immediately after the '!' " - "extension delimiter) from input. %s", error); - strfree(error); + pm_asprintf(errorP, "Failed to read function code " + "of GIF extension (immediately after the '!' " + "extension delimiter) from input. %s", error); + pm_strfree(error); } else { doExtension(ifP, functionCode, gif89P); } } 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; @@ -1517,20 +1840,111 @@ readExtensions(FILE* const ifP, +struct GifImageHeader { +/*---------------------------------------------------------------------------- + Information in the header (first 9 bytes) of a GIF image. +-----------------------------------------------------------------------------*/ + bool hasLocalColormap; + /* The image has its own color map. Its size is 'localColorMapSize' */ + /* (If an image does not have its own color map, the image uses the + global color map for the GIF stream) + */ + unsigned int localColorMapSize; + /* Meaningful only if 'hasLocalColormap' is true. */ + + /* Position of the image (max 65535) */ + unsigned int lpos; + unsigned int tpos; + + /* Dimensions of the image (max 65535) */ + unsigned int cols; + unsigned int rows; + + 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) - pm_message(" Uses global colormap"); + if (imageHeader.lpos > 0 || imageHeader.tpos > 0) + pm_message(" Image left position: %u top position: %u", + imageHeader.lpos, imageHeader.tpos); + + if (imageHeader.hasLocalColormap) + pm_message(" Uses local colormap of %u colors", + imageHeader.localColorMapSize); else - pm_message(" Uses local colormap of %u colors", localColorMapSize); + pm_message(" Uses global colormap"); +} + + + +static void +readImageHeader(FILE * const ifP, + struct GifImageHeader * const imageHeaderP) { + +#define LOCALCOLORMAP 0x80 +#define INTERLACE 0x40 + + unsigned char buf[16]; + const char * error; + + readFile(ifP, buf, 9, &error); + if (error) + pm_error("couldn't read left/top/width/height. %s", error); + + imageHeaderP->hasLocalColormap = !!(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 = !!(buf[8] & INTERLACE); + + if (verbose) + reportImageHeader(*imageHeaderP); + +#undef INTERLACE +#undef LOCALCOLORMAP +} + + + +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; + const char * error; + + readFile(ifP, &lzwMinCodeSize, sizeof(lzwMinCodeSize), &error); + if (error) { + pm_message("Unable to read file to skip image DataBlock. %s", error); + pm_strfree(error); + } + readThroughEod(ifP); } @@ -1538,88 +1952,51 @@ reportImageInfo(unsigned int const cols, static void convertImage(FILE * const ifP, bool const skipIt, - FILE * const imageout_file, - FILE * const alphafile, - struct gifScreen gifScreen, - struct gif89 const gif89, + 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; - - if (! ReadOK(ifP,buf,9)) - pm_error("couldn't read left/top/width/height"); - - 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); + struct GifImageHeader imageHeader; + GifColorMap localColorMap; + const GifColorMap * currentColorMapP; + bool hasGray, hasColor; - if (verbose) - reportImageInfo(cols, rows, useGlobalColormap, localColorMapSize, - interlaced); - - if (cols == 0) - pm_error("Invalid GIF - width is zero"); - - xels = pnm_allocarray(cols, rows); - if (!xels) - pm_error("couldn't alloc space for image" ); + readImageHeader(ifP, &imageHeader); - 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; + validateWithinGlobalScreen(imageHeader, gifScreen); - readColorMap(ifP, localColorMapSize, localColorMap, + if (imageHeader.hasLocalColormap) { + readColorMap(ifP, imageHeader.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); - } + currentColorMapP = &localColorMap; + } else if (gifScreen.hasGlobalColorMap) { + 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); - } + pm_error("Invalid GIF: " + "Image has no local color map and stream has no global " + "color map either."); } - 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); } @@ -1637,7 +2014,7 @@ disposeOfReadExtensionsError(const char * const error, else pm_error("Error accessing Image %u of stream. %s", imageSeq, error); - strfree(error); + pm_strfree(error); *eodP = TRUE; } } @@ -1645,17 +2022,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. @@ -1669,21 +2046,24 @@ 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. */ - struct gifScreen gifScreen; - struct gif89 gif89; + struct GifScreen gifScreen; + struct Gif89 gif89; 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; @@ -1701,9 +2081,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); } } @@ -1714,9 +2095,10 @@ convertImages(FILE * const ifP, int main(int argc, char **argv) { - struct cmdlineInfo cmdline; - FILE *ifP; - FILE *alpha_file, *imageout_file; + struct CmdlineInfo cmdline; + FILE * ifP; + FILE * alphaFileP; + FILE * imageOutFileP; pnm_init(&argc, argv); @@ -1724,27 +2106,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; } + + + |