/* ppmtogif.c - read a portable pixmap and produce a GIF file ** ** Based on GIFENCOD by David Rowley .A ** Lempel-Zim compression based on "compress". ** ** Modified by Marcel Wijkstra ** ** The non-LZW GIF generation stuff was adapted from the Independent ** JPEG Group's djpeg on 2001.09.29. The uncompressed output subroutines ** are derived directly from the corresponding subroutines in djpeg's ** wrgif.c source file. Its copyright notice say: * Copyright (C) 1991-1997, Thomas G. Lane. * This file is part of the Independent JPEG Group's software. * For conditions of distribution and use, see the accompanying README file. * The reference README file is README.JPEG in the Netpbm package. ** ** 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. */ /* TODO: merge the LZW and uncompressed subroutines. They are separate only because they had two different lineages and the code is too complicated for me quickly to rewrite it. */ #include #include "ppm.h" #include "shhopt.h" #define MAXCMAPSIZE 256 /* * a code_int must be able to hold 2**BITS values of type int, and also -1 */ typedef int code_int; #ifdef SIGNED_COMPARE_SLOW typedef unsigned long int count_int; typedef unsigned short int count_short; #else /*SIGNED_COMPARE_SLOW*/ typedef long int count_int; #endif /*SIGNED_COMPARE_SLOW*/ struct cmap { /* This is the information for the GIF colormap (aka palette). */ int red[MAXCMAPSIZE], green[MAXCMAPSIZE], blue[MAXCMAPSIZE]; /* These arrays arrays map a color index, as is found in the raster part of the GIF, to an intensity value for the indicated RGB component. */ int perm[MAXCMAPSIZE], permi[MAXCMAPSIZE]; /* perm[i] is the position in the sorted colormap of the color which is at position i in the unsorted colormap. permi[] is the inverse function of perm[]. */ int cmapsize; /* Number of entries in the GIF colormap. I.e. number of colors in the image, plus possibly one fake transparency color. */ int transparent; /* color index number in GIF palette of the color that is to be transparent. -1 if no color is transparent. */ colorhash_table cht; /* A hash table that relates a PPM pixel value to to a pre-sort GIF colormap index. */ pixval maxval; /* The maxval for the colors in 'cht'. */ }; struct gif_dest { /* This structure controls output of uncompressed GIF raster */ /* State for packing variable-width codes into a bitstream */ int n_bits; /* current number of bits/code */ int maxcode; /* maximum code, given n_bits */ int cur_accum; /* holds bits not yet output */ int cur_bits; /* # of bits in cur_accum */ /* State for GIF code assignment */ int ClearCode; /* clear code (doesn't change) */ int EOFCode; /* EOF code (ditto) */ int code_counter; /* counts output symbols */ }; struct cmdline_info { /* 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 *alpha_filespec; /* Filespec of alpha file; NULL if none */ 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 */ }; static void handleLatex2htmlHack(void) { /*---------------------------------------------------------------------------- This program used to put out a "usage" message when it saw an option it didn't understand. Latex2html's configure program does a ppmtogif -h (-h was never a valid option) to elicit that message and then parses the message to see if it included the strings "-interlace" and "-transparent". That way it knows if the 'ppmtogif' program it found has those options or not. I don't think any 'ppmtogif' you're likely to find today lacks those options, but latex2html checks anyway, and we don't want it to conclude that we don't have them. So we issue a special error message just to trick latex2html into deciding that we have -interlace and -transparent options. The function is not documented in the man page. We would like to see Latex2html either stop checking or check like configure programs usually do -- try the option and see if you get success or failure. -Bryan 2001.11.14 -----------------------------------------------------------------------------*/ pm_error("latex2html, you should just try the -interlace and " "-transparent options to see if they work instead of " "expecting a 'usage' message from -h"); } static void parse_command_line(int argc, char ** argv, struct cmdline_info *cmdline_p) { /*---------------------------------------------------------------------------- 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. -----------------------------------------------------------------------------*/ optStruct3 opt; /* set by OPTENT3 */ optEntry *option_def = malloc(100*sizeof(optEntry)); unsigned int option_def_index; unsigned int latex2htmlhack; option_def_index = 0; /* incremented by OPTENT3 */ OPTENT3(0, "interlace", OPT_FLAG, NULL, &cmdline_p->interlace, 0); OPTENT3(0, "sort", OPT_FLAG, NULL, &cmdline_p->sort, 0); OPTENT3(0, "nolzw", OPT_FLAG, NULL, &cmdline_p->nolzw, 0); OPTENT3(0, "mapfile", OPT_STRING, &cmdline_p->mapfile, NULL, 0); OPTENT3(0, "transparent", OPT_STRING, &cmdline_p->transparent, NULL, 0); OPTENT3(0, "comment", OPT_STRING, &cmdline_p->comment, NULL, 0); OPTENT3(0, "alpha", OPT_STRING, &cmdline_p->alpha_filespec, NULL, 0); OPTENT3(0, "alphacolor", OPT_STRING, &cmdline_p->alphacolor, NULL, 0); OPTENT3(0, "h", OPT_FLAG, NULL, &latex2htmlhack, 0); /* Set the defaults */ cmdline_p->mapfile = NULL; cmdline_p->transparent = NULL; /* no transparency */ cmdline_p->comment = NULL; /* no comment */ cmdline_p->alpha_filespec = NULL; /* no alpha file */ cmdline_p->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 */ optParseOptions3(&argc, argv, opt, sizeof(opt), 0); /* Uses and sets argc, argv, and some of *cmdline_p and others. */ if (latex2htmlhack) handleLatex2htmlHack(); if (argc-1 == 0) cmdline_p->input_filespec = "-"; else if (argc-1 != 1) pm_error("Program takes zero or one argument (filename). You " "specified %d", argc-1); else cmdline_p->input_filespec = argv[1]; if (cmdline_p->alpha_filespec && cmdline_p->transparent) pm_error("You cannot specify both -alpha and -transparent."); } static int __inline__ sqr(const int x) { return x*x; } /* * 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 __inline__ closestcolor(const pixel color, pixval const maxval, struct cmap * cmapP) { /*---------------------------------------------------------------------------- Return the pre-sort colormap index of the color in the colormap *cmapP that is closest to the color 'color', whose maxval is 'maxval'. 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. -----------------------------------------------------------------------------*/ int i,r,g,b; int imin,dmin; r=(int)PPM_GETR(color)*255/maxval; g=(int)PPM_GETG(color)*255/maxval; b=(int)PPM_GETB(color)*255/maxval; dmin = sqr(255)*3; imin = 0; for (i=0;i < cmapP->cmapsize; i++) { int d; d=sqr(r-cmapP->red[i])+sqr(g-cmapP->green[i])+sqr(b-cmapP->blue[i]); if (dcht,&color,cmapP->permi[imin]); return cmapP->permi[imin]; } static __inline__ int GetPixel(pixel ** const pixels, pixval const input_maxval, gray ** const alpha, gray const alpha_threshold, struct cmap * const cmapP, int const x, int const y) { /*---------------------------------------------------------------------------- Return the colormap index of the pixel at location (x,y) in the PPM raster 'pixels', using colormap *cmapP. -----------------------------------------------------------------------------*/ int colorindex; if (alpha && alpha[y][x] < alpha_threshold) colorindex = cmapP->transparent; else { int presort_colorindex; presort_colorindex = ppm_lookupcolor(cmapP->cht, &pixels[y][x]); if (presort_colorindex == -1) presort_colorindex = closestcolor(pixels[y][x], input_maxval, cmapP); colorindex = cmapP->perm[presort_colorindex]; } return colorindex; } #define TRUE 1 #define FALSE 0 static int Width, Height; static int curx, cury; static long CountDown; static int Pass = 0; static int Interlace; /* * Bump the 'curx' and 'cury' to point to the next pixel */ static void BumpPixel() { /* * Bump the current X position */ ++curx; /* * If we are at the end of a scan line, set curx back to the beginning * If we are interlaced, bump the cury to the appropriate spot, * otherwise, just increment it. */ if( curx == Width ) { curx = 0; if( !Interlace ) ++cury; else { switch( Pass ) { case 0: cury += 8; if( cury >= Height ) { ++Pass; cury = 4; } break; case 1: cury += 8; if( cury >= Height ) { ++Pass; cury = 2; } break; case 2: cury += 4; if( cury >= Height ) { ++Pass; cury = 1; } break; case 3: cury += 2; break; } } } } static __inline__ int GIFNextPixel(pixel ** const pixels, pixval const input_maxval, gray ** const alpha, gray const alpha_threshold, struct cmap * const cmapP) { /*---------------------------------------------------------------------------- Return the pre-sort color index (index into the unsorted GIF color map) of the next pixel to be processed from the input image. 'alpha_threshold' is the gray level such that a pixel in the alpha map whose value is less that that represents a transparent pixel in the output. -----------------------------------------------------------------------------*/ int r; if( CountDown == 0 ) return EOF; --CountDown; r = GetPixel(pixels, input_maxval, alpha, alpha_threshold, cmapP, curx, cury); BumpPixel(); return r; } static void write_transparent_color_index_extension(FILE *fp, const int Transparent) { /*---------------------------------------------------------------------------- Write out extension for transparent color index. -----------------------------------------------------------------------------*/ fputc( '!', fp ); fputc( 0xf9, fp ); fputc( 4, fp ); fputc( 1, fp ); fputc( 0, fp ); fputc( 0, fp ); fputc( Transparent, fp ); fputc( 0, fp ); } static void write_comment_extension(FILE *fp, const char comment[]) { /*---------------------------------------------------------------------------- Write out extension for a comment -----------------------------------------------------------------------------*/ char *segment; fputc('!', fp); /* Identifies an extension */ fputc(0xfe, fp); /* Identifies a comment */ /* Write it out in segments no longer than 255 characters */ for (segment = (char *) comment; segment < comment+strlen(comment); segment += 255) { const int length_this_segment = MIN(255, strlen(segment)); fputc(length_this_segment, fp); fwrite(segment, 1, length_this_segment, fp); } fputc(0, fp); /* 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 #define HSIZE 5003 /* 80% occupancy */ #ifdef NO_UCHAR typedef char char_type; #else /*NO_UCHAR*/ typedef unsigned char char_type; #endif /*NO_UCHAR*/ /* * * 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) * */ #include #define ARGVAL() (*++(*argv) || (--argc && *++argv)) static int n_bits; /* number of bits/code */ static int maxbits = BITS; /* user settable max # bits/code */ static code_int maxcode; /* maximum code, given n_bits */ static code_int maxmaxcode = (code_int)1 << BITS; /* should NEVER generate this code */ #ifdef COMPATIBLE /* But wrong! */ # define MAXCODE(n_bits) ((code_int) 1 << (n_bits) - 1) #else /*COMPATIBLE*/ # define MAXCODE(n_bits) (((code_int) 1 << (n_bits)) - 1) #endif /*COMPATIBLE*/ static count_int htab [HSIZE]; static unsigned short codetab [HSIZE]; #define HashTabOf(i) htab[i] #define CodeTabOf(i) codetab[i] static code_int hsize = HSIZE; /* for dynamic table sizing */ /* * To save much memory, we overlay the table used by compress() with those * used by decompress(). The tab_prefix table is the same size and type * as the codetab. The tab_suffix table needs 2**BITS characters. We * get this from the beginning of htab. The output stack uses the rest * of htab, and contains characters. There is plenty of room for any * possible stack (stack used to be 8000 characters). */ #define tab_prefixof(i) CodeTabOf(i) #define tab_suffixof(i) ((char_type*)(htab))[i] #define de_stack ((char_type*)&tab_suffixof((code_int)1< 0 ) { fputc( a_count, g_outfile ); fwrite( accum, 1, a_count, g_outfile ); a_count = 0; } } static void char_out( int const c ) { /*---------------------------------------------------------------------------- Add a character to the end of the current packet, and if it is 254 character, flush the packet to the output file. -----------------------------------------------------------------------------*/ accum[ a_count++ ] = c; if( a_count >= 254 ) flush_char(); } static unsigned long const masks[] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; static void output( code_int const code, FILE * const outfile ) { /*---------------------------------------------------------------------------- Output one GIF code to the file. The code is represented as cur_bits+n_bits/8 bytes in the file. * Inputs: * code: A n_bits-bit integer. If == -1, then EOF. This assumes * that n_bits =< (long)wordsize - 1. * Assumptions: * Chars are 8 bits long. * Algorithm: * Maintain a BITS character long buffer (so that 8 codes will * fit in it exactly). Use the VAX insv instruction to insert each * code in turn. When the buffer fills up empty it and start over. -----------------------------------------------------------------------------*/ /* Note: this initialization of static variables by the loader must go if we want to use the compressor more than once per invocation (e.g. we want to create a multi-image gif). */ static unsigned long cur_accum = 0; static int cur_bits = 0; g_outfile = outfile; cur_accum &= masks[ cur_bits ]; if( cur_bits > 0 ) cur_accum |= ((long)code << cur_bits); else cur_accum = code; cur_bits += n_bits; while( cur_bits >= 8 ) { char_out( (unsigned int)(cur_accum & 0xff) ); cur_accum >>= 8; cur_bits -= 8; } /* * If the next entry is going to be too big for the code size, * then increase it, if possible. */ if ( free_ent > maxcode || clear_flg ) { if( clear_flg ) { maxcode = MAXCODE (n_bits = g_init_bits); clear_flg = 0; } else { ++n_bits; if ( n_bits == maxbits ) maxcode = maxmaxcode; else maxcode = MAXCODE(n_bits); } } if( code == EOFCode ) { /* * At EOF, write the rest of the buffer. */ while( cur_bits > 0 ) { char_out( (unsigned int)(cur_accum & 0xff) ); cur_accum >>= 8; cur_bits -= 8; /* ?? what if cur_bits < 8? Apparently only an issue if we extend this program to do multiple images */ } flush_char(); fflush( g_outfile ); if( ferror( g_outfile ) ) pm_error( "error writing output file" ); } } static void cl_hash(hsize) /* reset code table */ register count_int hsize; { register count_int *htab_p = htab+hsize; register long i; register long m1 = -1; i = hsize - 16; do { /* might use Sys V memset(3) here */ *(htab_p-16) = m1; *(htab_p-15) = m1; *(htab_p-14) = m1; *(htab_p-13) = m1; *(htab_p-12) = m1; *(htab_p-11) = m1; *(htab_p-10) = m1; *(htab_p-9) = m1; *(htab_p-8) = m1; *(htab_p-7) = m1; *(htab_p-6) = m1; *(htab_p-5) = m1; *(htab_p-4) = m1; *(htab_p-3) = m1; *(htab_p-2) = m1; *(htab_p-1) = m1; htab_p -= 16; } while ((i -= 16) >= 0); for ( i += 16; i > 0; --i ) *--htab_p = m1; } /* * Clear out the hash table */ static void cl_block (FILE * const outfile) /* table clear for block compress */ { cl_hash ( (count_int) hsize ); free_ent = ClearCode + 2; clear_flg = 1; output( (code_int)ClearCode, outfile ); } static void write_raster_LZW(pixel ** const pixels, pixval const input_maxval, gray ** const alpha, gray const alpha_maxval, struct cmap * const cmapP, int const init_bits, FILE * const outfile) { /*---------------------------------------------------------------------------- Write the raster to file 'outfile'. The raster to write is 'pixels', which has maxval 'input_maxval', modified by alpha mask 'alpha', which has maxval 'alpha_maxval'. Use the colormap 'cmapP' to generate the raster ('pixels' is composed of RGB samples; the GIF raster is colormap indices). Write the raster using LZW compression. -----------------------------------------------------------------------------*/ code_int ent; code_int disp; code_int hsize_reg; int hshift; bool eof; gray const alpha_threshold = (alpha_maxval + 1) / 2; /* gray levels below this in the alpha mask indicate transparent pixels in the output image. */ /* * Set up the globals: g_init_bits - initial number of bits * g_outfile - pointer to output file */ g_init_bits = init_bits; /* * Set up the necessary values */ offset = 0; out_count = 0; clear_flg = 0; in_count = 1; maxcode = MAXCODE(n_bits = g_init_bits); ClearCode = (1 << (init_bits - 1)); EOFCode = ClearCode + 1; free_ent = ClearCode + 2; char_init(); ent = GIFNextPixel(pixels, input_maxval, alpha, alpha_threshold, cmapP); { long fcode; hshift = 0; for ( fcode = (long) hsize; fcode < 65536L; fcode *= 2L ) ++hshift; hshift = 8 - hshift; /* set hash code range bound */ } hsize_reg = hsize; cl_hash( (count_int) hsize_reg); /* clear hash table */ output( (code_int)ClearCode, outfile ); eof = FALSE; while (!eof) { int gifpixel; /* The value for the pixel in the GIF image. I.e. the colormap index. Or -1 to indicate "no more pixels." */ gifpixel = GIFNextPixel(pixels, input_maxval, alpha, alpha_threshold, cmapP); if (gifpixel == EOF) eof = TRUE; if (!eof) { long const fcode = (long) (((long) gifpixel << maxbits) + ent); code_int i; /* xor hashing */ ++in_count; i = (((code_int)gifpixel << hshift) ^ ent); if ( HashTabOf (i) == fcode ) { ent = CodeTabOf (i); continue; } else if ( (long)HashTabOf (i) < 0 ) /* empty slot */ goto nomatch; disp = hsize_reg - i; /* secondary hash (after G. Knott) */ if ( i == 0 ) disp = 1; probe: if ( (i -= disp) < 0 ) i += hsize_reg; if ( HashTabOf (i) == fcode ) { ent = CodeTabOf (i); continue; } if ( (long)HashTabOf (i) > 0 ) goto probe; nomatch: output ( (code_int) ent, outfile ); ++out_count; ent = gifpixel; if ( free_ent < maxmaxcode ) { /* } */ CodeTabOf (i) = free_ent++; /* code -> hashtable */ HashTabOf (i) = fcode; } else cl_block(outfile); } } /* * Put out the final code. */ output( (code_int)ent, outfile ); ++out_count; output( (code_int) EOFCode, outfile ); } /* Routine to convert variable-width codes into a byte stream */ static void output_uncompressed(struct gif_dest * const dinfo, int const code, FILE * const outfile) { g_outfile = outfile; /* Emit a code of n_bits bits */ /* Uses cur_accum and cur_bits to reblock into 8-bit bytes */ dinfo->cur_accum |= ((int) code) << dinfo->cur_bits; dinfo->cur_bits += dinfo->n_bits; while (dinfo->cur_bits >= 8) { char_out(dinfo->cur_accum & 0xFF); dinfo->cur_accum >>= 8; dinfo->cur_bits -= 8; } } static void write_raster_uncompressed_init (struct gif_dest * const dinfo, int const i_bits, FILE * const outfile) /* Initialize pseudo-compressor */ { /* init all the state variables */ dinfo->n_bits = i_bits; dinfo->maxcode = MAXCODE(dinfo->n_bits); dinfo->ClearCode = (1 << (i_bits - 1)); dinfo->EOFCode = dinfo->ClearCode + 1; dinfo->code_counter = dinfo->ClearCode + 2; /* init output buffering vars */ dinfo->cur_accum = 0; dinfo->cur_bits = 0; /* GIF specifies an initial Clear code */ output_uncompressed(dinfo, dinfo->ClearCode, outfile); } static void write_raster_uncompressed_pixel (struct gif_dest * const dinfo, int const c, FILE * const outfile) /* Accept and "compress" one pixel value. * The given value must be less than n_bits wide. */ { /* Output the given pixel value as a symbol. */ output_uncompressed(dinfo, c, outfile); /* Issue Clear codes often enough to keep the reader from ratcheting up * its symbol size. */ if (dinfo->code_counter < dinfo->maxcode) { dinfo->code_counter++; } else { output_uncompressed(dinfo, dinfo->ClearCode, outfile); dinfo->code_counter = dinfo->ClearCode + 2; /* reset the counter */ } } static void write_raster_uncompressed_term (struct gif_dest * const dinfo, FILE * const outfile) /* Clean up at end */ { /* Send an EOF code */ output_uncompressed(dinfo, dinfo->EOFCode, outfile); /* Flush the bit-packing buffer */ if (dinfo->cur_bits > 0) { char_out(dinfo->cur_accum & 0xFF); } /* Flush the packet buffer */ flush_char(); } static void write_raster_uncompressed(pixel ** const pixels, pixval const input_maxval, gray ** const alpha, gray const alpha_maxval, struct cmap * const cmapP, int const init_bits, FILE * const outfile) { /*---------------------------------------------------------------------------- Write the raster to file 'outfile'. Same as write_raster_LZW(), except written out one code per pixel (plus some clear codes), so no compression. And no use of the LZW patent. -----------------------------------------------------------------------------*/ gray const alpha_threshold = (alpha_maxval + 1) / 2; /* gray levels below this in the alpha mask indicate transparent pixels in the output image. */ bool eof; struct gif_dest gif_dest; write_raster_uncompressed_init(&gif_dest, init_bits, outfile); g_outfile = outfile; eof = FALSE; while (!eof) { int gifpixel; /* The value for the pixel in the GIF image. I.e. the colormap index. Or -1 to indicate "no more pixels." */ gifpixel = GIFNextPixel(pixels, input_maxval, alpha, alpha_threshold, cmapP); if (gifpixel == EOF) eof = TRUE; if (!eof) { write_raster_uncompressed_pixel(&gif_dest, gifpixel, outfile); } } write_raster_uncompressed_term(&gif_dest, outfile); } /****************************************************************************** * * GIF Specific routines * *****************************************************************************/ static void writeGifHeader(FILE * const fp, int const Width, int const Height, int const GInterlace, int const Background, int const BitsPerPixel, struct cmap * const cmapP, const char comment[]) { int B; int const Resolution = BitsPerPixel; int const ColorMapSize = 1 << BitsPerPixel; /* Calculate number of bits we are expecting */ CountDown = (long)Width * (long)Height; /* Indicate which pass we are on (if interlace) */ Pass = 0; /* Set up the current x and y position */ curx = cury = 0; /* Write the Magic header */ if (cmapP->transparent != -1 || comment) fwrite("GIF89a", 1, 6, fp); else fwrite("GIF87a", 1, 6, fp); /* Write out the screen width and height */ Putword( Width, fp ); Putword( Height, fp ); /* Indicate that there is a global colour 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, fp ); /* Write out the Background color */ fputc( Background, fp ); /* Byte of 0's (future expansion) */ fputc( 0, fp ); { /* 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. */ int i; for ( i=0; i < ColorMapSize; ++i ) { if ( i < cmapP->cmapsize ) { fputc( cmapP->red[i], fp ); fputc( cmapP->green[i], fp ); fputc( cmapP->blue[i], fp ); } else { fputc( 0, fp ); fputc( 0, fp ); fputc( 0, fp ); } } } if ( cmapP->transparent >= 0 ) write_transparent_color_index_extension(fp, cmapP->transparent); if ( comment ) write_comment_extension(fp, comment); } static void GIFEncode(FILE * const fp, pixel ** const pixels, pixval const input_maxval, int const GWidth, int const GHeight, gray ** const alpha, gray const alpha_maxval, int const GInterlace, int const Background, int const BitsPerPixel, struct cmap * const cmapP, const char comment[], bool const nolzw) { int const LeftOfs = 0; int const TopOfs = 0; int InitCodeSize; writeGifHeader(fp, GWidth, GHeight, GInterlace, Background, BitsPerPixel, cmapP, comment); /* Write an Image separator */ fputc( ',', fp ); /* Write the Image header */ Putword( LeftOfs, fp ); Putword( TopOfs, fp ); Putword( GWidth, fp ); Putword( GHeight, fp ); /* Write out whether or not the image is interlaced */ if( GInterlace ) fputc( 0x40, fp ); else fputc( 0x00, fp ); /* The initial code size */ if( BitsPerPixel <= 1 ) InitCodeSize = 2; else InitCodeSize = BitsPerPixel; /* Write out the initial code size */ fputc( InitCodeSize, fp ); /* Set some global variables for BumpPixel() */ Interlace = GInterlace; Width = GWidth; Height = GHeight; /* Write the actual raster */ if (nolzw) write_raster_uncompressed(pixels, input_maxval, alpha, alpha_maxval, cmapP, InitCodeSize+1, fp); else write_raster_LZW(pixels, input_maxval, alpha, alpha_maxval, cmapP, InitCodeSize+1, fp); /* Write out a Zero-length packet (to end the series) */ fputc( 0, fp ); /* Write the GIF file terminator */ fputc( ';', fp ); /* And close the file */ fclose( fp ); } static int compute_transparent(const char colorarg[], struct cmap * const cmapP) { /*---------------------------------------------------------------------------- Figure out the color index (index into the colormap) of the color that is to be transparent in the GIF. colorarg[] is the string that specifies the color the user wants to be transparent (e.g. "red", "#fefefe"). Its maxval is the maxval of the colormap. 'cmap' is the full colormap except that its 'transparent' component isn't valid. colorarg[] is a standard Netpbm color specification, except that may have a "=" prefix, which means it specifies a particular exact color, as opposed to without the "=", which means "the color that is closest to this and actually in the image." Return -1 if colorarg[] specifies an exact color and that color is not in the image. Also issue an informational message. -----------------------------------------------------------------------------*/ int retval; const char *colorspec; bool exact; int presort_colorindex; pixel transcolor; if (colorarg[0] == '=') { colorspec = &colorarg[1]; exact = TRUE; } else { colorspec = colorarg; exact = FALSE; } transcolor = ppm_parsecolor((char*)colorspec, cmapP->maxval); presort_colorindex = ppm_lookupcolor(cmapP->cht, &transcolor); if (presort_colorindex != -1) retval = cmapP->perm[presort_colorindex]; else if (!exact) retval = cmapP->perm[closestcolor(transcolor, cmapP->maxval, cmapP)]; else { retval = -1; pm_message( "Warning: specified transparent color does not occur in image."); } return retval; } static void sort_colormap(int const sort, struct cmap * const cmapP) { /*---------------------------------------------------------------------------- Sort (in place) the colormap *cmapP. Create the perm[] and permi[] mappings for the colormap. 'sort' is logical: true means to sort the colormap by red intensity, then by green intensity, then by blue intensity. False means a null sort -- leave it in the same order in which we found it. -----------------------------------------------------------------------------*/ int * const Red = cmapP->red; int * const Blue = cmapP->blue; int * const Green = cmapP->green; int * const perm = cmapP->perm; int * const permi = cmapP->permi; unsigned int const cmapsize = cmapP->cmapsize; int i; for (i=0; i < cmapsize; i++) permi[i] = i; if (sort) { pm_message("sorting colormap"); for (i=0; i < cmapsize; i++) { int j; for (j=i+1; j < cmapsize; j++) if (((Red[i]*MAXCMAPSIZE)+Green[i])*MAXCMAPSIZE+Blue[i] > ((Red[j]*MAXCMAPSIZE)+Green[j])*MAXCMAPSIZE+Blue[j]) { int tmp; tmp=permi[i]; permi[i]=permi[j]; permi[j]=tmp; tmp=Red[i]; Red[i]=Red[j]; Red[j]=tmp; tmp=Green[i]; Green[i]=Green[j]; Green[j]=tmp; tmp=Blue[i]; Blue[i]=Blue[j]; Blue[j]=tmp; } } } for (i=0; i < cmapsize; i++) perm[permi[i]] = i; } static void normalize_to_255(colorhist_vector const chv, struct cmap * const cmapP) { /*---------------------------------------------------------------------------- With a PPM color histogram vector 'chv' as input, produce a colormap of integers 0-255 as output in *cmapP. -----------------------------------------------------------------------------*/ int i; pixval const maxval = cmapP->maxval; if ( maxval != 255 ) pm_message( "maxval is not 255 - automatically rescaling colors" ); for ( i = 0; i < cmapP->cmapsize; ++i ) { if ( maxval == 255 ) { cmapP->red[i] = (int) PPM_GETR( chv[i].color ); cmapP->green[i] = (int) PPM_GETG( chv[i].color ); cmapP->blue[i] = (int) PPM_GETB( chv[i].color ); } else { cmapP->red[i] = (int) PPM_GETR( chv[i].color ) * 255 / maxval; cmapP->green[i] = (int) PPM_GETG( chv[i].color ) * 255 / maxval; cmapP->blue[i] = (int) PPM_GETB( chv[i].color ) * 255 / maxval; } } } static void add_to_colormap(struct cmap * const cmapP, const char * const colorspec, int * const new_indexP) { /*---------------------------------------------------------------------------- Add a new entry to the colormap. Make the color that specified by 'colorspec', and return the index of the new entry as *new_indexP. '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. -----------------------------------------------------------------------------*/ pixel const transcolor = ppm_parsecolor((char*)colorspec, cmapP->maxval); *new_indexP = cmapP->cmapsize++; cmapP->red[*new_indexP] = PPM_GETR(transcolor); cmapP->green[*new_indexP] = PPM_GETG(transcolor); cmapP->blue[*new_indexP] = PPM_GETB(transcolor); } static void colormap_from_file(const char filespec[], unsigned int const maxcolors, colorhist_vector * const chvP, pixval * const maxvalP, int * const colorsP) { /*---------------------------------------------------------------------------- Read a colormap from the PPM file filespec[]. Return the color histogram vector (which is practically a colormap) of the input image as *cvhP and the maxval for that histogram as *maxvalP. -----------------------------------------------------------------------------*/ FILE *mapfile; int cols, rows; pixel ** colormap_ppm; mapfile = pm_openr(filespec); colormap_ppm = ppm_readppm(mapfile, &cols, &rows, maxvalP); pm_close(mapfile); /* Figure out the colormap from the . */ pm_message("computing other colormap..."); *chvP = ppm_computecolorhist(colormap_ppm, cols, rows, maxcolors, colorsP); ppm_freearray(colormap_ppm, rows); } static void get_alpha(const char * const alpha_filespec, int const cols, int const rows, gray *** const alphaP, gray * const maxvalP) { if (alpha_filespec) { int alpha_cols, alpha_rows; *alphaP = pgm_readpgm(pm_openr(alpha_filespec), &alpha_cols, &alpha_rows, maxvalP); if (alpha_cols != cols || alpha_rows != rows) pm_error("alpha mask is not the same dimensions as the " "input file (alpha is %dW x %dH; image is %dW x %dH)", alpha_cols, alpha_rows, cols, rows); } else *alphaP = NULL; } static void compute_ppm_colormap(pixel ** const pixels, int const cols, int const rows, int const input_maxval, bool const have_alpha, const char * const mapfile, colorhist_vector * const chvP, colorhash_table * const chtP, pixval * const colormap_maxvalP, int * const colorsP) { /*---------------------------------------------------------------------------- Compute a colormap, PPM style, for the image 'pixels', which is 'cols' by 'rows' with maxval 'input_maxval'. If 'mapfile' is non-null, Use the colors in that (PPM) file for the color map instead of the colors in 'pixels'. Return the colormap as *chvP and *chtP. Return the maxval for that colormap as *colormap_maxvalP. While we're at it, count the colors and validate that there aren't too many. Return the count as *colorsP. In determining if there are too many, allow one slot for a fake transparency color if 'have_alpha' is true. If there are too many, issue an error message and abort the program. -----------------------------------------------------------------------------*/ unsigned int maxcolors; /* 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. */ if (have_alpha) maxcolors = MAXCMAPSIZE - 1; else maxcolors = MAXCMAPSIZE; if (mapfile) { /* Read the colormap from a separate colormap file. */ colormap_from_file(mapfile, maxcolors, chvP, colormap_maxvalP, colorsP); } else { /* Figure out the color map from the input file */ pm_message("computing colormap..."); *chvP = ppm_computecolorhist(pixels, cols, rows, maxcolors, colorsP); *colormap_maxvalP = input_maxval; } if (*chvP == NULL) pm_error("too many colors - try doing a 'pnmquant %d'", maxcolors); pm_message("%d colors found", *colorsP); /* And make a hash table for fast lookup. */ *chtP = ppm_colorhisttocolorhash(*chvP, *colorsP); } int main(int argc, char *argv[]) { struct cmdline_info cmdline; FILE* ifp; int rows, cols; int BitsPerPixel; pixel ** pixels; /* The input image, in PPM format */ pixval input_maxval; /* Maxval for 'pixels' */ gray ** alpha; /* The supplied alpha mask; NULL if none */ gray alpha_maxval; /* Maxval for 'alpha' */ struct cmap cmap; /* The colormap, with all its accessories */ colorhist_vector chv; int fake_transparent; /* colormap index of the fake transparency color we're using to implement the alpha mask. Undefined if we're not doing an alpha mask. */ ppm_init( &argc, argv ); parse_command_line(argc, argv, &cmdline); ifp = pm_openr(cmdline.input_filespec); pixels = ppm_readppm(ifp, &cols, &rows, &input_maxval); pm_close(ifp); get_alpha(cmdline.alpha_filespec, cols, rows, &alpha, &alpha_maxval); compute_ppm_colormap(pixels, cols, rows, input_maxval, (alpha != NULL), cmdline.mapfile, &chv, &cmap.cht, &cmap.maxval, &cmap.cmapsize); /* Now turn the ppm colormap into the appropriate GIF colormap. */ normalize_to_255(chv, &cmap); ppm_freecolorhist(chv); if (alpha) { /* Add a fake entry to the end of the colormap for transparency. Make its color black. */ add_to_colormap(&cmap, cmdline.alphacolor, &fake_transparent); } sort_colormap(cmdline.sort, &cmap); BitsPerPixel = pm_maxvaltobits(cmap.cmapsize-1); if (alpha) { cmap.transparent = cmap.perm[fake_transparent]; } else { if (cmdline.transparent) cmap.transparent = compute_transparent(cmdline.transparent, &cmap); else cmap.transparent = -1; } /* All set, let's do it. */ GIFEncode(stdout, pixels, input_maxval, cols, rows, alpha, alpha_maxval, cmdline.interlace, 0, BitsPerPixel, &cmap, cmdline.comment, cmdline.nolzw); ppm_freearray(pixels, rows); if (alpha) pgm_freearray(alpha, rows); exit(0); }