/*============================================================================= pamtogif =============================================================================== Convert a Netpbm image to GIF History and copyright information is at the end of the file. =============================================================================*/ #include #include #include #include "pm_c_util.h" #include "mallocvar.h" #include "shhopt.h" #include "nstring.h" #include "pam.h" #include "pammap.h" #define MAXCMAPSIZE 256 static bool verbose; enum TransparencyType {TRANS_NONE, TRANS_COLOR, TRANS_ALPHA}; /* The source of transparency for the GIF: nothing is transparent, All pixels of a certain color are transparent, or the alpha plane in the input tells what is transparent. */ typedef unsigned int StringCode; /* A code to be place in the GIF raster. It represents a string of one or more pixels. You interpret this in the context of a current code size. The lower half of the values representable in the current code size represent singleton strings and the value is simply the value of the one pixel in the string. The first two values in the upper half of the range are the clear code and EOF code, respectively. The rest of the values represent multi-pixel strings. The mapping between value and the sequence of pixels changes throughout the image. Ergo, this data structure must be at least BITS bits wide. */ struct Cmap { /* This is the information for the GIF colormap (aka palette). */ struct pam pam; /* Gives depth and maxval for colors in color[] */ tuple color[MAXCMAPSIZE]; /* Maps a color index, as is found in the raster part of the GIF, to color. */ unsigned int cmapSize; /* Number of entries in the GIF colormap. I.e. number of colors in the image, plus possibly one fake transparency color. */ bool haveTransparent; /* The colormap contains an entry for transparent pixels */ unsigned int transparent; /* color index number in GIF palette of the color that is to be transparent. Meaningful only if 'haveTransparent' is true. */ tuplehash tuplehash; /* A hash table to translate color to GIF colormap index. */ }; struct CmdlineInfo { /* All the information the user supplied in the command line, in a form easy for the program to use. */ const char *input_filespec; /* Filespec of input file */ const char *alphacolor; /* -alphacolor option value or default */ unsigned int interlace; /* -interlace option value */ unsigned int sort; /* -sort option value */ const char *mapfile; /* -mapfile option value. NULL if none. */ const char *transparent; /* -transparent option value. NULL if none. */ const char *comment; /* -comment option value; NULL if none */ unsigned int nolzw; /* -nolzw option */ unsigned int noclear; /* -noclear option */ float aspect; /* -aspect option value (the ratio). */ unsigned int verbose; }; static unsigned int pamAlphaPlane(struct pam * const pamP) { /*---------------------------------------------------------------------------- The number of the alpha plane, or zero if there is no alpha plane, as indicated by *pamP. Note that the alpha plane is never zero in any Netpbm tuple type. -----------------------------------------------------------------------------*/ unsigned int alphaPlane; if (streq(pamP->tuple_type, "RGB_ALPHA")) alphaPlane = 3; else if (streq(pamP->tuple_type, "GRAYSCALE_ALPHA")) alphaPlane = 1; else if (streq(pamP->tuple_type, "BLACKANDWHITE_ALPHA")) alphaPlane = 1; else alphaPlane = 0; if (alphaPlane >= pamP->depth) pm_error("Tuple type is '%s', but depth (%u) is less than %u", pamP->tuple_type, pamP->depth, alphaPlane + 1); return alphaPlane; } static void parseCommandLine(int argc, const char ** argv, struct CmdlineInfo * const cmdlineP) { /*---------------------------------------------------------------------------- Parse the program arguments (given by argc and argv) into a form the program can deal with more easily -- a cmdline_info structure. If the syntax is invalid, issue a message and exit the program via pm_error(). Note that the file spec array we return is stored in the storage that was passed to us as the argv array. -----------------------------------------------------------------------------*/ optEntry * option_def; /* malloc'ed */ optStruct3 opt; /* set by OPTENT3 */ unsigned int option_def_index; unsigned int aspectSpec; MALLOCARRAY_NOFAIL(option_def, 100); option_def_index = 0; /* incremented by OPTENT3 */ OPTENT3(0, "interlace", OPT_FLAG, NULL, &cmdlineP->interlace, 0); OPTENT3(0, "sort", OPT_FLAG, NULL, &cmdlineP->sort, 0); OPTENT3(0, "nolzw", OPT_FLAG, NULL, &cmdlineP->nolzw, 0); OPTENT3(0, "noclear", OPT_FLAG, NULL, &cmdlineP->noclear, 0); OPTENT3(0, "mapfile", OPT_STRING, &cmdlineP->mapfile, NULL, 0); OPTENT3(0, "transparent", OPT_STRING, &cmdlineP->transparent, NULL, 0); OPTENT3(0, "comment", OPT_STRING, &cmdlineP->comment, NULL, 0); OPTENT3(0, "alphacolor", OPT_STRING, &cmdlineP->alphacolor, NULL, 0); OPTENT3(0, "aspect", OPT_FLOAT, &cmdlineP->aspect, &aspectSpec, 0); OPTENT3(0, "verbose", OPT_FLAG, NULL, &cmdlineP->verbose, 0); /* Set the defaults */ cmdlineP->mapfile = NULL; cmdlineP->transparent = NULL; /* no transparency */ cmdlineP->comment = NULL; /* no comment */ cmdlineP->alphacolor = "rgb:0/0/0"; /* We could say "black" here, but then we depend on the color names database existing. */ opt.opt_table = option_def; opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ opt.allowNegNum = FALSE; /* We have no parms that are negative numbers */ pm_optParseOptions4(&argc, argv, opt, sizeof(opt), 0); /* Uses and sets argc, argv, and some of *cmdlineP and others. */ if (argc-1 == 0) cmdlineP->input_filespec = "-"; else if (argc-1 != 1) pm_error("Program takes zero or one argument (filename). You " "specified %d", argc-1); else cmdlineP->input_filespec = argv[1]; if (aspectSpec) { if (cmdlineP->aspect < 0.25 || cmdlineP->aspect > 4.21875) pm_error("Invalid -aspect value: %f. " "GIF allows only the range 0.25-4.0 .", cmdlineP->aspect); else if (cmdlineP->aspect > 4.0) pm_message("Warning: " "You specified an aspect ratio over 4.0: %f. " "This will result in an invalid GIF.", cmdlineP->aspect); } else cmdlineP->aspect = 1.0; } /* * Write out a word to the GIF file */ static void Putword(int const w, FILE * const fp) { fputc( w & 0xff, fp ); fputc( (w / 256) & 0xff, fp ); } static int closestColor(tuple const color, struct pam * const pamP, const struct Cmap * const cmapP) { /*---------------------------------------------------------------------------- Return the colormap index of the color in the colormap *cmapP that is closest to the color 'color', whose format is specified by *pamP. Also add 'color' to the colormap hash, with the colormap index we are returning. Caller must ensure that the color is not already in there. -----------------------------------------------------------------------------*/ unsigned int const nComp = pamP->depth >= 3 ? 3 : 1; /* Number of color components (not alpha) in 'color' */ unsigned int i; unsigned int imin, dmin; int fits; dmin = UINT_MAX; imin = 0; for (i = 0; i < cmapP->cmapSize; ++i) { unsigned int distance; unsigned int plane; for (distance = 0, plane = 0; plane < nComp; ++plane) /* Divide by 4 is to avoid arithmetic overflow */ distance += SQR(color[plane] - cmapP->color[i][plane]) / 4; if (distance < dmin) { dmin = distance; imin = i; } } pnm_addtotuplehash(pamP, cmapP->tuplehash, color, imin, &fits); return imin; } enum pass {MULT8PLUS0, MULT8PLUS4, MULT4PLUS2, MULT2PLUS1}; typedef struct { struct pam pam; /* Description of input file/image. The position of the file is also part of the state of this rowReader. */ pm_filepos rasterPos; /* Position in file fileP of the start of the raster */ bool interlace; /* We're accessing the image in interlace fashion */ bool eof; /* The image is at EOF (we have returned all of the rows) */ unsigned int nextRow; /* Number of row to which input file is positioned; meaningless if 'eof'. */ enum pass pass; /* The interlace pass. Undefined if !interlace */ tuple * discardBuffer; /* A bitbucket for rows we read in order to advance the file position. */ } RowReader; static RowReader * rowReader_create(struct pam * const pamP, pm_filepos const rasterPos, bool const interlace) { RowReader * rdrP; MALLOCVAR_NOFAIL(rdrP); rdrP->pam = *pamP; rdrP->rasterPos = rasterPos; rdrP->interlace = interlace; rdrP->eof = FALSE; rdrP->pass = MULT8PLUS0; pm_seek2(rdrP->pam.file, &rasterPos, sizeof(rasterPos)); rdrP->nextRow = 0; rdrP->discardBuffer = pnm_allocpamrow(&rdrP->pam); return rdrP; } static void rowReader_destroy(RowReader * const rdrP) { pnm_freepamrow(rdrP->discardBuffer); free(rdrP); } static void rowReader_skipRows(RowReader * const rdrP, unsigned int const rowCount, bool * const eofP) { /*---------------------------------------------------------------------------- Skip over the next 'rowCount' rows of the input file. Iff there aren't at least 'rowCount' rows left, return *eofP == TRUE. -----------------------------------------------------------------------------*/ if (rdrP->nextRow + rowCount >= rdrP->pam.height) *eofP = TRUE; else { /* This could be made faster if need be by adding a libnetpbm row skip function. Except with the plain formats, that could just compute the next row position and fseek() to it. pnm_readpamrow() with NULL for the output pointer would be a good interface for a row skip function. */ unsigned int i; *eofP = FALSE; for (i = 0; i < rowCount; ++i) pnm_readpamrow(&rdrP->pam, rdrP->discardBuffer); rdrP->nextRow += rowCount; } } static void rowReader_gotoNextInterlaceRow(RowReader * const rdrP) { /*---------------------------------------------------------------------------- Position reader to the next row in the interlace pattern, assuming it is now positioned immediately after the current row. -----------------------------------------------------------------------------*/ bool endOfPass; /* There are 4 passes: MULT8PLUS0: Rows 0, 8, 16, 24, 32, etc. MULT8PLUS4: Rows 4, 12, 20, 28, etc. MULT4PLUS2: Rows 2, 6, 10, 14, etc. MULT2PLUS1: Rows 1, 3, 5, 7, 9, etc. */ switch (rdrP->pass) { case MULT8PLUS0: rowReader_skipRows(rdrP, 7, &endOfPass); break; case MULT8PLUS4: rowReader_skipRows(rdrP, 7, &endOfPass); break; case MULT4PLUS2: rowReader_skipRows(rdrP, 3, &endOfPass); break; case MULT2PLUS1: rowReader_skipRows(rdrP, 1, &endOfPass); break; } /* Note that if there are more than 4 rows, the sequence of passes is sequential, but when there are fewer than 4, reading may skip e.g. from MULT8PLUS0 to MULT4PLUS2. */ while (endOfPass && !rdrP->eof) { pm_seek2(rdrP->pam.file, &rdrP->rasterPos, sizeof(rdrP->rasterPos)); rdrP->nextRow = 0; switch (rdrP->pass) { case MULT8PLUS0: rdrP->pass = MULT8PLUS4; rowReader_skipRows(rdrP, 4, &endOfPass); break; case MULT8PLUS4: rdrP->pass = MULT4PLUS2; rowReader_skipRows(rdrP, 2, &endOfPass); break; case MULT4PLUS2: rdrP->pass = MULT2PLUS1; rowReader_skipRows(rdrP, 1, &endOfPass); break; case MULT2PLUS1: rdrP->eof = TRUE; break; } } } static void rowReader_gotoNextStraightRow(RowReader * const rdrP) { /*---------------------------------------------------------------------------- Position reader to the next row in a straight, non-interlace pattern, assuming the file is now positioned immediately after the current row. This is trivial, since the next row _is_ immediately after the current row, except in the case that there are no more rows. -----------------------------------------------------------------------------*/ if (rdrP->nextRow >= rdrP->pam.height) rdrP->eof = TRUE; } static void rowReader_read(RowReader * const rdrP, tuple * const tuplerow) { if (rdrP->eof) pm_error("INTERNAL ERROR: rowReader attempted to read beyond end " "of image"); pnm_readpamrow(&rdrP->pam, tuplerow); ++rdrP->nextRow; if (rdrP->interlace) rowReader_gotoNextInterlaceRow(rdrP); else rowReader_gotoNextStraightRow(rdrP); } static unsigned int gifPixel(struct pam * const pamP, tuple const tuple, unsigned int const alphaPlane, sample const alphaThreshold, const struct Cmap * const cmapP) { /*---------------------------------------------------------------------------- Return the colormap index of the tuple 'tuple', whose format is described by *pamP, using colormap *cmapP. 'alphaPlane' is the number of the plane in 'tuple' to use for transparency, or zero if we aren't to use any plane for transparency. (note that Caller cannot specify plane 0 for transparency). 'alphaThreshold' is the alpha level below which we consider a pixel transparent for GIF purposes. If 'alphaPlane' is nonzero, we assume *cmapP contains a transparent entry. -----------------------------------------------------------------------------*/ int colorIndex; if (alphaPlane && tuple[alphaPlane] < alphaThreshold) { assert(cmapP->haveTransparent); colorIndex = cmapP->transparent; } else { int found; pnm_lookuptuple(pamP, cmapP->tuplehash, tuple, &found, &colorIndex); if (!found) colorIndex = closestColor(tuple, pamP, cmapP); } assert(colorIndex >= 0); return (unsigned int) colorIndex; } static void writeTransparentColorIndexExtension(FILE * const ofP, unsigned int const transColorIndex) { /*---------------------------------------------------------------------------- Write out extension for transparent color index. -----------------------------------------------------------------------------*/ fputc('!', ofP); fputc(0xf9, ofP); fputc(4, ofP); fputc(1, ofP); fputc(0, ofP); fputc(0, ofP); fputc(transColorIndex, ofP); fputc(0, ofP); } static void writeCommentExtension(FILE * const ofP, char const comment[]) { /*---------------------------------------------------------------------------- Write out extension for a comment -----------------------------------------------------------------------------*/ unsigned int const maxSegmentSize = 255; const char * segment; fputc('!', ofP); /* Identifies an extension */ fputc(0xfe, ofP); /* Identifies a comment */ /* Write it out in segments no longer than 255 characters */ for (segment = &comment[0]; segment < comment + strlen(comment); segment += maxSegmentSize) { unsigned int const lengthThisSegment = MIN(maxSegmentSize, strlen(segment)); fputc(lengthThisSegment, ofP); fwrite(segment, 1, lengthThisSegment, ofP); } fputc(0, ofP); /* No more comment blocks in this extension */ } /*************************************************************************** * * GIFCOMPR.C - GIF Image compression routines * * Lempel-Ziv compression based on 'compress'. GIF modifications by * David Rowley (mgardi@watdcsu.waterloo.edu) * ***************************************************************************/ /* * General DEFINEs */ #define BITS 12 /* * * GIF Image compression - modified 'compress' * * Based on: compress.c - File compression ala IEEE Computer, June 1984. * * By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) * Jim McKie (decvax!mcvax!jim) * Steve Davies (decvax!vax135!petsd!peora!srd) * Ken Turkowski (decvax!decwrl!turtlevax!ken) * James A. Woods (decvax!ihnp4!ames!jaw) * Joe Orost (decvax!vax135!petsd!joe) * */ static StringCode const maxCodeLimitLzw = (StringCode)1 << BITS; /* One beyond the largest string code that can exist in GIF */ /* Used only in assertions */ struct HashTableEntry { /* This is an entry in the string table, which is a hash table. It says that the string code 'combinedString' represents the string which is the single pixel 'additionalPixel' appended to 'baseString', where 'baseString' may represent a multi-pixel string. */ bool present; /* There is an entry here. Following members are meaningless if not. */ StringCode baseString; StringCode additionalPixel; StringCode combinedString; }; /*************************************************************************** * BYTE OUTPUTTER ***************************************************************************/ typedef struct { FILE * fileP; /* The file to which to output */ unsigned int count; /* Number of bytes so far in the current data block */ unsigned char buffer[256]; /* The current data block, under construction */ } ByteBuffer; static ByteBuffer * byteBuffer_create(FILE * const fileP) { ByteBuffer * byteBufferP; MALLOCVAR_NOFAIL(byteBufferP); byteBufferP->fileP = fileP; byteBufferP->count = 0; return byteBufferP; } static void byteBuffer_destroy(ByteBuffer * const byteBufferP) { free(byteBufferP); } static void byteBuffer_flush(ByteBuffer * const byteBufferP) { /*---------------------------------------------------------------------------- Write the current data block to the output file, then reset the current data block to empty. -----------------------------------------------------------------------------*/ if (byteBufferP->count > 0 ) { if (verbose) pm_message("Writing %u byte block", byteBufferP->count); fputc(byteBufferP->count, byteBufferP->fileP); fwrite(byteBufferP->buffer, 1, byteBufferP->count, byteBufferP->fileP); byteBufferP->count = 0; } } static void byteBuffer_flushFile(ByteBuffer * const byteBufferP) { fflush(byteBufferP->fileP); if (ferror(byteBufferP->fileP)) pm_error("error writing output file"); } static void byteBuffer_out(ByteBuffer * const byteBufferP, unsigned char const c) { /*---------------------------------------------------------------------------- Add a byte to the end of the current data block, and if it is now 255 characters, flush the data block to the output file. -----------------------------------------------------------------------------*/ byteBufferP->buffer[byteBufferP->count++] = c; if (byteBufferP->count >= 255) byteBuffer_flush(byteBufferP); } /*************************************************************************** * GIF CODE OUTPUTTER ***************************************************************************/ typedef struct { ByteBuffer * byteBufferP; unsigned int initBits; unsigned int nBits; /* Number of bits to put in output for each code */ StringCode maxCode; /* maximum code, given n_bits */ StringCode maxCodeLimit; /* LZW: One beyond the largest string code that can exist in GIF. Uncompressed: a ceiling to prevent code size from ratcheting up. In either case, output code never reaches this value. */ unsigned long curAccum; unsigned int curBits; unsigned int stringCount; /* Number of strings that have been output to this buffer (by writing a string code) since the last flush (or ever, if no last flush). Note that this counts only strings that go into the buffer; it doesn't matter if they have gone out the other side yet. The main use of this is debugging -- when something fails, you can see in a debugger where in the image it was, then set a trap for there. */ } CodeBuffer; static CodeBuffer * codeBuffer_create(FILE * const ofP, unsigned int const initBits, bool const lzw) { CodeBuffer * codeBufferP; MALLOCVAR_NOFAIL(codeBufferP); codeBufferP->initBits = initBits; codeBufferP->nBits = codeBufferP->initBits; codeBufferP->maxCode = (1 << codeBufferP->nBits) - 1; codeBufferP->maxCodeLimit = lzw ? (StringCode)1 << BITS : (StringCode) (1 << codeBufferP->nBits) - 1; codeBufferP->byteBufferP = byteBuffer_create(ofP); codeBufferP->curAccum = 0; codeBufferP->curBits = 0; codeBufferP->stringCount = 0; return codeBufferP; } static void codeBuffer_destroy(CodeBuffer * const codeBufferP) { byteBuffer_destroy(codeBufferP->byteBufferP); free(codeBufferP); } static void codeBuffer_resetCodeSize(CodeBuffer * const codeBufferP) { codeBufferP->nBits = codeBufferP->initBits; assert(codeBufferP->nBits <= BITS); codeBufferP->maxCode = (1 << codeBufferP->nBits) - 1; } static void codeBuffer_increaseCodeSize(CodeBuffer * const codeBufferP) { ++codeBufferP->nBits; assert(codeBufferP->nBits <= BITS); codeBufferP->maxCode = (1 << codeBufferP->nBits) - 1; } static void codeBuffer_output(CodeBuffer * const codeBufferP, StringCode const code) { /*---------------------------------------------------------------------------- Output one GIF code to the file, through the code buffer. The code is represented as N bits in the file -- the lower N bits of 'code'. N is a the current code size of *codeBufferP. -----------------------------------------------------------------------------*/ assert (code <= codeBufferP->maxCode); codeBufferP->curAccum &= (1 << codeBufferP->curBits) - 1; if (codeBufferP->curBits > 0) codeBufferP->curAccum |= ((unsigned long)code << codeBufferP->curBits); else codeBufferP->curAccum = code; codeBufferP->curBits += codeBufferP->nBits; while (codeBufferP->curBits >= 8) { byteBuffer_out(codeBufferP->byteBufferP, codeBufferP->curAccum & 0xff); codeBufferP->curAccum >>= 8; codeBufferP->curBits -= 8; } ++codeBufferP->stringCount; } static void codeBuffer_flush(CodeBuffer * const codeBufferP) { /* Output the possible partial byte in the buffer */ if (codeBufferP->curBits > 0) { byteBuffer_out(codeBufferP->byteBufferP, codeBufferP->curAccum & 0xff); codeBufferP->curBits = 0; } byteBuffer_flush(codeBufferP->byteBufferP); byteBuffer_flushFile(codeBufferP->byteBufferP); if (verbose) pm_message("%u strings of pixels written to file", codeBufferP->stringCount); codeBufferP->stringCount = 0; } typedef struct { CodeBuffer * codeBufferP; /* The place to which we write our string codes. Constant. */ bool lzw; /* We're actually doing LZW compression. False means we follow the algorithm enough that an LZW decompressor will recover the proper data, but always using one code per pixel, and therefore not effecting any compression and not using the LZW patent. */ bool noclear; /* Never put a clear code in the output. Ergo don't recompute the string table from current input. When the string table fills up, continue using that table for the rest of the image. */ unsigned int hsize; /* The number of slots in the hash table. This variable to enhance overall performance by reducing memory use when encoding smaller gifs. */ unsigned int hshift; /* This is how many bits we shift left a string code in forming the primary hash of the concatenation of that string with another. Constant. */ /* Codes less than 'clearCode' are singleton pixel codes - each represents the pixel value equal to it. Codes greater than 'eofCode' are multipixel string codes. Each represents a string of pixels that is defined by the preceding stream. */ StringCode clearCode; /* The code in an LZW stream that means to clear the string dictionary and start fresh. Constant. */ StringCode eofCode; /* The code in an LZW stream that means there's no more coming Constant. */ StringCode initCodeLimit; /* The value of 'codeLimit' at the start of a block. Constant. */ StringCode codeLimit; /* One beyond the maximum code possible with the current code size. */ struct HashTableEntry * hashTable; StringCode nextCodeToDefine; /* The next string code the GIF protocol will define. It will do this the next time we emit a string code. */ StringCode stringSoFar; /* The code for the string we have built so far. This code indicates one or more pixels that we have encoded but not yet output because we're hoping to match an even longer string. Valid only when 'buildingString' is true. In the non-lzw case the single pixel to output. */ bool buildingString; /* We are in the middle of building a string; 'stringSoFar' describes the pixels in it so far. The only time this is false is at the very beginning of the stream. Ignored in the non-lzw case. */ bool reportedNoclear; /* We have reported to Standard Error that the string table filled up and we elected not to clear it. */ } LzwCompressor; static unsigned int nSignificantBits(unsigned int const arg){ #if HAVE_GCC_BITCOUNT return (arg == 0) ? 0 : 8 * sizeof(unsigned int) - __builtin_clz(arg); #else unsigned int i = 0; while (arg >> i != 0) ++i; return i; #endif } static LzwCompressor * lzw_create(FILE * const ofP, unsigned int const initBits, bool const lzw, bool const noclear, unsigned int const pixelCount) { unsigned int const hsizeTable[] = {257, 521, 1031, 2053, 4099, 5003}; /* If the image has 4096 or fewer pixels we use prime numbers slightly above powers of two between 8 and 12. In this case the hash table never fills up; clear code is never emitted. Above that we use a table with 4096 slots plus 20% extra. When this is not enough the clear code is emitted. Because of the extra 20% the table itself never fills up. lzw.hsize and lzw.hshift stay constant through the image. Variable hsize is a performance enhancement based on the fact that the encoder never needs more codes than the number of pixels in the image. Typically, the ratio of pixels to codes is around 10:1 to 20:1. Logic works with fixed values lzw.hsize=5003 and t=13. */ LzwCompressor * lzwP; MALLOCVAR_NOFAIL(lzwP); /* Constants */ lzwP->lzw = lzw; lzwP->noclear = noclear; lzwP->clearCode = 1 << (initBits - 1); lzwP->eofCode = lzwP->clearCode + 1; lzwP->initCodeLimit = 1 << initBits; if (lzw) { unsigned int const t = MIN(13, MAX(8, nSignificantBits(pixelCount +lzwP->eofCode - 2))); /* Index into hsizeTable */ lzwP->hsize = hsizeTable[t-8]; lzwP->hshift = (t == 13 ? 12 : t) - nSignificantBits(MAXCMAPSIZE-1); MALLOCARRAY(lzwP->hashTable, lzwP->hsize); if (lzwP->hashTable == NULL) pm_error("Couldn't get memory for %u-entry hash table.", lzwP->hsize); } else { /* No LZW compression. We don't need a stringcode hash table */ lzwP->hashTable = NULL; lzwP->hsize = 0; } lzwP->buildingString = FALSE; lzwP->codeBufferP = codeBuffer_create(ofP, initBits, lzw); lzwP->reportedNoclear = false; return lzwP; } static void lzw_destroy(LzwCompressor * const lzwP) { codeBuffer_destroy(lzwP->codeBufferP); free(lzwP->hashTable); free(lzwP); } static void lzwHashClear(LzwCompressor * const lzwP) { /* Empty the code table */ unsigned int i; for (i = 0; i < lzwP->hsize; ++i) lzwP->hashTable[i].present = false; lzwP->nextCodeToDefine = lzwP->clearCode + 2; } static void lzw_reportNoclear(LzwCompressor * const lzwP) { if (verbose && !lzwP->reportedNoclear) { pm_message("String table filled up. Not starting a new one " "because of noclear mode"); lzwP->reportedNoclear = true; } } static void lzw_clearBlock(LzwCompressor * const lzwP) { /*---------------------------------------------------------------------------- Insert a string table clear in the stream. Clear our table and set it up to start building again, and emit the code to tell the decoder we're doing it so he can do the same. -----------------------------------------------------------------------------*/ lzwHashClear(lzwP); codeBuffer_output(lzwP->codeBufferP, lzwP->clearCode); codeBuffer_resetCodeSize(lzwP->codeBufferP); lzwP->codeLimit = lzwP->initCodeLimit; } static void lzwAdjustCodeSize(LzwCompressor * const lzwP, StringCode const newCode) { /*---------------------------------------------------------------------------- Assuming we just defined code 'newCode', increase the code size as required so that this code fits. The decompressor is mimicking our assignment of that code, so knows that we are making this adjustment, so expects codes of the new size. -----------------------------------------------------------------------------*/ assert(newCode <= lzwP->codeLimit); if (newCode == lzwP->codeLimit) { lzwP->codeLimit *= 2; codeBuffer_increaseCodeSize(lzwP->codeBufferP); assert(lzwP->codeLimit <= maxCodeLimitLzw); } } static void lzwOutputCurrentString(LzwCompressor * const lzwP) { /*---------------------------------------------------------------------------- Put a code for the currently built-up string in the output stream. Doing this causes the protocol to define a new string code, defined as the string we put plus the first pixel of the next string we put. (It almost seems to violate causality, especially since the next string we put can legally be the very string code that gets defined here, but it actually works). The code that gets defined is lzwP->nextCodeToDefine. Caller is responsible for figuring out the value for the code (i.e. what we just said above) so it can use the code in the future. BUT: if the string table has reached its maximum size, issue a clear code instead to cause the protocol to forget all the defined string coes and start its table over (so Caller must start its own table over too). EXCEPT: if we're running in no-clear mode; then we skip the clear code (so the protocol maintains all the string code definitions and caller will have to do so as well). Note that in the non-compressing case, the overall limit is small enough to prevent us from ever defining string codes; we'll always issue the clear code. Note that there is a case where Caller can just ignore the fact that we cause the protocol to define a new string code: where the string we're outputting is the last one in the stream. In that case, the new string code we define is irrelevant; it will never be used. -----------------------------------------------------------------------------*/ codeBuffer_output(lzwP->codeBufferP, lzwP->stringSoFar); if (lzwP->nextCodeToDefine < lzwP->codeBufferP->maxCodeLimit) { /* Record that the protocol defined a new string code, to wit the numerically next one, when we did the output to the stream above, and adjust the code size if this code is too wide to fit in the current size. */ StringCode const newCode = lzwP->nextCodeToDefine++; lzwAdjustCodeSize(lzwP, newCode); } else { if (lzwP->noclear) lzw_reportNoclear(lzwP); else { /* Forget all the strings so far; start building again; tell decompressor to do the same. */ lzw_clearBlock(lzwP); if (verbose) pm_message("String table filled up. " "Clearing and starting over"); } } } static void lzw_flush(LzwCompressor * const lzwP) { if (lzwP->lzw) lzwOutputCurrentString(lzwP); /* Put out the code for the final string. */ codeBuffer_output(lzwP->codeBufferP, lzwP->eofCode); codeBuffer_flush(lzwP->codeBufferP); } static unsigned int primaryHash(StringCode const baseString, StringCode const additionalPixel, unsigned int const hshift) { unsigned int hash; assert(baseString < maxCodeLimitLzw); assert(additionalPixel < MAXCMAPSIZE); hash = (additionalPixel << hshift) ^ baseString; return hash; } static void lookupInHash(LzwCompressor * const lzwP, unsigned int const gifPixel, bool * const foundP, StringCode * const codeP, unsigned int * const hashP) { unsigned int disp; /* secondary hash stride (after G. Knott) */ unsigned int hash; /* Index into hash table */ hash = primaryHash(lzwP->stringSoFar, gifPixel, lzwP->hshift); assert(hash < lzwP->hsize); disp = (hash == 0) ? 1 : lzwP->hsize - hash; while (lzwP->hashTable[hash].present && (lzwP->hashTable[hash].baseString != lzwP->stringSoFar || lzwP->hashTable[hash].additionalPixel != gifPixel)) { if (hash < disp) hash += lzwP->hsize; assert(hash >= disp); hash -= disp; assert(hash < lzwP->hsize); } if (lzwP->hashTable[hash].present) { /* Found fcode in hash table */ *foundP = true; *codeP = lzwP->hashTable[hash].combinedString; } else { /* Found where it _should_ be (but it's not) with primary hash */ *foundP = false; *hashP = hash; } } static void lzw_encodePixel(LzwCompressor * const lzwP, unsigned int const gifPixel) { assert(gifPixel < 256); if (!lzwP->buildingString) { /* Start a new string with just this pixel */ lzwP->stringSoFar = gifPixel; lzwP->buildingString = true; } else { bool found; /* There's a code for the current string in the string table */ StringCode code; /* Existing code for the current string in the string table, if any */ unsigned int hash; /* Index into hash table where the entry for the new string code should go; meaningless if we don't need a new string code (because there's already one in the hash table for the current string) */ lookupInHash(lzwP, gifPixel, &found, &code, &hash); if (found) /* With this new pixel, it is still a known string; 'code' is its code */ lzwP->stringSoFar = code; else { /* We've found the longest prefix of the rest of the image for which we have a string code defined. Output the code for that prefix, thus defining a new string code in the protocol for possible later use. The new code is defined as this string plus the first pixel of the next string. But if there aren't any unused string codes left, outputting our string doesn't define any new string code. */ if (lzwP->nextCodeToDefine < lzwP->codeBufferP->maxCodeLimit) { lzwP->hashTable[hash].present = true; lzwP->hashTable[hash].baseString = lzwP->stringSoFar; lzwP->hashTable[hash].additionalPixel = gifPixel; lzwP->hashTable[hash].combinedString = lzwP->nextCodeToDefine; } lzwOutputCurrentString(lzwP); /* This singleton pixel starts the next string */ lzwP->stringSoFar = gifPixel; } } } /* * Algorithm: use open addressing double hashing (no chaining) on the * prefix code / next character combination. We do a variant of Knuth's * algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime * secondary probe. Here, the modular division first probe is gives way * to a faster exclusive-or manipulation. Also do block compression with * an adaptive reset, whereby the code table is cleared when the compression * ratio decreases, but after the table fills. The variable-length output * codes are re-sized at this point, and a special CLEAR code is generated * for the decompressor. Late addition: construct the table according to * file size for noticeable speed improvement on small files. Please direct * questions about this implementation to ames!jaw. */ static void writePixelUncompressed(LzwCompressor * const lzwP, unsigned int const gifPixel) { lzwP->stringSoFar = gifPixel; lzwOutputCurrentString(lzwP); } static void writeGlobalColorMap(FILE * const ofP, const struct Cmap * const cmapP, unsigned int const bitsPerPixel) { /*---------------------------------------------------------------------------- Write out the Global Color Map Note that the Global Color Map is always a power of two colors in size, but *cmapP could be smaller than that. So we pad with black. -----------------------------------------------------------------------------*/ unsigned int const colorMapSize = 1 << bitsPerPixel; struct pam pam; unsigned int i; tuple tupleRgb255; if (verbose) pm_message("Writing %u-entry global colormap for %u colors", colorMapSize, cmapP->cmapSize); pam = cmapP->pam; pam.size = PAM_STRUCT_SIZE(allocation_depth); pam.len = pam.size; pnm_setminallocationdepth(&pam, 3); tupleRgb255 = pnm_allocpamtuple(&pam); for (i = 0; i < colorMapSize; ++i) { if (i < cmapP->cmapSize) { tuple const color = cmapP->color[i]; assert(i < cmapP->cmapSize); pnm_scaletuple(&pam, tupleRgb255, color, 255); pnm_maketuplergb(&pam, tupleRgb255); fputc(tupleRgb255[PAM_RED_PLANE], ofP); fputc(tupleRgb255[PAM_GRN_PLANE], ofP); fputc(tupleRgb255[PAM_BLU_PLANE], ofP); } else { fputc(0, ofP); fputc(0, ofP); fputc(0, ofP); } } pnm_freepamtuple(tupleRgb255); } static void reportImageInfo(bool const interlace, unsigned int const background, unsigned int const bitsPerPixel) { if (verbose) { if (interlace) pm_message("interlaced"); else pm_message("not interlaced"); pm_message("Background color index = %u", background); pm_message("%u bits per pixel", bitsPerPixel); } } static void writeGifHeader(FILE * const ofP, unsigned int const width, unsigned int const height, unsigned int const background, unsigned int const bitsPerPixel, const struct Cmap * const cmapP, char const comment[], float const aspect) { unsigned int const resolution = bitsPerPixel; unsigned char b; /* Write the Magic header */ if (cmapP->haveTransparent || comment || aspect != 1.0 ) fwrite("GIF89a", 1, 6, ofP); else fwrite("GIF87a", 1, 6, ofP); /* Write out the screen width and height */ Putword(width, ofP); Putword(height, ofP); /* Indicate that there is a global color map */ b = 0x80; /* Yes, there is a color map */ /* OR in the resolution */ b |= (resolution - 1) << 4; /* OR in the Bits per Pixel */ b |= (bitsPerPixel - 1); /* Write it out */ fputc(b, ofP); /* Write out the Background color */ assert((unsigned char)background == background); fputc(background, ofP); { int const aspectValue = aspect == 1.0 ? 0 : ROUND(aspect * 64) - 15; assert(0 <= aspectValue && aspectValue <= 255); fputc(aspectValue, ofP); } writeGlobalColorMap(ofP, cmapP, bitsPerPixel); if (cmapP->haveTransparent) writeTransparentColorIndexExtension(ofP, cmapP->transparent); if (comment) writeCommentExtension(ofP, comment); } static void writeImageSeparator(FILE * const ofP) { fputc(',', ofP); } static void writeImageHeader(FILE * const ofP, unsigned int const leftOffset, unsigned int const topOffset, unsigned int const gWidth, unsigned int const gHeight, bool const gInterlace, unsigned int const initCodeSize) { Putword(leftOffset, ofP); Putword(topOffset, ofP); Putword(gWidth, ofP); Putword(gHeight, ofP); /* Write out whether or not the image is interlaced */ if (gInterlace) fputc(0x40, ofP); else fputc(0x00, ofP); /* Write out the initial code size */ fputc(initCodeSize, ofP); } static void writeRaster(struct pam * const pamP, RowReader * const rowReaderP, unsigned int const alphaPlane, unsigned int const alphaThreshold, const struct Cmap * const cmapP, unsigned int const initBits, FILE * const ofP, bool const lzw, bool const noclear) { /*---------------------------------------------------------------------------- Write the raster to file 'ofP'. Get the raster to write from 'rowReaderP', which gives tuples whose format is described by 'pamP'. 'alphaPlane' is the number of the plane in the tuples supplied by 'rowReaderP' that we should use for transparency information, and 'alphaThreshold' is the value in that plane below which we should consider the pixel transparent for GIF purposes. 'alphaPlane' is zero to indicate we should not use any plane as an alpha plane (so it's not possible to specify Plane 0 as alpha). Use the colormap 'cmapP' to generate the raster ('rowReaderP' gives pixel values as RGB samples; the GIF raster is colormap indices). Write the raster using LZW compression, or uncompressed depending on 'lzw'. If 'noclear', don't use any GIF clear codes in the output; i.e. don't recompute the string table from current input. Once the string table gets to maximum size, just keep using that table for the rest of the image. -----------------------------------------------------------------------------*/ LzwCompressor * lzwP; tuple * tuplerow; unsigned int nRowsDone; /* Number of rows we have read so far from the the input (the last of which is the one we're working on now). Note that in case of interlace, this is not the same thing as the row number of the current row. */ lzwP = lzw_create(ofP, initBits, lzw, noclear, pamP->height * pamP->width); tuplerow = pnm_allocpamrow(pamP); lzw_clearBlock(lzwP); nRowsDone = 0; while (nRowsDone < pamP->height) { unsigned int col; rowReader_read(rowReaderP, tuplerow); for (col = 0; col < pamP->width; ++col) { unsigned int const colorIndex = gifPixel(pamP, tuplerow[col], alphaPlane, alphaThreshold, cmapP); /* The value for the pixel in the GIF image. I.e. the colormap index. */ if (lzw) lzw_encodePixel(lzwP, colorIndex); else writePixelUncompressed(lzwP, colorIndex); } ++nRowsDone; } /* Gif is no good with no pixels; fortunately, that's impossible: */ assert(nRowsDone > 0); lzw_flush(lzwP); pnm_freepamrow(tuplerow); lzw_destroy(lzwP); } static void writeEndOfImage(FILE * const ofP) { /* An empty block marks the end of a series of blocks */ fputc(0, ofP); } static void writeGifImage(FILE * const ofP, unsigned int const leftOffset, unsigned int const topOffset, struct pam * const pamP, pm_filepos const rasterPos, bool const gInterlace, unsigned int const bitsPerPixel, bool const lzw, bool const noclear, bool const usingAlpha, const struct Cmap * const cmapP) { unsigned int const initCodeSize = bitsPerPixel <= 1 ? 2 : bitsPerPixel; /* The initial code size */ sample const alphaThreshold = (pamP->maxval + 1) / 2; /* Levels below this in the alpha plane indicate transparent pixels in the output image. */ unsigned int const alphaPlane = usingAlpha ? pamAlphaPlane(pamP) : 0; RowReader * rowReaderP; writeImageSeparator(ofP); writeImageHeader(ofP, leftOffset, topOffset, pamP->width, pamP->height, gInterlace, initCodeSize); rowReaderP = rowReader_create(pamP, rasterPos, gInterlace); writeRaster(pamP, rowReaderP, alphaPlane, alphaThreshold, cmapP, initCodeSize + 1, ofP, lzw, noclear); rowReader_destroy(rowReaderP); writeEndOfImage(ofP); } static void writeGifStreamTerminator(FILE * const ofP) { fputc(';', ofP); } static void writeGif(struct pam * const pamP, FILE * const ofP, pm_filepos const rasterPos, bool const gInterlace, int const background, unsigned int const bitsPerPixel, const struct Cmap * const cmapP, char const comment[], float const aspect, bool const lzw, bool const noclear, bool const usingAlpha) { /*---------------------------------------------------------------------------- 'usingAlpha' means use the alpha (transparency) plane, if there is one, to determine which GIF pixels are transparent. When this is true, the colormap *cmapP must contain a transparent entry. -----------------------------------------------------------------------------*/ unsigned int const leftOffset = 0; unsigned int const topOffset = 0; reportImageInfo(gInterlace, background, bitsPerPixel); if (pamP->width > 65535) pm_error("Image width %u too large for GIF format. (Max 65535)", pamP->width); if (pamP->height > 65535) pm_error("Image height %u too large for GIF format. (Max 65535)", pamP->height); writeGifHeader(ofP, pamP->width, pamP->height, background, bitsPerPixel, cmapP, comment, aspect); writeGifImage(ofP, leftOffset, topOffset, pamP, rasterPos, gInterlace, bitsPerPixel, lzw, noclear, usingAlpha, cmapP); writeGifStreamTerminator(ofP); } static void reportTransparent(enum TransparencyType const transType, const struct Cmap * const cmapP) { if (verbose) { switch (transType) { case TRANS_NONE: pm_message("Not making transparent pixels"); break; case TRANS_COLOR: pm_message("Making pixels of a certain color transparent"); break; case TRANS_ALPHA: pm_message("Making pixels transparent per input alpha mask"); break; } if (cmapP->haveTransparent) { tuple const color = cmapP->color[cmapP->transparent]; pm_message("Color %u (%lu, %lu, %lu) is transparent", cmapP->transparent, color[PAM_RED_PLANE], color[PAM_GRN_PLANE], color[PAM_BLU_PLANE]); } else pm_message("No transparent color"); } } static void computeTransparent(enum TransparencyType const transType, char const colorarg[], unsigned int const fakeTransparent, struct Cmap * const cmapP) { /*---------------------------------------------------------------------------- Figure out the color index (index into the colormap) of the color that is to be transparent in the GIF and set it in the colormap. 'transType' tells what the source of the transparency is. If 'transType' says all pixels of a single foreground color are to be transparent: 'colorarg' is the specification of that color. Its maxval is the maxval of the colormap. colorarg[] is a standard Netpbm color specification (e.g. "red", "#fefefe"), except that may have a "=" prefix, which means it specifies a particular exact color, as opposed to without the "=", which means "the color that is closest to this and actually in the image." Establish no transparent color if colorarg[] specifies an exact color and that color is not in the image. Also issue an informational message. If 'transType' says an input alpha channel will dtermine what pixels are transparent: 'fakeTransparent' is the special color map index for transparent pixels. -----------------------------------------------------------------------------*/ switch (transType) { case TRANS_COLOR: { const char * colorspec; bool exact; tuple transcolor; int found; int colorindex; assert(colorarg); if (colorarg[0] == '=') { colorspec = &colorarg[1]; exact = TRUE; } else { colorspec = colorarg; exact = FALSE; } transcolor = pnm_parsecolor(colorspec, cmapP->pam.maxval); pnm_lookuptuple(&cmapP->pam, cmapP->tuplehash, transcolor, &found, &colorindex); if (found) { cmapP->haveTransparent = TRUE; cmapP->transparent = colorindex; } else if (!exact) { cmapP->haveTransparent = TRUE; cmapP->transparent = closestColor(transcolor, &cmapP->pam, cmapP); } else { cmapP->haveTransparent = FALSE; pm_message("Warning: specified transparent color " "does not occur in image."); } } break; case TRANS_ALPHA: { cmapP->haveTransparent = TRUE; cmapP->transparent = fakeTransparent; } break; case TRANS_NONE: { cmapP->haveTransparent = FALSE; } break; } /* switch */ reportTransparent(transType, cmapP); } static unsigned int sortOrderColor(tuple const tuple) { return ((tuple[PAM_RED_PLANE] * MAXCMAPSIZE) + tuple[PAM_GRN_PLANE]) * MAXCMAPSIZE + tuple[PAM_BLU_PLANE]; } #ifndef LITERAL_FN_DEF_MATCH static qsort_comparison_fn sortCompareColor; #endif static int sortCompareColor(const void * const entry1P, const void * const entry2P) { struct tupleint * const * const tupleint1PP = entry1P; struct tupleint * const * const tupleint2PP = entry2P; return (sortOrderColor((*tupleint1PP)->tuple) - sortOrderColor((*tupleint2PP)->tuple)); } #ifndef LITERAL_FN_DEF_MATCH static qsort_comparison_fn sortCompareGray; #endif static int sortCompareGray(const void * const entry1P, const void * const entry2P){ struct tupleint * const * const tupleint1PP = entry1P; struct tupleint * const * const tupleint2PP = entry2P; return ((*tupleint1PP)->tuple[0] - (*tupleint2PP)->tuple[0]); } static void sortTupletable(struct pam * const mapPamP, unsigned int const colors, tupletable const tuplefreq) { /*---------------------------------------------------------------------------- Sort the colormap *cmapP. Sort the colormap by red intensity, then by green intensity, then by blue intensity. -----------------------------------------------------------------------------*/ pm_message("sorting colormap"); if (mapPamP->depth < 3) qsort(tuplefreq, colors, sizeof(tuplefreq[0]), sortCompareGray); else qsort(tuplefreq, colors, sizeof(tuplefreq[0]), sortCompareColor); } static void addToColormap(struct Cmap * const cmapP, const char * const colorspec, unsigned int * const newIndexP) { /*---------------------------------------------------------------------------- Add a new entry to the colormap. Make the color that specified by 'colorspec', and return the index of the new entry as *newIndexP. 'colorspec' is a color specification given by the user, e.g. "red" or "rgb:ff/03/0d". The maxval for this color specification is that for the colormap *cmapP. -----------------------------------------------------------------------------*/ tuple const transcolor = pnm_parsecolor(colorspec, cmapP->pam.maxval); unsigned int const colorIndex = cmapP->cmapSize++; cmapP->color[colorIndex] = pnm_allocpamtuple(&cmapP->pam); if (cmapP->pam.depth < 3) { if (!pnm_rgbtupleisgray(transcolor)) pm_error("Image is grayscale, but color '%s' is not gray. " "It is (%lu, %lu, %lu)", colorspec, transcolor[PAM_RED_PLANE], transcolor[PAM_GRN_PLANE], transcolor[PAM_BLU_PLANE]); else cmapP->color[colorIndex][0] = transcolor[0]; } else { pnm_assigntuple(&cmapP->pam, cmapP->color[colorIndex], transcolor); } *newIndexP = colorIndex; } static void colormapFromFile(char const filespec[], unsigned int const maxcolors, tupletable * const tupletableP, struct pam * const mapPamP, unsigned int * const colorCountP) { /*---------------------------------------------------------------------------- Read a colormap from the Netpbm file filespec[]. Return a tupletable of the colors in it (which is practically a colormap) as *tupletableP and the format of those tuples as *mapPamP. Return the number of colors as *colorsCountP. -----------------------------------------------------------------------------*/ FILE * mapfileP; tuple ** colors; unsigned int colorCount; mapfileP = pm_openr(filespec); colors = pnm_readpam(mapfileP, mapPamP, PAM_STRUCT_SIZE(tuple_type)); pm_close(mapfileP); pm_message("computing other colormap ..."); *tupletableP = pnm_computetuplefreqtable(mapPamP, colors, maxcolors, &colorCount); *colorCountP = colorCount; pnm_freepamarray(colors, mapPamP); } static void readAndValidateColormapFromFile(char const filename[], unsigned int const maxcolors, tupletable * const tuplefreqP, struct pam * const mapPamP, unsigned int * const colorCountP, unsigned int const nInputComp, sample const inputMaxval) { /*---------------------------------------------------------------------------- Read the colormap from a separate colormap file named filename[], and make sure it's consistent with an image with 'nInputComp' color components (e.g. 3 for RGB) and a maxval of 'inputMaxval'. -----------------------------------------------------------------------------*/ colormapFromFile(filename, maxcolors, tuplefreqP, mapPamP, colorCountP); if (mapPamP->depth != nInputComp) pm_error("Depth of map file (%u) does not match number of " "color components in input file (%u)", mapPamP->depth, nInputComp); if (mapPamP->maxval != inputMaxval) pm_error("Maxval of map file (%lu) does not match maxval of " "input file (%lu)", mapPamP->maxval, inputMaxval); } static void computeColormapBw(struct pam * const pamP, struct pam * const mapPamP, unsigned int * const colorCountP, tupletable * const tuplefreqP) { /*---------------------------------------------------------------------------- Shortcut for black and white (e.g. PBM). We know that there are only two colors. Users who know that only one color is present in the image should specify -sort at the command line. Example: $ pbmmake -w 600 400 | pamtogif -sort > canvas.gif -----------------------------------------------------------------------------*/ tupletable const colormap = pnm_alloctupletable(pamP, 2); *mapPamP = *pamP; mapPamP->depth = 1; colormap[0]->value = 1; colormap[0]->tuple[0] = PAM_BLACK; colormap[1]->value = 1; colormap[1]->tuple[0] = PAM_BW_WHITE; *tuplefreqP = colormap; *colorCountP = 2; } static void computeColormapFromInput(struct pam * const pamP, unsigned int const maxcolors, unsigned int const nInputComp, struct pam * const mapPamP, unsigned int * const colorCountP, tupletable * const tuplefreqP) { tupletable tuplefreq; pm_message("computing colormap..."); tuplefreq = pnm_computetuplefreqtable3( pamP, NULL, maxcolors, nInputComp, pamP->maxval, colorCountP); *mapPamP = *pamP; mapPamP->depth = nInputComp; *tuplefreqP = tuplefreq; } static void computeLibnetpbmColormap(struct pam * const pamP, enum TransparencyType const transType, const char * const mapfile, tuple * const color, tuplehash * const tuplehashP, struct pam * const mapPamP, unsigned int * const colorCountP, bool const sort) { /*---------------------------------------------------------------------------- Compute a colormap, libnetpbm style, for the image described by 'pamP', which is positioned to the raster. If 'mapfile' is non-null, Use the colors in that (Netpbm) file for the color map instead of the colors in 'pamP'. Return the colormap as color[] and *tuplehashP. Return the format of those tuples as *mapPamP. The tuples of the color map have a meaningful depth of 1 (grayscale) or 3 (color) and *mapPamP reflects that. While we're at it, count the colors and validate that there aren't too many. Return the count as *colorCountP. In determining if there are too many, allow one slot for a fake transparency color if 'transType' is 'TRANS_ALPHA'. If there are too many, issue an error message and abort the program. 'sort' means to sort the colormap by red intensity, then by green intensity, then by blue intensity, as opposed to arbitrary order. -----------------------------------------------------------------------------*/ unsigned int const maxcolors = transType == TRANS_ALPHA ? MAXCMAPSIZE - 1 : MAXCMAPSIZE; /* The most colors we can tolerate in the image. If we have our own made-up entry in the colormap for transparency, it isn't included in this count. */ unsigned int const nInputComp = pamAlphaPlane(pamP) ? pamP->depth - 1 : pamP->depth; /* Number of color components (not alpha) in the input image */ unsigned int i; tupletable tuplefreq; unsigned int colorCount; if (mapfile) readAndValidateColormapFromFile(mapfile, maxcolors, &tuplefreq, mapPamP, &colorCount, nInputComp, pamP->maxval); else if (nInputComp == 1 && pamP->maxval == 1 && !sort && pamP->height * pamP->width > 1) computeColormapBw(pamP, mapPamP, &colorCount, &tuplefreq); else computeColormapFromInput(pamP, maxcolors, nInputComp, mapPamP, &colorCount, &tuplefreq); if (tuplefreq == NULL) pm_error("too many colors - try doing a 'pnmquant %u'", maxcolors); pm_message("%u colors found", colorCount); if (sort) sortTupletable(mapPamP, colorCount, tuplefreq); for (i = 0; i < colorCount; ++i) { color[i] = pnm_allocpamtuple(mapPamP); pnm_assigntuple(mapPamP, color[i], tuplefreq[i]->tuple); } /* And make a hash table for fast lookup. */ *tuplehashP = pnm_computetupletablehash(mapPamP, tuplefreq, colorCount); *colorCountP = colorCount; pnm_freetupletable(mapPamP, tuplefreq); } static void destroyCmap(struct Cmap * const cmapP) { unsigned int colorIndex; for (colorIndex = 0; colorIndex < cmapP->cmapSize; ++colorIndex) pnm_freepamtuple(cmapP->color[colorIndex]); pnm_destroytuplehash(cmapP->tuplehash); } int main(int argc, const char ** argv) { struct CmdlineInfo cmdline; FILE * ifP; struct pam pam; unsigned int bitsPerPixel; pm_filepos rasterPos; struct Cmap cmap; /* The colormap, with all its accessories */ enum TransparencyType transType; unsigned int fakeTransparent; /* colormap index of the fake transparency color we're using to implement the alpha mask. Defined only if 'transType' is TRANS_ALPHA. */ pm_proginit(&argc, argv); parseCommandLine(argc, argv, &cmdline); verbose = cmdline.verbose; ifP = pm_openr_seekable(cmdline.input_filespec); pnm_readpaminit(ifP, &pam, PAM_STRUCT_SIZE(tuple_type)); pm_tell2(ifP, &rasterPos, sizeof(rasterPos)); transType = cmdline.transparent ? TRANS_COLOR : pamAlphaPlane(&pam) ? TRANS_ALPHA : TRANS_NONE; computeLibnetpbmColormap(&pam, transType, cmdline.mapfile, cmap.color, &cmap.tuplehash, &cmap.pam, &cmap.cmapSize, cmdline.sort); assert(cmap.pam.maxval == pam.maxval); if (transType == TRANS_ALPHA) { /* Add a fake entry to the end of the colormap for transparency. Make its color black. */ addToColormap(&cmap, cmdline.alphacolor, &fakeTransparent); } bitsPerPixel = cmap.cmapSize == 1 ? 1 : nSignificantBits(cmap.cmapSize-1); computeTransparent(transType, cmdline.transparent, fakeTransparent, &cmap); writeGif(&pam, stdout, rasterPos, cmdline.interlace, 0, bitsPerPixel, &cmap, cmdline.comment, cmdline.aspect, !cmdline.nolzw, cmdline.noclear, transType==TRANS_ALPHA); destroyCmap(&cmap); pm_close(ifP); pm_close(stdout); return 0; } /*============================================================================ Original version, named 'ppmgif' was by Jef Poskanzer in 1989, based on GIFENCOD by David Rowley .A Lempel-Zim compression based on "compress". Switched to use libnetpbm PAM facilities (ergo process PAM images) and renamed 'pamtogif' by Bryan Henderson November 2006. The non-LZW GIF generation stuff was adapted from the Independent JPEG Group's djpeg on 2001.09.29. In 2006.12 the output subroutines were rewritten; now no uncompressed output subroutines are derived from the Independent JPEG Group's source code. 2007.01 Changed sort routine to qsort. (afu) 2007.03 Implemented variable hash table size, PBM color table shortcut and "-aspect" command line option. (afu) Copyright (C) 1989 by Jef Poskanzer. Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation. This software is provided "as is" without express or implied warranty. The Graphics Interchange Format(c) is the Copyright property of CompuServe Incorporated. GIF(sm) is a Service Mark property of CompuServe Incorporated. ============================================================================*/