diff options
Diffstat (limited to 'converter/other')
-rw-r--r-- | converter/other/Makefile | 2 | ||||
-rw-r--r-- | converter/other/giftopnm.c | 4 | ||||
-rw-r--r-- | converter/other/hdifftopam.c | 2 | ||||
-rw-r--r-- | converter/other/jpeg2000/libjasper/Makefile.common | 2 | ||||
-rw-r--r-- | converter/other/pamtogif.c | 1864 | ||||
-rw-r--r-- | converter/other/pamtopnm.c | 59 | ||||
-rw-r--r-- | converter/other/pamtosvg/curve.h | 8 | ||||
-rw-r--r-- | converter/other/pamtosvg/fit.c | 1127 | ||||
-rw-r--r-- | converter/other/pgmtopbm.c | 5 | ||||
-rw-r--r-- | converter/other/pgmtoppm.c | 343 | ||||
-rw-r--r-- | converter/other/pngtopnm.c | 22 | ||||
-rw-r--r-- | converter/other/pnmtoddif.c | 4 | ||||
-rw-r--r-- | converter/other/pnmtojpeg.c | 698 | ||||
-rw-r--r-- | converter/other/pnmtopalm/Makefile | 3 | ||||
-rw-r--r-- | converter/other/pnmtopng.c | 16 |
15 files changed, 3047 insertions, 1112 deletions
diff --git a/converter/other/Makefile b/converter/other/Makefile index 6cde424d..0273c523 100644 --- a/converter/other/Makefile +++ b/converter/other/Makefile @@ -79,7 +79,7 @@ endif PORTBINARIES = bmptopnm fitstopnm \ gemtopnm giftopnm hdifftopam infotopam \ - pamtodjvurle pamtofits \ + pamtodjvurle pamtofits pamtogif \ pamtohdiff pamtohtmltbl pamtopfm pamtopnm pamtouil \ pamtoxvmini \ pbmtopgm pfmtopam \ diff --git a/converter/other/giftopnm.c b/converter/other/giftopnm.c index d3d02fde..d7d54775 100644 --- a/converter/other/giftopnm.c +++ b/converter/other/giftopnm.c @@ -652,7 +652,7 @@ termStack(struct stack * const stackP) { max_dataVal. The first byte in the stream tells you what dataWidth is. - LZW codes 0 - max_dataVal are direct codes. Each on represents + LZW codes 0 - max_dataVal are direct codes. Each one represents the true data element whose value is that of the LZW code itself. No decompression is required. @@ -805,7 +805,7 @@ expandCodeOntoStack(struct decompressor * const decompP, 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 stringCount; stringCount = 0; diff --git a/converter/other/hdifftopam.c b/converter/other/hdifftopam.c index 444d6068..0f1a5ca3 100644 --- a/converter/other/hdifftopam.c +++ b/converter/other/hdifftopam.c @@ -96,7 +96,7 @@ main(int argc, char *argv[]) { tuple * outrow; tuple * prevrow; - pnm_init( &argc, argv ); + pnm_init(&argc, argv); parseCommandLine(argc, argv, &cmdline); diff --git a/converter/other/jpeg2000/libjasper/Makefile.common b/converter/other/jpeg2000/libjasper/Makefile.common index 7737f2c0..d2cb7d42 100644 --- a/converter/other/jpeg2000/libjasper/Makefile.common +++ b/converter/other/jpeg2000/libjasper/Makefile.common @@ -26,7 +26,7 @@ INCLUDES = -I$(JASPERSRCDIR)/include -Iimportinc DEFS = -DHAVE_LIBM=1 -DSTDC_HEADERS=1 -DHAVE_FCNTL_H=1 -DHAVE_LIMITS_H=1 -DHAVE_UNISTD_H=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STDDEF_H=1 -DEXCLUDE_BMP_SUPPORT -DEXCLUDE_RAS_SUPPORT -DEXCLUDE_MIF_SUPPORT -DEXCLUDE_JPG_SUPPORT -DEXCLUDE_PGX_SUPPORT -DEXCLUDE_PNM_SUPPORT $(LIB_OBJECTS):%.o:%.c - $(CC) -c $(CFLAGS) $(INCLUDES) $(DEFS) $(CADD) $< + $(CC) -c $(CPPFLAGS) $(CFLAGS) $(INCLUDES) $(DEFS) $(CADD) $< $(LIB_OBJECTS): importinc diff --git a/converter/other/pamtogif.c b/converter/other/pamtogif.c new file mode 100644 index 00000000..ef3441cb --- /dev/null +++ b/converter/other/pamtogif.c @@ -0,0 +1,1864 @@ +/*============================================================================= + pamtogif +=============================================================================== + Convert a Netpbm image to GIF + + History and copyright information is at the end of the file. +=============================================================================*/ + +#include <assert.h> +#include <string.h> + +#include "mallocvar.h" +#include "shhopt.h" +#include "nstring.h" +#include "pam.h" +#include "pammap.h" + +#define MAXCMAPSIZE 256 + +static unsigned int const gifMaxval = 255; + +static bool verbose; + +typedef int stringCode; + /* A code to be place in the GIF raster. It represents + a string of one or more pixels. You interpret this in the context + of a current code size. The lower half of the values representable + in the current code size represent singleton strings and the value + is simply the value of the one pixel in the string. The first two + values in the upper half of the range are the clear code and EOF + code, respectively. The rest of the values represent multi-pixel + strings. The mapping between value and the sequence of pixels + changes throughout the image. + + A variable of this type sometimes has the value -1 instead of + a string code due to cheesy programming. + + Ergo, this data structure must be signed and at least BITS bits + wide plus sign bit. + + TODO: Some variables that should be of this type are defined as + "long". + */ + + +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. + */ + 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[]. + + The sort colormap is the one that goes into the GIF. The + unsorted one is the one that is hashed so we can find colors + in it. + */ + 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. This is a post-sort index. + + Meaningful only if 'haveTransparent' is true. + */ + tuplehash tuplehash; + /* A hash table to translate color to GIF (presort) 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 verbose; +}; + + +static unsigned int +nSignificantBits(unsigned int const arg) { + + unsigned int i; + + i = 0; + + while (arg >> i != 0) + ++i; + + return i; +} + + + +static unsigned int +pamAlphaPlane(struct pam * const pamP) { + + unsigned int alphaPlane; + + if (STREQ(pamP->tuple_type, "RGB_ALPHA")) + alphaPlane = 3; + else if (STREQ(pamP->tuple_type, "GRAYSCALE_ALPHA")) + alphaPlane = 2; + else if (STREQ(pamP->tuple_type, "BLACKANDWHITE_ALPHA")) + alphaPlane = 2; + else + alphaPlane = 0; + + if (alphaPlane >= pamP->depth) + pm_error("Tuple type is '%s', but depth (%u) is less than %u", + pamP->tuple_type, pamP->depth, alphaPlane + 1); + + return alphaPlane; +} + + + +static void +parseCommandLine(int argc, char ** argv, + struct cmdlineInfo * const cmdlineP) { +/*---------------------------------------------------------------------------- + Parse the program arguments (given by argc and argv) into a form + the program can deal with more easily -- a cmdline_info structure. + If the syntax is invalid, issue a message and exit the program via + pm_error(). + + Note that the file spec array we return is stored in the storage that + was passed to us as the argv array. +-----------------------------------------------------------------------------*/ + optEntry * option_def; /* malloc'ed */ + optStruct3 opt; /* set by OPTENT3 */ + unsigned int option_def_index; + + MALLOCARRAY_NOFAIL(option_def, 100); + + option_def_index = 0; /* incremented by OPTENT3 */ + OPTENT3(0, "interlace", OPT_FLAG, + NULL, &cmdlineP->interlace, 0); + OPTENT3(0, "sort", OPT_FLAG, + NULL, &cmdlineP->sort, 0); + OPTENT3(0, "nolzw", OPT_FLAG, + NULL, &cmdlineP->nolzw, 0); + OPTENT3(0, "mapfile", OPT_STRING, + &cmdlineP->mapfile, NULL, 0); + OPTENT3(0, "transparent", OPT_STRING, + &cmdlineP->transparent, NULL, 0); + OPTENT3(0, "comment", OPT_STRING, + &cmdlineP->comment, NULL, 0); + OPTENT3(0, "alphacolor", OPT_STRING, + &cmdlineP->alphacolor, NULL, 0); + OPTENT3(0, "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 */ + + optParseOptions3(&argc, argv, opt, sizeof(opt), 0); + /* Uses and sets argc, argv, and some of *cmdlineP and others. */ + + if (argc-1 == 0) + cmdlineP->input_filespec = "-"; + else if (argc-1 != 1) + pm_error("Program takes zero or one argument (filename). You " + "specified %d", argc-1); + else + cmdlineP->input_filespec = argv[1]; +} + + + +/* + * Write out a word to the GIF file + */ +static void +Putword(int const w, FILE * const fp) { + + fputc( w & 0xff, fp ); + fputc( (w / 256) & 0xff, fp ); +} + + + +static int +closestColor(tuple const color, + struct pam * const pamP, + struct cmap * const cmapP) { +/*---------------------------------------------------------------------------- + Return the pre-sort 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; + bool fits; + + dmin = UINT_MAX; + imin = 0; + for (i = 0; i < cmapP->cmapSize; ++i) { + unsigned int distance; + unsigned int plane; + + for (distance = 0, plane = 0; plane < nComp; ++plane) + /* Divide by 4 is to avoid arithmetic overflow */ + distance += SQR(color[plane] - cmapP->color[i][plane]) / 4; + + if (distance < dmin) { + dmin = distance; + imin = i; + } + } + pnm_addtotuplehash(pamP, cmapP->tuplehash, color, imin, &fits); + + return imin; +} + + + +enum pass {MULT8PLUS0, MULT8PLUS4, MULT4PLUS2, MULT2PLUS1}; + + +typedef struct { + struct pam pam; + /* Description of input file/image. The position of the file + is also part of the state of this rowReader. + */ + pm_filepos rasterPos; + /* Position in file fileP of the start of the raster */ + bool interlace; + /* We're accessing the image in interlace fashion */ + bool eof; + /* The image is at EOF (we have returned all of the rows) */ + unsigned int nextRow; + /* Number of row to which input file is positioned; + meaningless if 'eof'. + */ + enum pass pass; + /* The interlace pass. Undefined if !interlace */ + tuple * discardBuffer; + /* A bitbucket for rows we read in order to advance the file + position. + */ +} rowReader; + + + +static rowReader * +rowReader_create(struct pam * const pamP, + pm_filepos const rasterPos, + bool const interlace) { + + rowReader * rdrP; + + MALLOCVAR_NOFAIL(rdrP); + + rdrP->pam = *pamP; + rdrP->rasterPos = rasterPos; + rdrP->interlace = interlace; + rdrP->eof = FALSE; + rdrP->pass = MULT8PLUS0; + + pm_seek2(rdrP->pam.file, &rasterPos, sizeof(rasterPos)); + rdrP->nextRow = 0; + + rdrP->discardBuffer = pnm_allocpamrow(&rdrP->pam); + + return rdrP; +} + + + +static void +rowReader_destroy(rowReader * const rdrP) { + + pnm_freepamrow(rdrP->discardBuffer); + free(rdrP); +} + + + +static void +rowReaderSkipRows(rowReader * const rdrP, + unsigned int const rowCount, + bool * const eofP) { +/*---------------------------------------------------------------------------- + Skip over the next 'rowCount' rows of the input file. + + Iff there aren't at least 'rowCount' rows left, return *eofP == TRUE. +-----------------------------------------------------------------------------*/ + if (rdrP->nextRow + rowCount >= rdrP->pam.height) + *eofP = TRUE; + else { + /* This could be made faster if need be by adding a libnetpbm + row skip function. Except with the plain formats, that could + just compute the next row position and fseek() to it. + pnm_readpamrow() with NULL for the output pointer would be a + good interface for a row skip function. + */ + unsigned int i; + + *eofP = FALSE; + + for (i = 0; i < rowCount; ++i) + pnm_readpamrow(&rdrP->pam, rdrP->discardBuffer); + + rdrP->nextRow += rowCount; + } +} + + + +static void +rowReaderGotoNextInterlaceRow(rowReader * const rdrP) { +/*---------------------------------------------------------------------------- + Position reader to the next row in the interlace pattern, assuming it + is now positioned immediately after the current row. +-----------------------------------------------------------------------------*/ + bool endOfPass; + + /* There are 4 passes: + MULT8PLUS0: Rows 0, 8, 16, 24, 32, etc. + MULT8PLUS4: Rows 4, 12, 20, 28, etc. + MULT4PLUS2: Rows 2, 6, 10, 14, etc. + MULT2PLUS1: Rows 1, 3, 5, 7, 9, etc. + */ + + switch (rdrP->pass) { + case MULT8PLUS0: + rowReaderSkipRows(rdrP, 7, &endOfPass); + break; + case MULT8PLUS4: + rowReaderSkipRows(rdrP, 7, &endOfPass); + break; + case MULT4PLUS2: + rowReaderSkipRows(rdrP, 3, &endOfPass); + break; + case MULT2PLUS1: + rowReaderSkipRows(rdrP, 1, &endOfPass); + break; + } + + /* Note that if there are more than 4 rows, the sequence of passes + is sequential, but when there are fewer than 4, reading may skip + e.g. from MULT8PLUS0 to MULT4PLUS2. + */ + while (endOfPass && !rdrP->eof) { + pm_seek2(rdrP->pam.file, &rdrP->rasterPos, sizeof(rdrP->rasterPos)); + rdrP->nextRow = 0; + + switch (rdrP->pass) { + case MULT8PLUS0: + rdrP->pass = MULT8PLUS4; + rowReaderSkipRows(rdrP, 4, &endOfPass); + break; + case MULT8PLUS4: + rdrP->pass = MULT4PLUS2; + rowReaderSkipRows(rdrP, 2, &endOfPass); + break; + case MULT4PLUS2: + rdrP->pass = MULT2PLUS1; + rowReaderSkipRows(rdrP, 1, &endOfPass); + break; + case MULT2PLUS1: + rdrP->eof = TRUE; + break; + } + } +} + + + + +static void +rowReaderGotoNextStraightRow(rowReader * const rdrP) { +/*---------------------------------------------------------------------------- + Position reader to the next row in a straight, non-interlace + pattern, assuming the file is now positioned immediately after the + current row. + + This is trivial, since the next row _is_ immediately after the current + row, except in the case that there are no more rows. +-----------------------------------------------------------------------------*/ + if (rdrP->nextRow >= rdrP->pam.height) + rdrP->eof = TRUE; +} + + + +static void +rowReader_read(rowReader * const rdrP, + tuple * const tuplerow) { + + if (rdrP->eof) + pm_error("INTERNAL ERROR: rowReader attempted to read beyond end " + "of image"); + + pnm_readpamrow(&rdrP->pam, tuplerow); + ++rdrP->nextRow; + + if (rdrP->interlace) + rowReaderGotoNextInterlaceRow(rdrP); + else + rowReaderGotoNextStraightRow(rdrP); +} + + + +static unsigned int +gifPixel(struct pam * const pamP, + tuple const tuple, + unsigned int const alphaPlane, + sample const alphaThreshold, + struct cmap * const cmapP) { +/*---------------------------------------------------------------------------- + Return as *colorIndexP the colormap index of the tuple 'tuple', + whose format is described by *pamP, using colormap *cmapP. + + 'alphaThreshold' is the alpha level below which we consider a + pixel transparent for GIF purposes. +-----------------------------------------------------------------------------*/ + unsigned int colorIndex; + + if (alphaPlane && tuple[alphaPlane] < alphaThreshold && + cmapP->haveTransparent) + colorIndex = cmapP->transparent; + else { + int presortColorindex; + int found; + + pnm_lookuptuple(pamP, cmapP->tuplehash, tuple, + &found, &presortColorindex); + + if (!found) + presortColorindex = closestColor(tuple, pamP, cmapP); + + colorIndex = cmapP->perm[presortColorindex]; + } + return 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 + +#define HSIZE 5003 /* 80% occupancy */ + +/* + * + * 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 { + long fcode; /* -1 means unused slot */ + unsigned int ent; +}; + + + +/*************************************************************************** +* BYTE OUTPUTTER +***************************************************************************/ + +typedef struct { + FILE * fileP; /* The file to which to output */ + unsigned int count; + /* Number of bytes so far in the current data block */ + unsigned char buffer[256]; + /* The current data block, under construction */ +} byteBuffer; + + + +static byteBuffer * +byteBuffer_create(FILE * const fileP) { + + byteBuffer * byteBufferP; + + MALLOCVAR_NOFAIL(byteBufferP); + + byteBufferP->fileP = fileP; + byteBufferP->count = 0; + + return byteBufferP; +} + + + +static void +byteBuffer_destroy(byteBuffer * const byteBufferP) { + + free(byteBufferP); +} + + + +static void +byteBuffer_flush(byteBuffer * const byteBufferP) { +/*---------------------------------------------------------------------------- + Write the current data block to the output file, then reset the current + data block to empty. +-----------------------------------------------------------------------------*/ + if (byteBufferP->count > 0 ) { + if (verbose) + pm_message("Writing %u byte block", byteBufferP->count); + fputc(byteBufferP->count, byteBufferP->fileP); + fwrite(byteBufferP->buffer, 1, byteBufferP->count, byteBufferP->fileP); + byteBufferP->count = 0; + } +} + + + +static void +byteBuffer_flushFile(byteBuffer * const byteBufferP) { + + fflush(byteBufferP->fileP); + + if (ferror(byteBufferP->fileP)) + pm_error("error writing output file"); +} + + + +static void +byteBuffer_out(byteBuffer * const byteBufferP, + unsigned char const c) { +/*---------------------------------------------------------------------------- + Add a byte to the end of the current data block, and if it is now 254 + characters, flush the data block to the output file. +-----------------------------------------------------------------------------*/ + byteBufferP->buffer[byteBufferP->count++] = c; + if (byteBufferP->count >= 254) + byteBuffer_flush(byteBufferP); +} + + + +/*************************************************************************** +* GIF CODE OUTPUTTER +***************************************************************************/ + +typedef struct { + byteBuffer * byteBufferP; + unsigned int initBits; + unsigned int nBits; + /* Number of bits to put in output for each code */ + stringCode maxCode; /* maximum code, given n_bits */ + stringCode maxCodeLimit; + /* LZW: One beyond the largest string code that can exist in GIF. + Uncompressed: a ceiling to prevent code size from ratcheting up. + In either case, output code never reaches this value. + */ + unsigned long curAccum; + int curBits; + unsigned int codeCount; + /* Number of codes that have been output to this buffer (doesn't + matter if they have gone out the other side yet or not) since + the last flush (or ever, if no last flush). The main use of this + is debugging -- when something fails, you can see in a debugger + where in the image it was, then set a trap for there. + */ +} codeBuffer; + + + +static codeBuffer * +codeBuffer_create(FILE * const ofP, + unsigned int const initBits, + bool const lzw) { + + codeBuffer * codeBufferP; + + MALLOCVAR_NOFAIL(codeBufferP); + + codeBufferP->initBits = initBits; + codeBufferP->nBits = codeBufferP->initBits; + codeBufferP->maxCode = (1 << codeBufferP->nBits) - 1; + codeBufferP->maxCodeLimit = lzw ? + (stringCode)1 << BITS : (stringCode) (1 << codeBufferP->nBits) - 1; + codeBufferP->byteBufferP = byteBuffer_create(ofP); + codeBufferP->curAccum = 0; + codeBufferP->curBits = 0; + codeBufferP->codeCount = 0; + + return codeBufferP; +} + + + +static void +codeBuffer_destroy(codeBuffer * const codeBufferP) { + + byteBuffer_destroy(codeBufferP->byteBufferP); + + free(codeBufferP); +} + + + +static void +codeBuffer_resetCodeSize(codeBuffer * const codeBufferP) { + + codeBufferP->nBits = codeBufferP->initBits; + + assert(codeBufferP->nBits <= BITS); + + codeBufferP->maxCode = (1 << codeBufferP->nBits) - 1; +} + + + +static void +codeBuffer_increaseCodeSize(codeBuffer * const codeBufferP) { + + ++codeBufferP->nBits; + + assert(codeBufferP->nBits <= BITS); + + codeBufferP->maxCode = (1 << codeBufferP->nBits) - 1; +} + + + +static unsigned long const masks[] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, + 0x001F, 0x003F, 0x007F, 0x00FF, + 0x01FF, 0x03FF, 0x07FF, 0x0FFF, + 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; + +static void +codeBuffer_output(codeBuffer * const codeBufferP, + stringCode const code) { +/*---------------------------------------------------------------------------- + Output one GIF code to the file, through the code buffer. + + The code is represented as N bits in the file -- the lower + N bits of 'code'. N is a the current code size of *codeBufferP. + + Id 'code' is the maximum possible code for the current code size + for *codeBufferP, increase that code size (unless it's already + maxed out). +-----------------------------------------------------------------------------*/ + assert (code <= codeBufferP->maxCode); + + codeBufferP->curAccum &= masks[codeBufferP->curBits]; + + if (codeBufferP->curBits > 0) + codeBufferP->curAccum |= ((long)code << codeBufferP->curBits); + else + codeBufferP->curAccum = code; + + codeBufferP->curBits += codeBufferP->nBits; + + while (codeBufferP->curBits >= 8) { + byteBuffer_out(codeBufferP->byteBufferP, + codeBufferP->curAccum & 0xff); + codeBufferP->curAccum >>= 8; + codeBufferP->curBits -= 8; + } + + ++codeBufferP->codeCount; +} + + + +static void +codeBuffer_flush(codeBuffer * const codeBufferP) { + + /* Output the possible partial byte in the buffer */ + + if (codeBufferP->curBits > 0) { + byteBuffer_out(codeBufferP->byteBufferP, + codeBufferP->curAccum & 0xff); + codeBufferP->curBits = 0; + } + byteBuffer_flush(codeBufferP->byteBufferP); + + byteBuffer_flushFile(codeBufferP->byteBufferP); + + if (verbose) + pm_message("%u strings of pixels written to file", + codeBufferP->codeCount); + codeBufferP->codeCount = 0; +} + + + +typedef struct { + codeBuffer * codeBufferP; + /* The place to which we write our string codes. + + Constant. + */ + bool lzw; + /* We're actually doing LZW compression. False means we follow + the algorithm enough tht an LZW decompressor will recover the + proper data, but always using one code per pixel, and therefore + not effecting any compression and not using the LZW patent. + */ + unsigned int hshift; + /* This is how many bits we shift left a string code in forming the + primary hash of the concatenation of that string with another. + Constant. + */ + + /* Codes less than 'clearCode' are singleton pixel codes - each + represents the pixel value equal to it. + + Codes greater than 'eofCode' are multipixel string codes. Each + represents a string of pixels that is defined by the preceding + stream. + */ + stringCode clearCode; + /* The code in an LZW stream that means to clear the string + dictionary and start fresh. + + Constant. + */ + stringCode eofCode; + /* The code in an LZW stream that means there's no more coming + + Constant. + */ + stringCode initCodeLimit; + /* The value of 'codeLimit' at the start of a block. + + Constant. + */ + + stringCode codeLimit; + /* One beyond the maximum code possible with the current code + size. + */ + + struct hashTableEntry * hashTable; + stringCode nextUnusedCode; + /* Numerically next code available to assign to a a multi-pixel + string. Note that codes for multi-pixel strings are in the + upper half of the range of codes, always greater than + 'clearCode'. + */ + + stringCode stringSoFar; + /* The code for the string we have built so far. This code indicates + one or more pixels that we have encoded but not yet output + because we're hoping to match an even longer string. + + Valid only when 'buildingString' is true. + + In the non-lzw case the single pixel to output. + */ + bool buildingString; + /* We are in the middle of building a string; 'stringSoFar' describes + the pixels in it so far. The only time this is false is at the + very beginning of the stream. + + Ignored in the non-lzw case. + */ +} lzwCompressor; + + + +static lzwCompressor * +lzw_create(FILE * const ofP, + unsigned int const initBits, + bool const lzw) { + + lzwCompressor * lzwP; + + MALLOCVAR_NOFAIL(lzwP); + + MALLOCARRAY(lzwP->hashTable, HSIZE); + + if (lzwP->hashTable == NULL) + pm_error("Couldn't get memory for %u-entry hash table.", HSIZE); + + /* Constants */ + lzwP->lzw = lzw; + lzwP->hshift = + nSignificantBits(HSIZE-1) - 1 - nSignificantBits(MAXCMAPSIZE-1); + lzwP->clearCode = 1 << (initBits - 1); + lzwP->eofCode = lzwP->clearCode + 1; + lzwP->initCodeLimit = 1 << initBits; + + lzwP->buildingString = FALSE; + + lzwP->codeBufferP = codeBuffer_create(ofP, initBits, lzw); + + return lzwP; +} + + + +static void +lzw_destroy(lzwCompressor * const lzwP) { + + codeBuffer_destroy(lzwP->codeBufferP); + + free(lzwP->hashTable); + + free(lzwP); +} + + + +static void +lzwHashClear(lzwCompressor * const lzwP) { + + /* Empty the code table */ + + unsigned int i; + + for (i = 0; i < HSIZE; ++i) + lzwP->hashTable[i].fcode = -1; + + lzwP->nextUnusedCode = lzwP->clearCode + 2; + + +} + + + +static void +lzw_clearBlock(lzwCompressor * const lzwP) { +/*---------------------------------------------------------------------------- + +-----------------------------------------------------------------------------*/ + lzwHashClear(lzwP); + + codeBuffer_output(lzwP->codeBufferP, lzwP->clearCode); + + codeBuffer_resetCodeSize(lzwP->codeBufferP); + + lzwP->codeLimit = lzwP->initCodeLimit; +} + + + +static void +lzwAdjustCodeSize(lzwCompressor * const lzwP, + stringCode const newCode) { +/*---------------------------------------------------------------------------- + Assuming we just defined code 'newCode', increase the code size as + required so that this code fits. + + The decompressor is mimicking our assignment of that code, so knows that + we are making this adjustment, so expects codes of the new size. +-----------------------------------------------------------------------------*/ + assert(newCode <= lzwP->codeLimit); + + if (newCode == lzwP->codeLimit) { + lzwP->codeLimit *= 2; + codeBuffer_increaseCodeSize(lzwP->codeBufferP); + + assert(lzwP->codeLimit <= maxCodeLimitLzw); + } +} + + + +static void +lzwOutputCurrentString(lzwCompressor * const lzwP) { +/*---------------------------------------------------------------------------- + Put a code for the currently built-up string in the output stream. + + Doing this causes a new string code to be defined (code is + lzwP->nextUnusedCode), so Caller must add that to the hash. If + that code's size is beyond the overall limit, we reset the hash + (which means future codes will start back at the minimum size) and + put a clear code in the stream to tell the decompressor to do the + same. So Caller must add it to the hash _before_ calling us. + + Note that in the non-compressing case, the overall limit is small + enough to prevent us from ever defining string codes; we'll always + reset the hash. + + There's an odd case that always screws up any attempt to make this + code cleaner: At the end of the LZW stream, you have to output the + code for the final string even though you don't have a following + pixel that would make a longer string. So there's nothing to add + to the hash table and no point in allocating a new string code. + But the decompressor doesn't know that we're done, so he allocates + the next string code and may therefore increase his code length. + If we don't do the same, we will write our one last code -- the EOF + code -- in a code length smaller than what the decompressor is + expecting, and he will have a premature end of stream. + + So this subroutine does run for that final code flush and does some + of the motions of defining a new string code, but this subroutine + can't update the hash because in that particular case, there's + nothing to add. +-----------------------------------------------------------------------------*/ + codeBuffer_output(lzwP->codeBufferP, lzwP->stringSoFar); + if (lzwP->nextUnusedCode < lzwP->codeBufferP->maxCodeLimit) { + /* Allocate the code for the extended string, which Caller + should have already put in the hash so he can use it in the + future. Decompressor knows when it sees the code output + above to define a string on its end too, using the same + string code we do. + */ + stringCode const newCode = lzwP->nextUnusedCode++; + + /* This code may be too big to fit in the current code size, in + which case we have to increase the code size (and decompressor + will do the same). + */ + lzwAdjustCodeSize(lzwP, newCode); + } else { + /* Forget all the strings so far; start building again; tell + decompressor to do the same. + */ + lzw_clearBlock(lzwP); + } +} + + + +static void +lzw_flush(lzwCompressor * const lzwP) { + + if (lzwP->lzw) + lzwOutputCurrentString(lzwP); + /* Put out the code for the final string. */ + + codeBuffer_output(lzwP->codeBufferP, lzwP->eofCode); + + codeBuffer_flush(lzwP->codeBufferP); +} + + + +static unsigned int +primaryHash(stringCode const baseString, + stringCode const additionalPixel, + unsigned int const hshift) { + + unsigned int hash; + + assert(baseString < maxCodeLimitLzw); + assert(additionalPixel < MAXCMAPSIZE); + + hash = (additionalPixel << hshift) ^ baseString; + + assert(hash < HSIZE); + + return hash; +} + + + +static void +lookupInHash(lzwCompressor * const lzwP, + unsigned int const gifPixel, + long const fcode, + bool * const foundP, + unsigned short * const codeP, + unsigned int * const hashP) { + + int disp; + /* secondary hash stride (after G. Knott) */ + int i; + /* Index into hash table */ + + i = primaryHash(lzwP->stringSoFar, gifPixel, lzwP->hshift); + disp = (i == 0) ? 1 : HSIZE - i; + + while (lzwP->hashTable[i].fcode != fcode && + lzwP->hashTable[i].fcode >= 0) { + i -= disp; + if (i < 0) + i += HSIZE; + } + + if (lzwP->hashTable[i].fcode == fcode) { + /* Found fcode in hash table */ + *foundP = TRUE; + *codeP = lzwP->hashTable[i].ent; + } else { + /* Found where it _should_ be (but it's not) with primary hash */ + *foundP = FALSE; + *hashP = i; + } +} + + + +static void +lzw_encodePixel(lzwCompressor * const lzwP, + unsigned int const gifPixel) { + + bool found; + unsigned short code; + unsigned int hash; + /* Index into hash table where the value should go */ + + assert(gifPixel < 256); + + if (!lzwP->buildingString) { + /* Start a new string with just this pixel */ + lzwP->stringSoFar = gifPixel; + lzwP->buildingString = TRUE; + } else { + long const fcode = ((long) gifPixel << BITS) + lzwP->stringSoFar; + /* The encoding of the string we've already recognized plus the + instant pixel, to be looked up in the hash of knows strings. + */ + + lookupInHash(lzwP, gifPixel, fcode, &found, &code, &hash); + + if (found) + /* With this new pixel, it is still a known string; 'code' is + its code + */ + lzwP->stringSoFar = code; + else { + /* It's no longer a known string. Output the code for the + known prefix of the string, thus defining a new string + code for possible later use. Warning: + lzwOutputCurrentString() does more than you think. + */ + + lzwP->hashTable[hash].ent = lzwP->nextUnusedCode; + lzwP->hashTable[hash].fcode = fcode; + + lzwOutputCurrentString(lzwP); + + /* This singleton pixel starts the next string */ + lzwP->stringSoFar = gifPixel; + } + } +} + + + +/* + * compress stdin to stdout + * + * Algorithm: use open addressing double hashing (no chaining) on the + * prefix code / next character combination. We do a variant of Knuth's + * algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime + * secondary probe. Here, the modular division first probe is gives way + * to a faster exclusive-or manipulation. Also do block compression with + * an adaptive reset, whereby the code table is cleared when the compression + * ratio decreases, but after the table fills. The variable-length output + * codes are re-sized at this point, and a special CLEAR code is generated + * for the decompressor. Late addition: construct the table according to + * file size for noticeable speed improvement on small files. Please direct + * questions about this implementation to ames!jaw. + */ + +static void +writePixelUncompressed(lzwCompressor * const lzwP, + unsigned int const gifPixel) { + + lzwP->stringSoFar = gifPixel; + lzwOutputCurrentString(lzwP); + +} + +static void +writeRaster(struct pam * const pamP, + rowReader * const rowReaderP, + unsigned int const alphaPlane, + unsigned int const alphaThreshold, + struct cmap * const cmapP, + unsigned int const initBits, + FILE * const ofP, + bool const lzw) { +/*---------------------------------------------------------------------------- + Write the raster to file 'ofP'. + + Get the raster to write from 'rowReaderP', which gives tuples whose + format is described by 'pamP'. + + Use the colormap 'cmapP' to generate the raster ('rowReaderP' gives + pixel values as RGB samples; the GIF raster is colormap indices). + + Write the raster using LZW compression, or uncompressed depending + on 'lzw'. +-----------------------------------------------------------------------------*/ + lzwCompressor * lzwP; + tuple * tuplerow; + unsigned int nRowsDone; + /* Number of rows we have read so far from the the input (the + last of which is the one we're working on now). Note that + in case of interlace, this is not the same thing as the row + number of the current row. + */ + + lzwP = lzw_create(ofP, initBits, lzw); + + tuplerow = pnm_allocpamrow(pamP); + + lzw_clearBlock(lzwP); + + nRowsDone = 0; + + while (nRowsDone < pamP->height) { + unsigned int col; + + rowReader_read(rowReaderP, tuplerow); + + for (col = 0; col < pamP->width; ++col) { + unsigned int const colorIndex = + gifPixel(pamP, tuplerow[col], alphaPlane, alphaThreshold, + cmapP); + + /* The value for the pixel in the GIF image. I.e. the colormap + index. + */ + if (lzw) + lzw_encodePixel(lzwP, colorIndex); + else + writePixelUncompressed(lzwP, colorIndex); + } + ++nRowsDone; + } + /* Gif is no good with no pixels; fortunately, that's impossible: */ + assert(nRowsDone > 0); + + lzw_flush(lzwP); + + pnm_freepamrow(tuplerow); + + lzw_destroy(lzwP); +} + + + + + + + + + +static void +writeGlobalColorMap(FILE * const ofP, + const struct cmap * const cmapP, + unsigned int const bitsPerPixel) { +/*---------------------------------------------------------------------------- + Write out the Global Color Map + + Note that the Global Color Map is always a power of two colors + in size, but *cmapP could be smaller than that. So we pad with + black. +-----------------------------------------------------------------------------*/ + unsigned int const colorMapSize = 1 << bitsPerPixel; + + struct pam pam; + unsigned int i; + tuple tupleRgb255; + + if (verbose) + pm_message("Writing %u-entry global colormap for %u colors", + colorMapSize, cmapP->cmapSize); + + pam = cmapP->pam; + pam.size = PAM_STRUCT_SIZE(allocation_depth); + pam.len = pam.size; + pnm_setminallocationdepth(&pam, 3); + + tupleRgb255 = pnm_allocpamtuple(&pam); + + for (i = 0; i < colorMapSize; ++i) { + if (i < cmapP->cmapSize) { + tuple const color = cmapP->color[cmapP->permi[i]]; + + assert(cmapP->permi[i] < cmapP->cmapSize); + + pnm_scaletuple(&pam, tupleRgb255, color, 255); + pnm_maketuplergb(&pam, tupleRgb255); + + fputc(tupleRgb255[PAM_RED_PLANE], ofP); + fputc(tupleRgb255[PAM_GRN_PLANE], ofP); + fputc(tupleRgb255[PAM_BLU_PLANE], ofP); + } else { + fputc(0, ofP); + fputc(0, ofP); + fputc(0, ofP); + } + } + pnm_freepamtuple(tupleRgb255); +} + + + +static void +writeGifHeader(FILE * const ofP, + unsigned int const width, + unsigned int const height, + unsigned int const background, + unsigned int const bitsPerPixel, + const struct cmap * const cmapP, + char const comment[]) { + + unsigned int const resolution = bitsPerPixel; + + unsigned char b; + + /* Write the Magic header */ + if (cmapP->haveTransparent || comment) + 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); + + /* Byte of 0's (future expansion) */ + fputc(0x00, ofP); + + writeGlobalColorMap(ofP, cmapP, bitsPerPixel); + + if (cmapP->haveTransparent) + writeTransparentColorIndexExtension(ofP, cmapP->transparent); + + if (comment) + writeCommentExtension(ofP, comment); +} + + + +static void +writeImageHeader(FILE * const ofP, + unsigned int const leftOffset, + unsigned int const topOffset, + unsigned int const gWidth, + unsigned int const gHeight, + bool const gInterlace, + unsigned int const initCodeSize) { + + Putword(leftOffset, ofP); + Putword(topOffset, ofP); + Putword(gWidth, ofP); + Putword(gHeight, ofP); + + /* Write out whether or not the image is interlaced */ + if (gInterlace) + fputc(0x40, ofP); + else + fputc(0x00, ofP); + + /* Write out the initial code size */ + fputc(initCodeSize, ofP); +} + + + +static void +reportImageInfo(bool const interlace, + unsigned int const background, + unsigned int const bitsPerPixel) { + + if (verbose) { + if (interlace) + pm_message("interlaced"); + else + pm_message("not interlaced"); + pm_message("Background color index = %u", background); + pm_message("%u bits per pixel", bitsPerPixel); + } +} + + + +static void +gifEncode(struct pam * const pamP, + FILE * const ofP, + pm_filepos const rasterPos, + bool const gInterlace, + int const background, + unsigned int const bitsPerPixel, + struct cmap * const cmapP, + char const comment[], + bool const lzw) { + + unsigned int const leftOffset = 0; + unsigned int const topOffset = 0; + + unsigned int const initCodeSize = bitsPerPixel <= 1 ? 2 : bitsPerPixel; + /* The initial code size */ + + sample const alphaThreshold = (pamP->maxval + 1) / 2; + /* Levels below this in the alpha plane indicate transparent + pixels in the output image. + */ + + unsigned int const alphaPlane = pamAlphaPlane(pamP); + + rowReader * rowReaderP; + + reportImageInfo(gInterlace, background, bitsPerPixel); + + writeGifHeader(ofP, pamP->width, pamP->height, background, + bitsPerPixel, cmapP, comment); + + /* Write an Image separator */ + fputc(',', ofP); + + writeImageHeader(ofP, leftOffset, topOffset, pamP->width, pamP->height, + gInterlace, initCodeSize); + + rowReaderP = rowReader_create(pamP, rasterPos, gInterlace); + + /* Write the actual raster */ + + writeRaster(pamP, rowReaderP, alphaPlane, alphaThreshold, + cmapP, initCodeSize + 1, ofP, lzw); + + rowReader_destroy(rowReaderP); + + /* Write out a zero length data block (to end the series) */ + fputc(0, ofP); + + /* Write the GIF file terminator */ + fputc(';', ofP); +} + + + +static void +reportTransparent(struct cmap * const cmapP) { + + if (verbose) { + if (cmapP->haveTransparent) { + tuple const color = cmapP->color[cmapP->permi[cmapP->transparent]]; + pm_message("Color %u (%lu, %lu, %lu) is transparent", + cmapP->transparent, + color[PAM_RED_PLANE], + color[PAM_GRN_PLANE], + color[PAM_BLU_PLANE]); + } else + pm_message("No transparent color"); + } +} + + + +static void +computeTransparent(char const colorarg[], + bool const usingFakeTrans, + unsigned int const fakeTransparent, + struct cmap * const cmapP) { +/*---------------------------------------------------------------------------- + Figure out the color index (index into the colormap) of the color + that is to be transparent in the GIF. + + colorarg[] is the string that specifies the color the user wants to + be transparent (e.g. "red", "#fefefe"). Its maxval is the maxval + of the colormap. 'cmap' is the full colormap except that its + 'transparent' component isn't valid. + + colorarg[] is a standard Netpbm color specification, except that + may have a "=" prefix, which means it specifies a particular exact + color, as opposed to without the "=", which means "the color that + is closest to this and actually in the image." + + colorarg[] null means the color didn't ask for a particular color + to be transparent. + + Establish no transparent color if colorarg[] specifies an exact + color and that color is not in the image. Also issue an + informational message. + + 'usingFakeTrans' means pixels will be transparent because of something + other than their foreground color, and 'fakeTransparent' is the + color map index for transparent colors. +-----------------------------------------------------------------------------*/ + if (colorarg) { + const char * colorspec; + bool exact; + tuple transcolor; + bool found; + int presortColorindex; + + 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, + &presortColorindex); + + if (found) { + cmapP->haveTransparent = TRUE; + cmapP->transparent = cmapP->perm[presortColorindex]; + } else if (!exact) { + cmapP->haveTransparent = TRUE; + cmapP->transparent = + cmapP->perm[closestColor(transcolor, &cmapP->pam, cmapP)]; + } else { + cmapP->haveTransparent = FALSE; + pm_message("Warning: specified transparent color " + "does not occur in image."); + } + } else if (usingFakeTrans) { + cmapP->haveTransparent = TRUE; + cmapP->transparent = fakeTransparent; + } else + cmapP->haveTransparent = FALSE; + + reportTransparent(cmapP); +} + + + +static unsigned int +sortOrder(tuple const tuple, + struct pam * const pamP) { + + if (pamP->depth < 3) + return tuple[0]; + else + return ((tuple[0] * MAXCMAPSIZE) + tuple[1]) * MAXCMAPSIZE + tuple[2]; +} + + + + +static void +sortColormap(bool 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. +-----------------------------------------------------------------------------*/ + tuple * const color = cmapP->color; + int * const perm = cmapP->perm; + int * const permi = cmapP->permi; + struct pam * const pamP = &cmapP->pam; + unsigned int const cmapSize = cmapP->cmapSize; + + unsigned int i; + + /* We will sort permi[]. So we first set up permi[] to reflect the + original, unsorted order. + */ + for (i=0; i < cmapSize; i++) + permi[i] = i; + + if (sort) { + pm_message("sorting colormap"); + for (i = 0; i < cmapSize; ++i) { + unsigned int j; + for (j = i+1; j < cmapSize; ++j) { + if (sortOrder(color[permi[i]], pamP) > + sortOrder(color[permi[j]], pamP)) { + + sample tmp; + + tmp = permi[i]; permi[i] = permi[j]; permi[j] = tmp; + } + } + } + } + for (i = 0; i < cmapSize; ++i) + perm[permi[i]] = i; +} + + + +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 ..."); + + pnm_computetuplefreqtable(mapPamP, colors, maxcolors, &colorCount); + + *colorCountP = colorCount; + + pnm_freepamarray(colors, mapPamP); +} + + + +static void +computeColormapFromInput(struct pam * const pamP, + unsigned int const maxcolors, + unsigned int const nInputComp, + struct pam * const mapPamP, + unsigned int * const colorCountP, + tupletable * const tuplefreqP) { + + tupletable tuplefreq; + + pm_message("computing colormap..."); + + tuplefreq = pnm_computetuplefreqtable3( + pamP, NULL, maxcolors, nInputComp, pamP->maxval, colorCountP); + + *mapPamP = *pamP; + mapPamP->depth = nInputComp; + + *tuplefreqP = tuplefreq; +} + + + +static void +computeLibnetpbmColormap(struct pam * const pamP, + bool const haveAlpha, + const char * const mapfile, + tuple * const color, + tuplehash * const tuplehashP, + struct pam * const mapPamP, + unsigned int * const colorCountP) { +/*---------------------------------------------------------------------------- + Compute a colormap, libnetpbm style, for the image described by + 'pamP', which is positioned to the raster. + + If 'mapfile' is non-null, Use the colors in that (Netpbm) file for + the color map instead of the colors in 'pamP'. + + Return the colormap as color[] and *tuplehashP. Return the format + of those tuples as *mapPamP. + + The tuples of the color map have a meaningful depth of 1 (grayscale) or 3 + (color) and *mapPamP reflects that. + + While we're at it, count the colors and validate that there aren't + too many. Return the count as *colorCountP. In determining if there are + too many, allow one slot for a fake transparency color if 'haveAlpha' + is true. If there are too many, issue an error message and abort the + program. +-----------------------------------------------------------------------------*/ + unsigned int const maxcolors = haveAlpha ? MAXCMAPSIZE - 1 : MAXCMAPSIZE; + /* The most colors we can tolerate in the image. If we have + our own made-up entry in the colormap for transparency, it + isn't included in this count. + */ + unsigned int const nInputComp = haveAlpha ? pamP->depth - 1 : pamP->depth; + /* Number of color components (not alpha) in the input image */ + + unsigned int i; + tupletable tuplefreq; + unsigned int colorCount; + + if (mapfile) { + /* Read the colormap from a separate colormap file. */ + colormapFromFile(mapfile, maxcolors, &tuplefreq, mapPamP, + &colorCount); + + if (mapPamP->depth != nInputComp) + pm_error("Depth of map file (%u) does not match number of " + "color components in input file (%u)", + mapPamP->depth, pamP->depth); + if (mapPamP->maxval != pamP->maxval) + pm_error("Maxval of map file (%lu) does not match maxval of " + "input file (%lu)", mapPamP->maxval, pamP->maxval); + } 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); + + for (i = 0; i < colorCount; ++i) { + color[i] = pnm_allocpamtuple(mapPamP); + pnm_assigntuple(mapPamP, color[i], tuplefreq[i]->tuple); + } + + /* And make a hash table for fast lookup. */ + *tuplehashP = + pnm_computetupletablehash(mapPamP, tuplefreq, colorCount); + + *colorCountP = colorCount; + + pnm_freetupletable(mapPamP, tuplefreq); +} + + + +static void +destroyCmap(struct cmap * const cmapP) { + + unsigned int colorIndex; + + for (colorIndex = 0; colorIndex < cmapP->cmapSize; ++colorIndex) + pnm_freepamtuple(cmapP->color[colorIndex]); + + pnm_destroytuplehash(cmapP->tuplehash); +} + + + +int +main(int argc, char *argv[]) { + struct cmdlineInfo cmdline; + FILE * ifP; + struct pam pam; + unsigned int bitsPerPixel; + pm_filepos rasterPos; + + struct cmap cmap; + /* The colormap, with all its accessories */ + unsigned int fakeTransparent; + /* colormap index of the fake transparency color we're using to + implement the alpha mask. Undefined if we're not doing an alpha + mask. + */ + + pnm_init(&argc, argv); + + parseCommandLine(argc, argv, &cmdline); + + verbose = cmdline.verbose; + + ifP = pm_openr_seekable(cmdline.input_filespec); + + pnm_readpaminit(ifP, &pam, PAM_STRUCT_SIZE(tuple_type)); + + pm_tell2(ifP, &rasterPos, sizeof(rasterPos)); + + computeLibnetpbmColormap(&pam, !!pamAlphaPlane(&pam), cmdline.mapfile, + cmap.color, &cmap.tuplehash, + &cmap.pam, &cmap.cmapSize); + + assert(cmap.pam.maxval == pam.maxval); + + if (pamAlphaPlane(&pam)) { + /* Add a fake entry to the end of the colormap for transparency. + Make its color black. + */ + addToColormap(&cmap, cmdline.alphacolor, &fakeTransparent); + } + sortColormap(cmdline.sort, &cmap); + + bitsPerPixel = pm_maxvaltobits(cmap.cmapSize-1); + + computeTransparent(cmdline.transparent, + !!pamAlphaPlane(&pam), fakeTransparent, &cmap); + + /* All set, let's do it. */ + gifEncode(&pam, stdout, rasterPos, + cmdline.interlace, 0, bitsPerPixel, &cmap, cmdline.comment, + !cmdline.nolzw); + + destroyCmap(&cmap); + + pm_close(ifP); + pm_close(stdout); + + return 0; +} + + + +/*============================================================================ + Original version, named 'ppmgif' was by Jef Poskanzer in 1989, based + on GIFENCOD by David Rowley <mgardi@watdscu.waterloo.edu>.A Lempel-Zim + compression based on "compress". + + Switched to use libnetpbm PAM facilities (ergo process PAM images) + and renamed 'pamtogif' by Bryan Henderson November 2006. + + The non-LZW GIF generation stuff was adapted from the Independent + JPEG Group's djpeg on 2001.09.29. In 2006.12 the output subroutines + were rewritten; now no uncompressed output subroutines are derived from + the Independent JPEG Group's source code. + + + 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. +============================================================================*/ diff --git a/converter/other/pamtopnm.c b/converter/other/pamtopnm.c index cc1164da..6835b43f 100644 --- a/converter/other/pamtopnm.c +++ b/converter/other/pamtopnm.c @@ -108,6 +108,7 @@ main(int argc, char *argv[]) { struct cmdlineInfo cmdline; FILE* ifP; + bool eof; /* no more images in input stream */ struct pam inpam; /* Input PAM image */ struct pam outpam; /* Output PNM image */ @@ -117,39 +118,45 @@ main(int argc, char *argv[]) { ifP = pm_openr(cmdline.inputFilespec); - pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type)); + eof = FALSE; + while (!eof) { + pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type)); - validateTupleType(inpam, cmdline.assume); + validateTupleType(inpam, cmdline.assume); - outpam = inpam; - outpam.file = stdout; - - if (inpam.depth < 3) { - outpam.depth = 1; - if (inpam.maxval == 1) - outpam.format = PBM_FORMAT; - else - outpam.format = PGM_FORMAT; - } else { - outpam.depth = 3; - outpam.format = PPM_FORMAT; - } + outpam = inpam; + outpam.file = stdout; + + if (inpam.depth < 3) { + outpam.depth = 1; + if (inpam.maxval == 1) + outpam.format = PBM_FORMAT; + else + outpam.format = PGM_FORMAT; + } else { + outpam.depth = 3; + outpam.format = PPM_FORMAT; + } - pnm_writepaminit(&outpam); + pnm_writepaminit(&outpam); - { - tuple *tuplerow; - - tuplerow = pnm_allocpamrow(&inpam); - { - int row; + { + tuple *tuplerow; - for (row = 0; row < inpam.height; row++) { - pnm_readpamrow(&inpam, tuplerow); - pnm_writepamrow(&outpam, tuplerow); + tuplerow = pnm_allocpamrow(&inpam); + { + int row; + + for (row = 0; row < inpam.height; row++) { + pnm_readpamrow(&inpam, tuplerow); + pnm_writepamrow(&outpam, tuplerow); + } } + pnm_freepamrow(tuplerow); } - pnm_freepamrow(tuplerow); + + pnm_nextimage(ifP, &eof); } + return 0; } diff --git a/converter/other/pamtosvg/curve.h b/converter/other/pamtosvg/curve.h index ee046620..2f010d45 100644 --- a/converter/other/pamtosvg/curve.h +++ b/converter/other/pamtosvg/curve.h @@ -77,14 +77,16 @@ extern curve_type copy_most_of_curve (curve_type c); /* Free the memory C uses. */ extern void free_curve (curve_type c); +/* Like `append_pixel', for a point in real coordinates. */ +void +append_point(curve_type const curve, + float_coord const coord); + /* Append the point P to the end of C's list. */ void append_pixel(curve_type const c, pm_pixelcoord const p); -/* Like `append_pixel', for a point in real coordinates. */ -extern void append_point (curve_type const c, float_coord const p); - /* Write some or all, respectively, of the curve C in human-readable form to the log file, if logging is enabled. */ extern void log_curve (curve_type c, bool print_t); diff --git a/converter/other/pamtosvg/fit.c b/converter/other/pamtosvg/fit.c index 08db41db..2fa64500 100644 --- a/converter/other/pamtosvg/fit.c +++ b/converter/other/pamtosvg/fit.c @@ -54,21 +54,6 @@ typedef struct index_list #define INDEX_LIST_LENGTH(i_l) ((i_l).length) #define GET_LAST_INDEX(i_l) ((i_l).data[INDEX_LIST_LENGTH (i_l) - 1]) -static void append_index (index_list_type *, unsigned); -static void free_index_list (index_list_type *); -static index_list_type new_index_list (void); -static void remove_adjacent_corners (index_list_type *, unsigned, bool, - at_exception_type * exception); -static void filter (curve_type, fitting_opts_type *); -static void find_vectors - (unsigned const, pixel_outline_type const, vector_type * const, vector_type * const, unsigned const); -static float find_error (curve_type, spline_type, unsigned *, - at_exception_type * exception); -static vector_type find_half_tangent (curve_type, bool start, unsigned *, unsigned); -static void find_tangent (curve_type, bool, bool, unsigned); -static void remove_knee_points (curve_type const, bool const); -static void set_initial_parameter_values (curve_type); -static float distance (float_coord, float_coord); static pm_pixelcoord @@ -86,6 +71,50 @@ real_to_int_coord(float_coord const real_coord) { } +/* Lists of array indices (well, that is what we use it for). */ + +static index_list_type +new_index_list (void) +{ + index_list_type index_list; + + index_list.data = NULL; + INDEX_LIST_LENGTH (index_list) = 0; + + return index_list; +} + +static void +free_index_list (index_list_type *index_list) +{ + if (INDEX_LIST_LENGTH (*index_list) > 0) + { + free (index_list->data); + index_list->data = NULL; + INDEX_LIST_LENGTH (*index_list) = 0; + } +} + +static void +append_index (index_list_type *list, unsigned new_index) +{ + INDEX_LIST_LENGTH (*list)++; + REALLOCARRAY_NOFAIL(list->data, INDEX_LIST_LENGTH(*list)); + list->data[INDEX_LIST_LENGTH (*list) - 1] = new_index; +} + + +/* Return the Euclidean distance between P1 and P2. */ + +static float +distance (float_coord p1, float_coord p2) +{ + float x = p1.x - p2.x, y = p1.y - p2.y, z = p1.z - p2.z; + return (float) sqrt (SQR(x) + SQR(y) + SQR(z)); +} + + + static void appendCorner(index_list_type * const cornerListP, unsigned int const pixelSeq, @@ -102,6 +131,45 @@ appendCorner(index_list_type * const cornerListP, static void +find_vectors(unsigned int const test_index, + pixel_outline_type const outline, + vector_type * const in, + vector_type * const out, + unsigned int const corner_surround) { +/*---------------------------------------------------------------------------- + Return the difference vectors coming in and going out of the outline + OUTLINE at the point whose index is TEST_INDEX. In Phoenix, + Schneider looks at a single point on either side of the point we're + considering. That works for him because his points are not touching. + But our points *are* touching, and so we have to look at + `corner_surround' points on either side, to get a better picture of + the outline's shape. +-----------------------------------------------------------------------------*/ + int i; + unsigned n_done; + pm_pixelcoord const candidate = O_COORDINATE(outline, test_index); + + in->dx = in->dy = in->dz = 0.0; + out->dx = out->dy = out->dz = 0.0; + + /* Add up the differences from p of the `corner_surround' points + before p. + */ + for (i = O_PREV(outline, test_index), n_done = 0; + n_done < corner_surround; + i = O_PREV(outline, i), ++n_done) + *in = Vadd(*in, IPsubtract(O_COORDINATE(outline, i), candidate)); + + /* And the points after p. */ + for (i = O_NEXT (outline, test_index), n_done = 0; + n_done < corner_surround; + i = O_NEXT(outline, i), ++n_done) + *out = Vadd(*out, IPsubtract(O_COORDINATE(outline, i), candidate)); +} + + + +static void lookAheadForBetterCorner(pixel_outline_type const outline, unsigned int const basePixelSeq, float const baseCornerAngle, @@ -210,6 +278,287 @@ establishCornerSearchLimits(pixel_outline_type const outline, static void +remove_adjacent_corners(index_list_type * const list, + unsigned int const last_index, + bool const remove_adj_corners, + at_exception_type * const exception) { +/*---------------------------------------------------------------------------- + Remove adjacent points from the index list LIST. We do this by first + sorting the list and then running through it. Since these lists are + quite short, a straight selection sort (e.g., p.139 of the Art of + Computer Programming, vol.3) is good enough. LAST_INDEX is the index + of the last pixel on the outline, i.e., the next one is the first + pixel. We need this for checking the adjacency of the last corner. + + We need to do this because the adjacent corners turn into + two-pixel-long curves, which can only be fit by straight lines. +-----------------------------------------------------------------------------*/ + unsigned int j; + unsigned int last; + index_list_type new_list = new_index_list (); + + for (j = INDEX_LIST_LENGTH (*list) - 1; j > 0; j--) + { + unsigned search; + unsigned temp; + /* Find maximal element below `j'. */ + unsigned max_index = j; + + for (search = 0; search < j; search++) + if (GET_INDEX (*list, search) > GET_INDEX (*list, max_index)) + max_index = search; + + if (max_index != j) + { + temp = GET_INDEX (*list, j); + GET_INDEX (*list, j) = GET_INDEX (*list, max_index); + GET_INDEX (*list, max_index) = temp; + + /* xx -- really have to sort? */ + LOG ("needed exchange"); + at_exception_warning(exception, "needed exchange"); + } + } + + /* The list is sorted. Now look for adjacent entries. Each time + through the loop we insert the current entry and, if appropriate, + the next entry. */ + for (j = 0; j < INDEX_LIST_LENGTH (*list) - 1; j++) + { + unsigned current = GET_INDEX (*list, j); + unsigned next = GET_INDEX (*list, j + 1); + + /* We should never have inserted the same element twice. */ + /* assert (current != next); */ + + if ((remove_adj_corners) && ((next == current + 1) || (next == current))) + j++; + + append_index (&new_list, current); + } + + /* Don't append the last element if it is 1) adjacent to the previous + one; or 2) adjacent to the very first one. */ + last = GET_LAST_INDEX (*list); + if (INDEX_LIST_LENGTH (new_list) == 0 + || !(last == GET_LAST_INDEX (new_list) + 1 + || (last == last_index && GET_INDEX (*list, 0) == 0))) + append_index (&new_list, last); + + free_index_list (list); + *list = new_list; +} + +/* A ``knee'' is a point which forms a ``right angle'' with its + predecessor and successor. See the documentation (the `Removing + knees' section) for an example and more details. + + The argument CLOCKWISE tells us which direction we're moving. (We + can't figure that information out from just the single segment with + which we are given to work.) + + We should never find two consecutive knees. + + Since the first and last points are corners (unless the curve is + cyclic), it doesn't make sense to remove those. */ + +/* This evaluates to true if the vector V is zero in one direction and + nonzero in the other. */ +#define ONLY_ONE_ZERO(v) \ + (((v).dx == 0.0 && (v).dy != 0.0) || ((v).dy == 0.0 && (v).dx != 0.0)) + +/* There are four possible cases for knees, one for each of the four + corners of a rectangle; and then the cases differ depending on which + direction we are going around the curve. The tests are listed here + in the order of upper left, upper right, lower right, lower left. + Perhaps there is some simple pattern to the + clockwise/counterclockwise differences, but I don't see one. */ +#define CLOCKWISE_KNEE(prev_delta, next_delta) \ + ((prev_delta.dx == -1.0 && next_delta.dy == 1.0) \ + || (prev_delta.dy == 1.0 && next_delta.dx == 1.0) \ + || (prev_delta.dx == 1.0 && next_delta.dy == -1.0) \ + || (prev_delta.dy == -1.0 && next_delta.dx == -1.0)) + +#define COUNTERCLOCKWISE_KNEE(prev_delta, next_delta) \ + ((prev_delta.dy == 1.0 && next_delta.dx == -1.0) \ + || (prev_delta.dx == 1.0 && next_delta.dy == 1.0) \ + || (prev_delta.dy == -1.0 && next_delta.dx == 1.0) \ + || (prev_delta.dx == -1.0 && next_delta.dy == -1.0)) + + + +static void +remove_knee_points(curve_type const curve, + bool const clockwise) { + + unsigned const offset = (CURVE_CYCLIC(curve) == true) ? 0 : 1; + curve_type const trimmed_curve = copy_most_of_curve(curve); + + pm_pixelcoord previous; + unsigned i; + + if (!CURVE_CYCLIC(curve)) + append_pixel(trimmed_curve, + real_to_int_coord(CURVE_POINT(curve, 0))); + + previous = real_to_int_coord(CURVE_POINT(curve, + CURVE_PREV(curve, offset))); + + for (i = offset; i < CURVE_LENGTH (curve) - offset; ++i) { + pm_pixelcoord const current = + real_to_int_coord(CURVE_POINT(curve, i)); + pm_pixelcoord const next = + real_to_int_coord(CURVE_POINT(curve, CURVE_NEXT(curve, i))); + vector_type const prev_delta = IPsubtract(previous, current); + vector_type const next_delta = IPsubtract(next, current); + + if (ONLY_ONE_ZERO(prev_delta) && ONLY_ONE_ZERO(next_delta) + && ((clockwise && CLOCKWISE_KNEE(prev_delta, next_delta)) + || (!clockwise + && COUNTERCLOCKWISE_KNEE(prev_delta, next_delta)))) + LOG2(" (%d,%d)", current.col, current.row); + else { + previous = current; + append_pixel(trimmed_curve, current); + } + } + + if (!CURVE_CYCLIC(curve)) + append_pixel(trimmed_curve, + real_to_int_coord(LAST_CURVE_POINT(curve))); + + if (CURVE_LENGTH(trimmed_curve) == CURVE_LENGTH(curve)) + LOG(" (none)"); + + LOG(".\n"); + + free_curve(curve); + *curve = *trimmed_curve; + free(trimmed_curve); /* free_curve? --- Masatake */ +} + + + +/* Smooth the curve by adding in neighboring points. Do this + `filter_iterations' times. But don't change the corners. */ + +static void +filter(curve_type const curve, + fitting_opts_type * const fitting_opts) { + + unsigned iteration, this_point; + unsigned offset = (CURVE_CYCLIC (curve) == true) ? 0 : 1; + float_coord prev_new_point; + + /* We must have at least three points---the previous one, the current + one, and the next one. But if we don't have at least five, we will + probably collapse the curve down onto a single point, which means + we won't be able to fit it with a spline. + */ + if (CURVE_LENGTH (curve) < 5) { + LOG1 ("Length is %u, not enough to filter.\n", CURVE_LENGTH (curve)); + return; + } + + prev_new_point.x = FLT_MAX; + prev_new_point.y = FLT_MAX; + prev_new_point.z = FLT_MAX; + + for (iteration = 0; + iteration < fitting_opts->filter_iterations; + ++iteration) { + curve_type newcurve = copy_most_of_curve (curve); + bool collapsed = false; + + /* Keep the first point on the curve. */ + if (offset) + append_point (newcurve, CURVE_POINT (curve, 0)); + + for (this_point = offset; + this_point < CURVE_LENGTH (curve) - offset; + ++this_point) { + vector_type in, out, sum; + float_coord new_point; + + /* Calculate the vectors in and out, computed by looking + at n points on either side of this_point. Experimental + it was found that 2 is optimal. + */ + + signed int prev, prevprev; /* have to be signed */ + unsigned int next, nextnext; + float_coord candidate = CURVE_POINT (curve, this_point); + + prev = CURVE_PREV (curve, this_point); + prevprev = CURVE_PREV (curve, prev); + next = CURVE_NEXT (curve, this_point); + nextnext = CURVE_NEXT (curve, next); + + /* Add up the differences from p of the `surround' points + before p. + */ + in.dx = in.dy = in.dz = 0.0; + + in = Vadd (in, Psubtract (CURVE_POINT (curve, prev), candidate)); + if (prevprev >= 0) + in = Vadd(in, + Psubtract(CURVE_POINT (curve, prevprev), candidate)); + + /* And the points after p. Don't use more points after p than we + ended up with before it. + */ + out.dx = out.dy = out.dz = 0.0; + + out = Vadd (out, Psubtract (CURVE_POINT (curve, next), candidate)); + if (nextnext < CURVE_LENGTH (curve)) + out = Vadd(out, + Psubtract(CURVE_POINT (curve, nextnext), + candidate)); + + /* Start with the old point. */ + new_point = candidate; + sum = Vadd (in, out); + /* We added 2*n+2 points, so we have to divide the sum by 2*n+2 */ + new_point.x += sum.dx / 6; + new_point.y += sum.dy / 6; + new_point.z += sum.dz / 6; + if (fabs (prev_new_point.x - new_point.x) < 0.3 + && fabs (prev_new_point.y - new_point.y) < 0.3 + && fabs (prev_new_point.z - new_point.z) < 0.3) { + collapsed = true; + break; + } + + + /* Put the newly computed point into a separate curve, so it + doesn't affect future computation (on this iteration). + */ + append_point (newcurve, prev_new_point = new_point); + } + + if (collapsed) + free_curve (newcurve); + else { + /* Just as with the first point, we have to keep the last + point. + */ + if (offset) + append_point (newcurve, LAST_CURVE_POINT (curve)); + + /* Set the original curve to the newly filtered one, and go + again. + */ + free_curve (curve); + *curve = *newcurve; + } + free (newcurve); + } + log_curve (curve, false); +} + + + +static void removeAdjacent(index_list_type * const cornerListP, pixel_outline_type const outline, fitting_opts_type * const fittingOptsP, @@ -951,6 +1300,210 @@ logSplineFit(spline_type const spline) { +static vector_type +find_half_tangent(curve_type const c, + bool const to_start_point, + unsigned int * const n_points, + unsigned int const tangent_surround) { +/*---------------------------------------------------------------------------- + Find the change in y and change in x for `tangent_surround' (a global) + points along CURVE. Increment N_POINTS by the number of points we + actually look at. +-----------------------------------------------------------------------------*/ + unsigned int p; + int factor = to_start_point ? 1 : -1; + unsigned tangent_index = to_start_point ? 0 : c->length - 1; + float_coord tangent_point = CURVE_POINT (c, tangent_index); + vector_type tangent = { 0.0, 0.0 }; + unsigned int surround; + + if ((surround = CURVE_LENGTH(c) / 2) > tangent_surround) + surround = tangent_surround; + + for (p = 1; p <= surround; ++p) { + int const this_index = p * factor + tangent_index; + float_coord this_point; + + if (this_index < 0 || this_index >= (int) c->length) + break; + + this_point = CURVE_POINT(c, p * factor + tangent_index); + + /* Perhaps we should weight the tangent from `this_point' by some + factor dependent on the distance from the tangent point. + */ + tangent = Vadd (tangent, + Vmult_scalar(Psubtract(this_point, tangent_point), + (float) factor)); + ++*n_points; + } + + return tangent; +} + + + +static void +find_tangent(curve_type const curve, + bool const to_start_point, + bool const cross_curve, + unsigned int const tangent_surround_arg) { +/*---------------------------------------------------------------------------- + Find an approximation to the tangent to an endpoint of CURVE (to the + first point if TO_START_POINT is true, else the last). If + CROSS_CURVE is true, consider points on the adjacent curve to CURVE. + + It is important to compute an accurate approximation, because the + control points that we eventually decide upon to fit the curve will + be placed on the half-lines defined by the tangents and + endpoints...and we never recompute the tangent after this. +-----------------------------------------------------------------------------*/ + vector_type ** curve_tangent; + vector_type tangent; + unsigned n_points; + + LOG1(" tangent to %s: ", (to_start_point == true) ? "start" : "end"); + + n_points = 0; /* initial value */ + + curve_tangent = to_start_point ? + &(CURVE_START_TANGENT(curve)) : &(CURVE_END_TANGENT(curve)); + + if (*curve_tangent == NULL) { + unsigned int tangent_surround; + + tangent_surround = tangent_surround_arg; /* initial value */ + MALLOCVAR_NOFAIL(*curve_tangent); + do { + tangent = find_half_tangent(curve, to_start_point, &n_points, + tangent_surround); + + if (cross_curve || CURVE_CYCLIC(curve)) { + curve_type const adjacent_curve = to_start_point ? + PREVIOUS_CURVE(curve) : NEXT_CURVE(curve); + vector_type const tangent2 = !to_start_point ? + find_half_tangent(adjacent_curve, true, &n_points, + tangent_surround) : + find_half_tangent(adjacent_curve, true, &n_points, + tangent_surround); + + LOG3("(adjacent curve half tangent (%.3f,%.3f,%.3f)) ", + tangent2.dx, tangent2.dy, tangent2.dz); + tangent = Vadd (tangent, tangent2); + } + --tangent_surround; + } while (tangent.dx == 0.0 && tangent.dy == 0.0); + + assert(n_points > 0); + **curve_tangent = Vmult_scalar (tangent, (float)(1.0 / n_points)); + if (CURVE_CYCLIC(curve) && CURVE_START_TANGENT(curve)) + *CURVE_START_TANGENT(curve) = **curve_tangent; + if (CURVE_CYCLIC(curve) && CURVE_END_TANGENT(curve)) + *CURVE_END_TANGENT(curve) = **curve_tangent; + } else + LOG("(already computed) "); + + LOG3("(%.3f,%.3f,%.3f).\n", (*curve_tangent)->dx, + (*curve_tangent)->dy, (*curve_tangent)->dz); +} + + + +static float +find_error(curve_type const curve, + spline_type const spline, + unsigned int * const worst_point, + at_exception_type * const exception) { +/*---------------------------------------------------------------------------- + When this routine is called, we have computed a spline representation + for the digitized curve. The question is, how good is it? If the + fit is very good indeed, we might have an error of zero on each + point, and then WORST_POINT becomes irrelevant. But normally, we + return the error at the worst point, and the index of that point in + WORST_POINT. The error computation itself is the Euclidean distance + from the original curve CURVE to the fitted spline SPLINE. +-----------------------------------------------------------------------------*/ + unsigned int this_point; + float total_error; + float worst_error; + + total_error = 0.0; /* initial value */ + worst_error = FLT_MIN; /* initial value */ + + *worst_point = CURVE_LENGTH(curve) + 1; /* A sentinel value. */ + + for (this_point = 0; this_point < CURVE_LENGTH(curve); ++this_point) { + float_coord const curve_point = CURVE_POINT(curve, this_point); + float const t = CURVE_T(curve, this_point); + float_coord const spline_point = evaluate_spline(spline, t); + float const this_error = distance(curve_point, spline_point); + if (this_error >= worst_error) { + *worst_point = this_point; + worst_error = this_error; + } + total_error += this_error; + } + + if (*worst_point == CURVE_LENGTH(curve) + 1) { + /* Didn't have any ``worst point''; the error should be zero. */ + if (epsilon_equal(total_error, 0.0)) + LOG (" Every point fit perfectly.\n"); + else { + LOG("No worst point found; something is wrong"); + at_exception_warning(exception, + "No worst point found; something is wrong"); + } + } else { + if (epsilon_equal(total_error, 0.0)) + LOG (" Every point fit perfectly.\n"); + else { + LOG5(" Worst error (at (%.3f,%.3f,%.3f), point #%u) was %.3f.\n", + CURVE_POINT(curve, *worst_point).x, + CURVE_POINT(curve, *worst_point).y, + CURVE_POINT(curve, *worst_point).z, + *worst_point, worst_error); + LOG1(" Total error was %.3f.\n", total_error); + LOG2(" Average error (over %u points) was %.3f.\n", + CURVE_LENGTH(curve), total_error / CURVE_LENGTH(curve)); + } + } + return worst_error; +} + + + +static void +set_initial_parameter_values(curve_type const curve) { +/*---------------------------------------------------------------------------- + Find reasonable values for t for each point on CURVE. The method is + called chord-length parameterization, which is described in Plass & + Stone. The basic idea is just to use the distance from one point to + the next as the t value, normalized to produce values that increase + from zero for the first point to one for the last point. +-----------------------------------------------------------------------------*/ + unsigned int p; + + LOG("\nAssigning initial t values:\n "); + + CURVE_T(curve, 0) = 0.0; + + for (p = 1; p < CURVE_LENGTH (curve); ++p) { + float_coord const point = CURVE_POINT(curve, p); + float_coord const previous_p = CURVE_POINT(curve, p - 1); + float const d = distance (point, previous_p); + CURVE_T (curve, p) = CURVE_T (curve, p - 1) + d; + } + + assert(LAST_CURVE_T (curve) != 0.0); + + for (p = 1; p < CURVE_LENGTH(curve); ++p) + CURVE_T(curve, p) = CURVE_T(curve, p) / LAST_CURVE_T(curve); + + log_entire_curve(curve); +} + + + static spline_list_type * fit_with_least_squares(curve_type const curve, const fitting_opts_type * const fitting_opts, @@ -1377,547 +1930,3 @@ fit_outlines_to_splines(pixel_outline_list_type const pixelOutlineList, - -static void -find_vectors(unsigned int const test_index, - pixel_outline_type const outline, - vector_type * const in, - vector_type * const out, - unsigned int const corner_surround) { -/*---------------------------------------------------------------------------- - Return the difference vectors coming in and going out of the outline - OUTLINE at the point whose index is TEST_INDEX. In Phoenix, - Schneider looks at a single point on either side of the point we're - considering. That works for him because his points are not touching. - But our points *are* touching, and so we have to look at - `corner_surround' points on either side, to get a better picture of - the outline's shape. ------------------------------------------------------------------------------*/ - int i; - unsigned n_done; - pm_pixelcoord const candidate = O_COORDINATE(outline, test_index); - - in->dx = in->dy = in->dz = 0.0; - out->dx = out->dy = out->dz = 0.0; - - /* Add up the differences from p of the `corner_surround' points - before p. - */ - for (i = O_PREV(outline, test_index), n_done = 0; - n_done < corner_surround; - i = O_PREV(outline, i), ++n_done) - *in = Vadd(*in, IPsubtract(O_COORDINATE(outline, i), candidate)); - - /* And the points after p. */ - for (i = O_NEXT (outline, test_index), n_done = 0; - n_done < corner_surround; - i = O_NEXT(outline, i), ++n_done) - *out = Vadd(*out, IPsubtract(O_COORDINATE(outline, i), candidate)); -} - - - -/* Remove adjacent points from the index list LIST. We do this by first - sorting the list and then running through it. Since these lists are - quite short, a straight selection sort (e.g., p.139 of the Art of - Computer Programming, vol.3) is good enough. LAST_INDEX is the index - of the last pixel on the outline, i.e., the next one is the first - pixel. We need this for checking the adjacency of the last corner. - - We need to do this because the adjacent corners turn into - two-pixel-long curves, which can only be fit by straight lines. */ - -static void -remove_adjacent_corners (index_list_type *list, unsigned last_index, - bool remove_adj_corners, - at_exception_type * exception) - -{ - unsigned j; - unsigned last; - index_list_type new_list = new_index_list (); - - for (j = INDEX_LIST_LENGTH (*list) - 1; j > 0; j--) - { - unsigned search; - unsigned temp; - /* Find maximal element below `j'. */ - unsigned max_index = j; - - for (search = 0; search < j; search++) - if (GET_INDEX (*list, search) > GET_INDEX (*list, max_index)) - max_index = search; - - if (max_index != j) - { - temp = GET_INDEX (*list, j); - GET_INDEX (*list, j) = GET_INDEX (*list, max_index); - GET_INDEX (*list, max_index) = temp; - - /* xx -- really have to sort? */ - LOG ("needed exchange"); - at_exception_warning(exception, "needed exchange"); - } - } - - /* The list is sorted. Now look for adjacent entries. Each time - through the loop we insert the current entry and, if appropriate, - the next entry. */ - for (j = 0; j < INDEX_LIST_LENGTH (*list) - 1; j++) - { - unsigned current = GET_INDEX (*list, j); - unsigned next = GET_INDEX (*list, j + 1); - - /* We should never have inserted the same element twice. */ - /* assert (current != next); */ - - if ((remove_adj_corners) && ((next == current + 1) || (next == current))) - j++; - - append_index (&new_list, current); - } - - /* Don't append the last element if it is 1) adjacent to the previous - one; or 2) adjacent to the very first one. */ - last = GET_LAST_INDEX (*list); - if (INDEX_LIST_LENGTH (new_list) == 0 - || !(last == GET_LAST_INDEX (new_list) + 1 - || (last == last_index && GET_INDEX (*list, 0) == 0))) - append_index (&new_list, last); - - free_index_list (list); - *list = new_list; -} - -/* A ``knee'' is a point which forms a ``right angle'' with its - predecessor and successor. See the documentation (the `Removing - knees' section) for an example and more details. - - The argument CLOCKWISE tells us which direction we're moving. (We - can't figure that information out from just the single segment with - which we are given to work.) - - We should never find two consecutive knees. - - Since the first and last points are corners (unless the curve is - cyclic), it doesn't make sense to remove those. */ - -/* This evaluates to true if the vector V is zero in one direction and - nonzero in the other. */ -#define ONLY_ONE_ZERO(v) \ - (((v).dx == 0.0 && (v).dy != 0.0) || ((v).dy == 0.0 && (v).dx != 0.0)) - -/* There are four possible cases for knees, one for each of the four - corners of a rectangle; and then the cases differ depending on which - direction we are going around the curve. The tests are listed here - in the order of upper left, upper right, lower right, lower left. - Perhaps there is some simple pattern to the - clockwise/counterclockwise differences, but I don't see one. */ -#define CLOCKWISE_KNEE(prev_delta, next_delta) \ - ((prev_delta.dx == -1.0 && next_delta.dy == 1.0) \ - || (prev_delta.dy == 1.0 && next_delta.dx == 1.0) \ - || (prev_delta.dx == 1.0 && next_delta.dy == -1.0) \ - || (prev_delta.dy == -1.0 && next_delta.dx == -1.0)) - -#define COUNTERCLOCKWISE_KNEE(prev_delta, next_delta) \ - ((prev_delta.dy == 1.0 && next_delta.dx == -1.0) \ - || (prev_delta.dx == 1.0 && next_delta.dy == 1.0) \ - || (prev_delta.dy == -1.0 && next_delta.dx == 1.0) \ - || (prev_delta.dx == -1.0 && next_delta.dy == -1.0)) - - - -static void -remove_knee_points(curve_type const curve, - bool const clockwise) { - - unsigned const offset = (CURVE_CYCLIC(curve) == true) ? 0 : 1; - curve_type const trimmed_curve = copy_most_of_curve(curve); - - pm_pixelcoord previous; - unsigned i; - - if (!CURVE_CYCLIC(curve)) - append_pixel(trimmed_curve, - real_to_int_coord(CURVE_POINT(curve, 0))); - - previous = real_to_int_coord(CURVE_POINT(curve, - CURVE_PREV(curve, offset))); - - for (i = offset; i < CURVE_LENGTH (curve) - offset; ++i) { - pm_pixelcoord const current = - real_to_int_coord(CURVE_POINT(curve, i)); - pm_pixelcoord const next = - real_to_int_coord(CURVE_POINT(curve, CURVE_NEXT(curve, i))); - vector_type const prev_delta = IPsubtract(previous, current); - vector_type const next_delta = IPsubtract(next, current); - - if (ONLY_ONE_ZERO(prev_delta) && ONLY_ONE_ZERO(next_delta) - && ((clockwise && CLOCKWISE_KNEE(prev_delta, next_delta)) - || (!clockwise - && COUNTERCLOCKWISE_KNEE(prev_delta, next_delta)))) - LOG2(" (%d,%d)", current.col, current.row); - else { - previous = current; - append_pixel(trimmed_curve, current); - } - } - - if (!CURVE_CYCLIC(curve)) - append_pixel(trimmed_curve, - real_to_int_coord(LAST_CURVE_POINT(curve))); - - if (CURVE_LENGTH(trimmed_curve) == CURVE_LENGTH(curve)) - LOG(" (none)"); - - LOG(".\n"); - - free_curve(curve); - *curve = *trimmed_curve; - free(trimmed_curve); /* free_curve? --- Masatake */ -} - - - -/* Smooth the curve by adding in neighboring points. Do this - `filter_iterations' times. But don't change the corners. */ - -static void -filter (curve_type curve, fitting_opts_type *fitting_opts) -{ - unsigned iteration, this_point; - unsigned offset = (CURVE_CYCLIC (curve) == true) ? 0 : 1; - float_coord prev_new_point; - - /* We must have at least three points---the previous one, the current - one, and the next one. But if we don't have at least five, we will - probably collapse the curve down onto a single point, which means - we won't be able to fit it with a spline. */ - if (CURVE_LENGTH (curve) < 5) - { - LOG1 ("Length is %u, not enough to filter.\n", CURVE_LENGTH (curve)); - return; - } - - prev_new_point.x = FLT_MAX; - prev_new_point.y = FLT_MAX; - prev_new_point.z = FLT_MAX; - - for (iteration = 0; iteration < fitting_opts->filter_iterations; - iteration++) - { - curve_type newcurve = copy_most_of_curve (curve); - bool collapsed = false; - - /* Keep the first point on the curve. */ - if (offset) - append_point (newcurve, CURVE_POINT (curve, 0)); - - for (this_point = offset; this_point < CURVE_LENGTH (curve) - offset; - this_point++) - { - vector_type in, out, sum; - float_coord new_point; - - /* Calculate the vectors in and out, computed by looking at n points - on either side of this_point. Experimental it was found that 2 is - optimal. */ - - signed int prev, prevprev; /* have to be signed */ - unsigned int next, nextnext; - float_coord candidate = CURVE_POINT (curve, this_point); - - prev = CURVE_PREV (curve, this_point); - prevprev = CURVE_PREV (curve, prev); - next = CURVE_NEXT (curve, this_point); - nextnext = CURVE_NEXT (curve, next); - - /* Add up the differences from p of the `surround' points - before p. */ - in.dx = in.dy = in.dz = 0.0; - - in = Vadd (in, Psubtract (CURVE_POINT (curve, prev), candidate)); - if (prevprev >= 0) - in = Vadd (in, Psubtract (CURVE_POINT (curve, prevprev), candidate)); - - /* And the points after p. Don't use more points after p than we - ended up with before it. */ - out.dx = out.dy = out.dz = 0.0; - - out = Vadd (out, Psubtract (CURVE_POINT (curve, next), candidate)); - if (nextnext < CURVE_LENGTH (curve)) - out = Vadd (out, Psubtract (CURVE_POINT (curve, nextnext), candidate)); - - /* Start with the old point. */ - new_point = candidate; - sum = Vadd (in, out); - /* We added 2*n+2 points, so we have to divide the sum by 2*n+2 */ - new_point.x += sum.dx / 6; - new_point.y += sum.dy / 6; - new_point.z += sum.dz / 6; - if (fabs (prev_new_point.x - new_point.x) < 0.3 - && fabs (prev_new_point.y - new_point.y) < 0.3 - && fabs (prev_new_point.z - new_point.z) < 0.3) - { - collapsed = true; - break; - } - - - /* Put the newly computed point into a separate curve, so it - doesn't affect future computation (on this iteration). */ - append_point (newcurve, prev_new_point = new_point); - } - - if (collapsed) - free_curve (newcurve); - else - { - /* Just as with the first point, we have to keep the last point. */ - if (offset) - append_point (newcurve, LAST_CURVE_POINT (curve)); - - /* Set the original curve to the newly filtered one, and go again. */ - free_curve (curve); - *curve = *newcurve; - } - free (newcurve); - } - - log_curve (curve, false); -} - - - -/* Find reasonable values for t for each point on CURVE. The method is - called chord-length parameterization, which is described in Plass & - Stone. The basic idea is just to use the distance from one point to - the next as the t value, normalized to produce values that increase - from zero for the first point to one for the last point. */ - -static void -set_initial_parameter_values (curve_type curve) -{ - unsigned p; - - LOG ("\nAssigning initial t values:\n "); - - CURVE_T (curve, 0) = 0.0; - - for (p = 1; p < CURVE_LENGTH (curve); p++) - { - float_coord point = CURVE_POINT (curve, p), - previous_p = CURVE_POINT (curve, p - 1); - float d = distance (point, previous_p); - CURVE_T (curve, p) = CURVE_T (curve, p - 1) + d; - } - - assert (LAST_CURVE_T (curve) != 0.0); - - for (p = 1; p < CURVE_LENGTH (curve); p++) - CURVE_T (curve, p) = CURVE_T (curve, p) / LAST_CURVE_T (curve); - - log_entire_curve (curve); -} - -/* Find an approximation to the tangent to an endpoint of CURVE (to the - first point if TO_START_POINT is true, else the last). If - CROSS_CURVE is true, consider points on the adjacent curve to CURVE. - - It is important to compute an accurate approximation, because the - control points that we eventually decide upon to fit the curve will - be placed on the half-lines defined by the tangents and - endpoints...and we never recompute the tangent after this. */ - -static void -find_tangent (curve_type curve, bool to_start_point, bool cross_curve, - unsigned tangent_surround) -{ - vector_type tangent; - vector_type **curve_tangent = (to_start_point == true) ? &(CURVE_START_TANGENT (curve)) - : &(CURVE_END_TANGENT (curve)); - unsigned n_points = 0; - - LOG1 (" tangent to %s: ", (to_start_point == true) ? "start" : "end"); - - if (*curve_tangent == NULL) - { - MALLOCVAR_NOFAIL(*curve_tangent); - do - { - tangent = find_half_tangent (curve, to_start_point, &n_points, - tangent_surround); - - if ((cross_curve == true) || (CURVE_CYCLIC (curve) == true)) - { - curve_type adjacent_curve - = (to_start_point == true) ? PREVIOUS_CURVE (curve) : NEXT_CURVE (curve); - vector_type tangent2 - = (to_start_point == false) ? find_half_tangent (adjacent_curve, true, &n_points, - tangent_surround) : find_half_tangent (adjacent_curve, true, &n_points, - tangent_surround); - - LOG3 ("(adjacent curve half tangent (%.3f,%.3f,%.3f)) ", - tangent2.dx, tangent2.dy, tangent2.dz); - tangent = Vadd (tangent, tangent2); - } - tangent_surround--; - - } - while (tangent.dx == 0.0 && tangent.dy == 0.0); - - assert (n_points > 0); - **curve_tangent = Vmult_scalar (tangent, (float)(1.0 / n_points)); - if ((CURVE_CYCLIC (curve) == true) && CURVE_START_TANGENT (curve)) - *CURVE_START_TANGENT (curve) = **curve_tangent; - if ((CURVE_CYCLIC (curve) == true) && CURVE_END_TANGENT (curve)) - *CURVE_END_TANGENT (curve) = **curve_tangent; - } - else - LOG ("(already computed) "); - - LOG3 ("(%.3f,%.3f,%.3f).\n", (*curve_tangent)->dx, (*curve_tangent)->dy, (*curve_tangent)->dz); -} - -/* Find the change in y and change in x for `tangent_surround' (a global) - points along CURVE. Increment N_POINTS by the number of points we - actually look at. */ - -static vector_type -find_half_tangent (curve_type c, bool to_start_point, unsigned *n_points, - unsigned tangent_surround) -{ - unsigned p; - int factor = to_start_point ? 1 : -1; - unsigned tangent_index = to_start_point ? 0 : c->length - 1; - float_coord tangent_point = CURVE_POINT (c, tangent_index); - vector_type tangent = { 0.0, 0.0 }; - unsigned int surround; - - if ((surround = CURVE_LENGTH (c) / 2) > tangent_surround) - surround = tangent_surround; - - for (p = 1; p <= surround; p++) - { - int this_index = p * factor + tangent_index; - float_coord this_point; - - if (this_index < 0 || this_index >= (int) c->length) - break; - - this_point = CURVE_POINT (c, p * factor + tangent_index); - - /* Perhaps we should weight the tangent from `this_point' by some - factor dependent on the distance from the tangent point. */ - tangent = Vadd (tangent, - Vmult_scalar (Psubtract (this_point, tangent_point), - (float) factor)); - (*n_points)++; - } - - return tangent; -} - -/* When this routine is called, we have computed a spline representation - for the digitized curve. The question is, how good is it? If the - fit is very good indeed, we might have an error of zero on each - point, and then WORST_POINT becomes irrelevant. But normally, we - return the error at the worst point, and the index of that point in - WORST_POINT. The error computation itself is the Euclidean distance - from the original curve CURVE to the fitted spline SPLINE. */ - -static float -find_error (curve_type curve, spline_type spline, unsigned *worst_point, - at_exception_type * exception) -{ - unsigned this_point; - float total_error = 0.0; - float worst_error = FLT_MIN; - - *worst_point = CURVE_LENGTH (curve) + 1; /* A sentinel value. */ - - for (this_point = 0; this_point < CURVE_LENGTH (curve); this_point++) - { - float_coord curve_point = CURVE_POINT (curve, this_point); - float t = CURVE_T (curve, this_point); - float_coord spline_point = evaluate_spline (spline, t); - float this_error = distance (curve_point, spline_point); - if (this_error >= worst_error) - { - *worst_point = this_point; - worst_error = this_error; - } - total_error += this_error; - } - - if (*worst_point == CURVE_LENGTH (curve) + 1) - { /* Didn't have any ``worst point''; the error should be zero. */ - if (epsilon_equal (total_error, 0.0)) - LOG (" Every point fit perfectly.\n"); - else - { - LOG("No worst point found; something is wrong"); - at_exception_warning(exception, "No worst point found; something is wrong"); - } - } - else - { - if (epsilon_equal (total_error, 0.0)) - LOG (" Every point fit perfectly.\n"); - else - { - LOG5 (" Worst error (at (%.3f,%.3f,%.3f), point #%u) was %.3f.\n", - CURVE_POINT (curve, *worst_point).x, - CURVE_POINT (curve, *worst_point).y, - CURVE_POINT (curve, *worst_point).z, *worst_point, worst_error); - LOG1 (" Total error was %.3f.\n", total_error); - LOG2 (" Average error (over %u points) was %.3f.\n", - CURVE_LENGTH (curve), total_error / CURVE_LENGTH (curve)); - } - } - - return worst_error; -} - - -/* Lists of array indices (well, that is what we use it for). */ - -static index_list_type -new_index_list (void) -{ - index_list_type index_list; - - index_list.data = NULL; - INDEX_LIST_LENGTH (index_list) = 0; - - return index_list; -} - -static void -free_index_list (index_list_type *index_list) -{ - if (INDEX_LIST_LENGTH (*index_list) > 0) - { - free (index_list->data); - index_list->data = NULL; - INDEX_LIST_LENGTH (*index_list) = 0; - } -} - -static void -append_index (index_list_type *list, unsigned new_index) -{ - INDEX_LIST_LENGTH (*list)++; - REALLOCARRAY_NOFAIL(list->data, INDEX_LIST_LENGTH(*list)); - list->data[INDEX_LIST_LENGTH (*list) - 1] = new_index; -} - - -/* Return the Euclidean distance between P1 and P2. */ - -static float -distance (float_coord p1, float_coord p2) -{ - float x = p1.x - p2.x, y = p1.y - p2.y, z = p1.z - p2.z; - return (float) sqrt (SQR(x) + SQR(y) + SQR(z)); -} diff --git a/converter/other/pgmtopbm.c b/converter/other/pgmtopbm.c index 98bdc332..36b39017 100644 --- a/converter/other/pgmtopbm.c +++ b/converter/other/pgmtopbm.c @@ -11,10 +11,11 @@ */ #include <assert.h> + +#include "shhopt.h" #include "pgm.h" #include "dithers.h" #include "mallocvar.h" -#include "shhopt.h" enum halftone {QT_FS, QT_THRESH, QT_DITHER8, QT_CLUSTER, QT_HILBERT}; @@ -445,7 +446,7 @@ createFsConverter(unsigned int const cols, /* Initialize Floyd-Steinberg error vectors. */ MALLOCARRAY_NOFAIL(stateP->thiserr, cols + 2); MALLOCARRAY_NOFAIL(stateP->nexterr, cols + 2); - srand((int)(time(NULL) ^ getpid())); + srand(pm_randseed()); { /* (random errors in [-fs_scale/8 .. fs_scale/8]) */ diff --git a/converter/other/pgmtoppm.c b/converter/other/pgmtoppm.c index 7693fe0a..cface024 100644 --- a/converter/other/pgmtoppm.c +++ b/converter/other/pgmtoppm.c @@ -11,146 +11,229 @@ */ #include <string.h> + +#include "mallocvar.h" +#include "shhopt.h" #include "ppm.h" -int -main( argc, argv ) - int argc; - char* argv[]; - { - FILE* ifp; - gray* grayrow; - register gray* gP; - pixel p; - pixel* pixelrow; - register pixel* pP; - pixel** mappixels; - int argn, rows, cols, format, maprows, mapcols, mapmaxcolor, row; - register int col; - gray maxval; +struct cmdlineInfo { + /* All the information the user supplied in the command line, + in a form easy for the program to use. + */ + const char * inputFilename; /* '-' if stdin */ + const char * map; + const char * colorBlack; + const char * colorWhite; +}; + + + +static void +parseCommandLine(int argc, char ** argv, + struct cmdlineInfo * const cmdlineP) { +/*---------------------------------------------------------------------------- + parse program command line described in Unix standard form by argc + and argv. Return the information in the options as *cmdlineP. + + If command line is internally inconsistent (invalid options, etc.), + issue error message to stderr and abort program. + + Note that the strings we return are stored in the storage that + was passed to us as the argv array. We also trash *argv. +-----------------------------------------------------------------------------*/ + optEntry *option_def; + /* Instructions to optParseOptions3 on how to parse our options. + */ + optStruct3 opt; + + unsigned int option_def_index; + + unsigned int mapSpec; + + MALLOCARRAY_NOFAIL(option_def, 100); + + option_def_index = 0; /* incremented by OPTENT3 */ + OPTENT3(0, "map", OPT_STRING, &cmdlineP->map, + &mapSpec, 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); + /* Uses and sets argc, argv, and some of *cmdlineP and others. */ + + if (!mapSpec) + cmdlineP->map = NULL; + + if (mapSpec) { + /* No color argument; only argument is file name */ + if (argc-1 < 1) + cmdlineP->inputFilename = "-"; + else { + cmdlineP->inputFilename = argv[1]; + if (argc-1 > 1) + pm_error("With -map option, there is at most one argument: " + "the file name. You specified %u", argc-1); + } + } else { + /* Arguments are color or color range and file name */ + if (argc-1 < 1) { + cmdlineP->colorBlack = "black"; + cmdlineP->colorWhite = "white"; + } else { + char * buffer = strdup(argv[1]); + char * hyphenPos = strchr(buffer, '-'); + if (hyphenPos) { + *hyphenPos = '\0'; + cmdlineP->colorBlack = buffer; + cmdlineP->colorWhite = hyphenPos+1; + } else { + cmdlineP->colorBlack = "black"; + cmdlineP->colorWhite = buffer; + } + } + if (argc-1 < 2) + cmdlineP->inputFilename = "-"; + else + cmdlineP->inputFilename = argv[2]; + + if (argc-1 > 2) + pm_error("Program takes at most 2 arguments: " + "color name/range and input file name. " + "You specified %u", argc-1); + } +} + + + +static void +convertWithMap(FILE * const ifP, + unsigned int const cols, + unsigned int const rows, + gray const maxval, + int const format, + const char * const mapFileName, + FILE * const ofP, + gray * const grayrow, + pixel * const pixelrow) { + + unsigned int row; + FILE * mapFileP; + int mapcols, maprows; pixval mapmaxval; - char* color0; - char* color1; + pixel ** mappixels; + unsigned int mapmaxcolor; + + mapFileP = pm_openr(mapFileName); + mappixels = ppm_readppm(mapFileP, &mapcols, &maprows, &mapmaxval); + pm_close(mapFileP); + mapmaxcolor = maprows * mapcols - 1; + + ppm_writeppminit(ofP, cols, rows, mapmaxval, 0); + + for (row = 0; row < rows; ++row) { + unsigned int col; + + pgm_readpgmrow(ifP, grayrow, cols, maxval, format); + + for (col = 0; col < cols; ++col) { + unsigned int c; + if (maxval == mapmaxcolor) + c = grayrow[col]; + else + c = grayrow[col] * mapmaxcolor / maxval; + pixelrow[col] = mappixels[c / mapcols][c % mapcols]; + } + ppm_writeppmrow(ofP, pixelrow, cols, mapmaxval, 0); + } + ppm_freearray(mappixels, maprows); +} + + + +static void +convertLinear(FILE * const ifP, + unsigned int const cols, + unsigned int const rows, + gray const maxval, + int const format, + const char * const colorNameBlack, + const char * const colorNameWhite, + FILE * const ofP, + gray * const grayrow, + pixel * const pixelrow) { + + pixel colorBlack, colorWhite; pixval red0, grn0, blu0, red1, grn1, blu1; - const char* const usage = "<colorspec> [pgmfile]\n <colorspec1>,<colorspec2> [pgmfile]\n -map mapfile [pgmfile]"; - - - ppm_init( &argc, argv ); - - argn = 1; - mappixels = (pixel**) 0; - - if ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' ) - { - if ( pm_keymatch( argv[argn], "-map", 2 ) ) - { - ++argn; - if ( argn == argc ) - pm_usage( usage ); - ifp = pm_openr( argv[argn] ); - mappixels = ppm_readppm( ifp, &mapcols, &maprows, &mapmaxval ); - pm_close( ifp ); - mapmaxcolor = maprows * mapcols - 1; - } - else - pm_usage( usage ); - ++argn; - } - - if ( mappixels == (pixel**) 0 ) - { - if ( argn == argc ) - pm_usage( usage ); - color0 = argv[argn]; - ++argn; - } - - if ( argn != argc ) - { - ifp = pm_openr( argv[argn] ); - ++argn; - } - else - ifp = stdin; + unsigned int row; + + ppm_writeppminit(ofP, cols, rows, maxval, 0); + + colorBlack = ppm_parsecolor(colorNameBlack, maxval); + colorWhite = ppm_parsecolor(colorNameWhite, maxval); + + red0 = PPM_GETR(colorBlack); + grn0 = PPM_GETG(colorBlack); + blu0 = PPM_GETB(colorBlack); + red1 = PPM_GETR(colorWhite); + grn1 = PPM_GETG(colorWhite); + blu1 = PPM_GETB(colorWhite); + + for (row = 0; row < rows; ++row) { + unsigned int col; + + pgm_readpgmrow(ifP, grayrow, cols, maxval, format); + + for (col = 0; col < cols; ++col) { + gray const input = grayrow[col]; + PPM_ASSIGN( + pixelrow[col], + (red0 * (maxval - input) + red1 * input) / maxval, + (grn0 * (maxval - input) + grn1 * input) / maxval, + (blu0 * (maxval - input) + blu1 * input) / maxval); + } + ppm_writeppmrow(ofP, pixelrow, cols, maxval, 0); + } +} - if ( argn != argc ) - pm_usage( usage ); - pgm_readpgminit( ifp, &cols, &rows, &maxval, &format ); - grayrow = pgm_allocrow( cols ); - if ( mappixels == (pixel**) 0 ) - ppm_writeppminit( stdout, cols, rows, (pixval) maxval, 0 ); - else - ppm_writeppminit( stdout, cols, rows, mapmaxval, 0 ); - pixelrow = ppm_allocrow( cols ); - - if ( mappixels == (pixel**) 0 ) - { - color1 = strchr( color0, '-' ); - if ( color1 == 0 ) - { - color1 = color0; - red0 = 0; - grn0 = 0; - blu0 = 0; - } - else - { - *color1 = '\0'; - ++color1; - p = ppm_parsecolor( color0, (pixval) maxval ); - red0 = PPM_GETR( p ); - grn0 = PPM_GETG( p ); - blu0 = PPM_GETB( p ); - } - p = ppm_parsecolor( color1, (pixval) maxval ); - red1 = PPM_GETR( p ); - grn1 = PPM_GETG( p ); - blu1 = PPM_GETB( p ); - } - - for ( row = 0; row < rows; ++row ) - { - pgm_readpgmrow( ifp, grayrow, cols, maxval, format ); - - if ( mappixels == (pixel**) 0 ) - { - for ( col = 0, gP = grayrow, pP = pixelrow; - col < cols; - ++col, ++gP, ++pP ) - PPM_ASSIGN( - *pP, - ( red0 * ( maxval - *gP ) + red1 * *gP ) / maxval, - ( grn0 * ( maxval - *gP ) + grn1 * *gP ) / maxval, - ( blu0 * ( maxval - *gP ) + blu1 * *gP ) / maxval ); - - } - else - { - register int c; - - for ( col = 0, gP = grayrow, pP = pixelrow; - col < cols; - ++col, ++gP, ++pP ) - { - if ( maxval == mapmaxcolor ) - c = *gP; - else - c = *gP * mapmaxcolor / maxval; - *pP = mappixels[c / mapcols][c % mapcols]; - } - } - - if (!mappixels) - ppm_writeppmrow( stdout, pixelrow, cols, (pixval) maxval, 0 ); + +int +main(int argc, + char * argv[]) { + + FILE * ifP; + struct cmdlineInfo cmdline; + gray * grayrow; + pixel * pixelrow; + int rows, cols, format; + gray maxval; + + ppm_init(&argc, argv); + + parseCommandLine(argc, argv, &cmdline); + + ifP = pm_openr(cmdline.inputFilename); + + pgm_readpgminit(ifP, &cols, &rows, &maxval, &format); + grayrow = pgm_allocrow(cols); + pixelrow = ppm_allocrow(cols); + + if (cmdline.map) + convertWithMap(ifP, cols, rows, maxval, format, cmdline.map, + stdout, grayrow, pixelrow); else - ppm_writeppmrow( stdout, pixelrow, cols, mapmaxval, 0 ); - } + convertLinear(ifP, cols, rows, maxval, format, + cmdline.colorBlack, cmdline.colorWhite, stdout, + grayrow, pixelrow); - pm_close( ifp ); + ppm_freerow(pixelrow); + pgm_freerow(grayrow); + pm_close(ifP); /* If the program failed, it previously aborted with nonzero completion code, via various function calls. */ return 0; - } +} diff --git a/converter/other/pngtopnm.c b/converter/other/pngtopnm.c index bb8afb8d..2b88bd1e 100644 --- a/converter/other/pngtopnm.c +++ b/converter/other/pngtopnm.c @@ -240,23 +240,12 @@ gamma_correct(png_uint_16 const v, -#ifdef __STDC__ static int iscolor (png_color c) -#else -static int iscolor (c) -png_color c; -#endif { return c.red != c.green || c.green != c.blue; } -#ifdef __STDC__ static void save_text (png_info *info_ptr, FILE *tfp) -#else -static void save_text (info_ptr, tfp) -png_info *info_ptr; -FILE *tfp; -#endif { int i, j, k; @@ -285,12 +274,7 @@ FILE *tfp; } } -#ifdef __STDC__ static void show_time (png_info *info_ptr) -#else -static void show_time (info_ptr) -png_info *info_ptr; -#endif { static const char * const month[] = { "", "January", "February", "March", "April", "May", "June", @@ -305,13 +289,7 @@ png_info *info_ptr; } } -#ifdef __STDC__ static void pngtopnm_error_handler (png_structp png_ptr, png_const_charp msg) -#else -static void pngtopnm_error_handler (png_ptr, msg) -png_structp png_ptr; -png_const_charp msg; -#endif { jmpbuf_wrapper *jmpbuf_ptr; diff --git a/converter/other/pnmtoddif.c b/converter/other/pnmtoddif.c index 65152865..3a910a7b 100644 --- a/converter/other/pnmtoddif.c +++ b/converter/other/pnmtoddif.c @@ -166,11 +166,7 @@ wr_int(unsigned char ** buffer, int val) } else { sign = val < 0 ? 0xff : 0x00; /* Sign bits */ length = 4; -#ifdef __STDC__ mask = 0xffu << 24; -#else - mask = 0xff << 24; -#endif while ((val & mask) == sign) { /* Find the smallest representation */ length--; mask >>= 8; diff --git a/converter/other/pnmtojpeg.c b/converter/other/pnmtojpeg.c index 9a247633..5a4c5511 100644 --- a/converter/other/pnmtojpeg.c +++ b/converter/other/pnmtojpeg.c @@ -330,23 +330,6 @@ parseCommandLine(const int argc, char ** argv, } -static void -compute_rescaling_array(JSAMPLE ** const rescale_p, const pixval maxval, - const struct jpeg_compress_struct cinfo); -static void -convert_scanlines(struct jpeg_compress_struct * const cinfo_p, FILE * const input_file, - const pixval maxval, const int input_fmt, - JSAMPLE xlate_table[]); - -static boolean read_quant_tables (j_compress_ptr cinfo, char * filename, - int scale_factor, boolean force_baseline); - -static boolean read_scan_script (j_compress_ptr cinfo, char * filename); - -static boolean set_quant_slots (j_compress_ptr cinfo, char *arg); - -static boolean set_sample_factors (j_compress_ptr cinfo, char *arg); - static void report_compressor(const struct jpeg_compress_struct cinfo) { @@ -423,6 +406,349 @@ setup_jpeg_density(struct jpeg_compress_struct * const cinfoP, +/*---------------------------------------------------------------------------- + The functions below here are essentially the file rdswitch.c from + the JPEG library. They perform the functions specifed by the following + pnmtojpeg options: + + -qtables file Read quantization tables from text file + -scans file Read scan script from text file + -qslots N[,N,...] Set component quantization table selectors + -sample HxV[,HxV,...] Set component sampling factors +-----------------------------------------------------------------------------*/ + +static int +text_getc (FILE * file) +/* Read next char, skipping over any comments (# to end of line) */ +/* A comment/newline sequence is returned as a newline */ +{ + register int ch; + + ch = getc(file); + if (ch == '#') { + do { + ch = getc(file); + } while (ch != '\n' && ch != EOF); + } + return ch; +} + + +static boolean +read_text_integer (FILE * file, long * result, int * termchar) +/* Read an unsigned decimal integer from a file, store it in result */ +/* Reads one trailing character after the integer; returns it in termchar */ +{ + register int ch; + register long val; + + /* Skip any leading whitespace, detect EOF */ + do { + ch = text_getc(file); + if (ch == EOF) { + *termchar = ch; + return FALSE; + } + } while (isspace(ch)); + + if (! isdigit(ch)) { + *termchar = ch; + return FALSE; + } + + val = ch - '0'; + while ((ch = text_getc(file)) != EOF) { + if (! isdigit(ch)) + break; + val *= 10; + val += ch - '0'; + } + *result = val; + *termchar = ch; + return TRUE; +} + + +static boolean +read_scan_integer (FILE * file, long * result, int * termchar) +/* Variant of read_text_integer that always looks for a non-space termchar; + * this simplifies parsing of punctuation in scan scripts. + */ +{ + register int ch; + + if (! read_text_integer(file, result, termchar)) + return FALSE; + ch = *termchar; + while (ch != EOF && isspace(ch)) + ch = text_getc(file); + if (isdigit(ch)) { /* oops, put it back */ + if (ungetc(ch, file) == EOF) + return FALSE; + ch = ' '; + } else { + /* Any separators other than ';' and ':' are ignored; + * this allows user to insert commas, etc, if desired. + */ + if (ch != EOF && ch != ';' && ch != ':') + ch = ' '; + } + *termchar = ch; + return TRUE; +} + + + +static boolean +read_scan_script(j_compress_ptr const cinfo, + const char * const filename) { +/*---------------------------------------------------------------------------- + Read a scan script from the specified text file. + Each entry in the file defines one scan to be emitted. + Entries are separated by semicolons ';'. + An entry contains one to four component indexes, + optionally followed by a colon ':' and four progressive-JPEG parameters. + The component indexes denote which component(s) are to be transmitted + in the current scan. The first component has index 0. + Sequential JPEG is used if the progressive-JPEG parameters are omitted. + The file is free format text: any whitespace may appear between numbers + and the ':' and ';' punctuation marks. Also, other punctuation (such + as commas or dashes) can be placed between numbers if desired. + Comments preceded by '#' may be included in the file. + Note: we do very little validity checking here; + jcmaster.c will validate the script parameters. +-----------------------------------------------------------------------------*/ + FILE * fp; + unsigned int nscans; + unsigned int ncomps; + int termchar; + long val; +#define MAX_SCANS 100 /* quite arbitrary limit */ + jpeg_scan_info scans[MAX_SCANS]; + + fp = fopen(filename, "r"); + if (fp == NULL) { + pm_message("Can't open scan definition file %s", filename); + return FALSE; + } + nscans = 0; + + while (read_scan_integer(fp, &val, &termchar)) { + ++nscans; /* We got another scan */ + if (nscans > MAX_SCANS) { + pm_message("Too many scans defined in file %s", filename); + fclose(fp); + return FALSE; + } + scans[nscans-1].component_index[0] = (int) val; + ncomps = 1; + while (termchar == ' ') { + if (ncomps >= MAX_COMPS_IN_SCAN) { + pm_message("Too many components in one scan in file %s", + filename); + fclose(fp); + return FALSE; + } + if (! read_scan_integer(fp, &val, &termchar)) + goto bogus; + scans[nscans-1].component_index[ncomps] = (int) val; + ++ncomps; + } + scans[nscans-1].comps_in_scan = ncomps; + if (termchar == ':') { + if (! read_scan_integer(fp, &val, &termchar) || termchar != ' ') + goto bogus; + scans[nscans-1].Ss = (int) val; + if (! read_scan_integer(fp, &val, &termchar) || termchar != ' ') + goto bogus; + scans[nscans-1].Se = (int) val; + if (! read_scan_integer(fp, &val, &termchar) || termchar != ' ') + goto bogus; + scans[nscans-1].Ah = (int) val; + if (! read_scan_integer(fp, &val, &termchar)) + goto bogus; + scans[nscans-1].Al = (int) val; + } else { + /* set non-progressive parameters */ + scans[nscans-1].Ss = 0; + scans[nscans-1].Se = DCTSIZE2-1; + scans[nscans-1].Ah = 0; + scans[nscans-1].Al = 0; + } + if (termchar != ';' && termchar != EOF) { + bogus: + pm_message("Invalid scan entry format in file %s", filename); + fclose(fp); + return FALSE; + } + } + + if (termchar != EOF) { + pm_message("Non-numeric data in file %s", filename); + fclose(fp); + return FALSE; + } + + if (nscans > 0) { + /* Stash completed scan list in cinfo structure. NOTE: in + this program, JPOOL_IMAGE is the right lifetime for this + data, but if you want to compress multiple images you'd + want JPOOL_PERMANENT. + */ + const unsigned int scan_info_size = nscans * sizeof(jpeg_scan_info); + jpeg_scan_info * const scan_info = + (jpeg_scan_info *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + scan_info_size); + memcpy(scan_info, scans, scan_info_size); + cinfo->scan_info = scan_info; + cinfo->num_scans = nscans; + } + + fclose(fp); + return TRUE; +} + + + +static boolean +read_quant_tables (j_compress_ptr cinfo, char * filename, + int scale_factor, boolean force_baseline) +/* Read a set of quantization tables from the specified file. + * The file is plain ASCII text: decimal numbers with whitespace between. + * Comments preceded by '#' may be included in the file. + * There may be one to NUM_QUANT_TBLS tables in the file, each of 64 values. + * The tables are implicitly numbered 0,1,etc. + * NOTE: does not affect the qslots mapping, which will default to selecting + * table 0 for luminance (or primary) components, 1 for chrominance components. + * You must use -qslots if you want a different component->table mapping. + */ +{ + FILE * fp; + int tblno, i, termchar; + long val; + unsigned int table[DCTSIZE2]; + + if ((fp = fopen(filename, "rb")) == NULL) { + pm_message("Can't open table file %s", filename); + return FALSE; + } + tblno = 0; + + while (read_text_integer(fp, &val, &termchar)) { + /* read 1st element of table */ + if (tblno >= NUM_QUANT_TBLS) { + pm_message("Too many tables in file %s", filename); + fclose(fp); + return FALSE; + } + table[0] = (unsigned int) val; + for (i = 1; i < DCTSIZE2; i++) { + if (! read_text_integer(fp, &val, &termchar)) { + pm_message("Invalid table data in file %s", filename); + fclose(fp); + return FALSE; + } + table[i] = (unsigned int) val; + } + jpeg_add_quant_table(cinfo, tblno, table, scale_factor, + force_baseline); + tblno++; + } + + if (termchar != EOF) { + pm_message("Non-numeric data in file %s", filename); + fclose(fp); + return FALSE; + } + + fclose(fp); + return TRUE; +} + + + +static boolean +set_quant_slots (j_compress_ptr cinfo, char *arg) +/* Process a quantization-table-selectors parameter string, of the form + * N[,N,...] + * If there are more components than parameters, the last value is replicated. + */ +{ + int val = 0; /* default table # */ + int ci; + char ch; + + for (ci = 0; ci < MAX_COMPONENTS; ci++) { + if (*arg) { + ch = ','; /* if not set by sscanf, will be ',' */ + if (sscanf(arg, "%d%c", &val, &ch) < 1) + return FALSE; + if (ch != ',') /* syntax check */ + return FALSE; + if (val < 0 || val >= NUM_QUANT_TBLS) { + pm_message("Invalid quantization table number: %d. " + "JPEG quantization tables are numbered 0..%d", + val, NUM_QUANT_TBLS - 1); + return FALSE; + } + cinfo->comp_info[ci].quant_tbl_no = val; + while (*arg && *arg++ != ',') + /* advance to next segment of arg string */ + ; + } else { + /* reached end of parameter, set remaining components to last tbl*/ + cinfo->comp_info[ci].quant_tbl_no = val; + } + } + return TRUE; +} + + +static boolean +set_sample_factors (j_compress_ptr cinfo, char *arg) +/* Process a sample-factors parameter string, of the form + * HxV[,HxV,...] + * If there are more components than parameters, "1x1" is assumed for the rest. + */ +{ + int ci, val1, val2; + char ch1, ch2; + + for (ci = 0; ci < MAX_COMPONENTS; ci++) { + if (*arg) { + ch2 = ','; /* if not set by sscanf, will be ',' */ + if (sscanf(arg, "%d%c%d%c", &val1, &ch1, &val2, &ch2) < 3) + return FALSE; + if ((ch1 != 'x' && ch1 != 'X') || ch2 != ',') /* syntax check */ + return FALSE; + if (val1 <= 0 || val1 > 4) { + pm_message("Invalid sampling factor: %d. " + "JPEG sampling factors must be 1..4", val1); + return FALSE; + } + if (val2 <= 0 || val2 > 4) { + pm_message("Invalid sampling factor: %d. " + "JPEG sampling factors must be 1..4", val2); + return FALSE; + } + cinfo->comp_info[ci].h_samp_factor = val1; + cinfo->comp_info[ci].v_samp_factor = val2; + while (*arg && *arg++ != ',') + /* advance to next segment of arg string */ + ; + } else { + /* reached end of parameter, set remaining components + to 1x1 sampling */ + cinfo->comp_info[ci].h_samp_factor = 1; + cinfo->comp_info[ci].v_samp_factor = 1; + } + } + return TRUE; +} + + + static void setup_jpeg(struct jpeg_compress_struct * const cinfoP, struct jpeg_error_mgr * const jerrP, @@ -692,348 +1018,16 @@ convert_scanlines(struct jpeg_compress_struct * const cinfo_p, } -/*---------------------------------------------------------------------------- - The functions below here are essentially the file rdswitch.c from - the JPEG library. They perform the functions specifed by the following - pnmtojpeg options: - - -qtables file Read quantization tables from text file - -scans file Read scan script from text file - -qslots N[,N,...] Set component quantization table selectors - -sample HxV[,HxV,...] Set component sampling factors ------------------------------------------------------------------------------*/ - -static int -text_getc (FILE * file) -/* Read next char, skipping over any comments (# to end of line) */ -/* A comment/newline sequence is returned as a newline */ -{ - register int ch; - - ch = getc(file); - if (ch == '#') { - do { - ch = getc(file); - } while (ch != '\n' && ch != EOF); - } - return ch; -} - - -static boolean -read_text_integer (FILE * file, long * result, int * termchar) -/* Read an unsigned decimal integer from a file, store it in result */ -/* Reads one trailing character after the integer; returns it in termchar */ -{ - register int ch; - register long val; - - /* Skip any leading whitespace, detect EOF */ - do { - ch = text_getc(file); - if (ch == EOF) { - *termchar = ch; - return FALSE; - } - } while (isspace(ch)); - - if (! isdigit(ch)) { - *termchar = ch; - return FALSE; - } - - val = ch - '0'; - while ((ch = text_getc(file)) != EOF) { - if (! isdigit(ch)) - break; - val *= 10; - val += ch - '0'; - } - *result = val; - *termchar = ch; - return TRUE; -} - - -static boolean -read_quant_tables (j_compress_ptr cinfo, char * filename, - int scale_factor, boolean force_baseline) -/* Read a set of quantization tables from the specified file. - * The file is plain ASCII text: decimal numbers with whitespace between. - * Comments preceded by '#' may be included in the file. - * There may be one to NUM_QUANT_TBLS tables in the file, each of 64 values. - * The tables are implicitly numbered 0,1,etc. - * NOTE: does not affect the qslots mapping, which will default to selecting - * table 0 for luminance (or primary) components, 1 for chrominance components. - * You must use -qslots if you want a different component->table mapping. - */ -{ - FILE * fp; - int tblno, i, termchar; - long val; - unsigned int table[DCTSIZE2]; - - if ((fp = fopen(filename, "rb")) == NULL) { - pm_message("Can't open table file %s", filename); - return FALSE; - } - tblno = 0; - - while (read_text_integer(fp, &val, &termchar)) { /* read 1st element of table */ - if (tblno >= NUM_QUANT_TBLS) { - pm_message("Too many tables in file %s", filename); - fclose(fp); - return FALSE; - } - table[0] = (unsigned int) val; - for (i = 1; i < DCTSIZE2; i++) { - if (! read_text_integer(fp, &val, &termchar)) { - pm_message("Invalid table data in file %s", filename); - fclose(fp); - return FALSE; - } - table[i] = (unsigned int) val; - } - jpeg_add_quant_table(cinfo, tblno, table, scale_factor, - force_baseline); - tblno++; - } - - if (termchar != EOF) { - pm_message("Non-numeric data in file %s", filename); - fclose(fp); - return FALSE; - } - - fclose(fp); - return TRUE; -} - - -static boolean -read_scan_integer (FILE * file, long * result, int * termchar) -/* Variant of read_text_integer that always looks for a non-space termchar; - * this simplifies parsing of punctuation in scan scripts. - */ -{ - register int ch; - - if (! read_text_integer(file, result, termchar)) - return FALSE; - ch = *termchar; - while (ch != EOF && isspace(ch)) - ch = text_getc(file); - if (isdigit(ch)) { /* oops, put it back */ - if (ungetc(ch, file) == EOF) - return FALSE; - ch = ' '; - } else { - /* Any separators other than ';' and ':' are ignored; - * this allows user to insert commas, etc, if desired. - */ - if (ch != EOF && ch != ';' && ch != ':') - ch = ' '; - } - *termchar = ch; - return TRUE; -} - - -boolean -read_scan_script (j_compress_ptr cinfo, char * filename) -/* Read a scan script from the specified text file. - * Each entry in the file defines one scan to be emitted. - * Entries are separated by semicolons ';'. - * An entry contains one to four component indexes, - * optionally followed by a colon ':' and four progressive-JPEG parameters. - * The component indexes denote which component(s) are to be transmitted - * in the current scan. The first component has index 0. - * Sequential JPEG is used if the progressive-JPEG parameters are omitted. - * The file is free format text: any whitespace may appear between numbers - * and the ':' and ';' punctuation marks. Also, other punctuation (such - * as commas or dashes) can be placed between numbers if desired. - * Comments preceded by '#' may be included in the file. - * Note: we do very little validity checking here; - * jcmaster.c will validate the script parameters. - */ -{ - FILE * fp; - int nscans, ncomps, termchar; - long val; -#define MAX_SCANS 100 /* quite arbitrary limit */ - jpeg_scan_info scans[MAX_SCANS]; - - if ((fp = fopen(filename, "r")) == NULL) { - pm_message("Can't open scan definition file %s", filename); - return FALSE; - } - nscans = 0; - - while (read_scan_integer(fp, &val, &termchar)) { - nscans++; /* We got another scan */ - if (nscans > MAX_SCANS) { - pm_message("Too many scans defined in file %s", filename); - fclose(fp); - return FALSE; - } - scans[nscans-1].component_index[0] = (int) val; - ncomps = 1; - while (termchar == ' ') { - if (ncomps >= MAX_COMPS_IN_SCAN) { - pm_message("Too many components in one scan in file %s", - filename); - fclose(fp); - return FALSE; - } - if (! read_scan_integer(fp, &val, &termchar)) - goto bogus; - scans[nscans-1].component_index[ncomps] = (int) val; - ncomps++; - } - scans[nscans-1].comps_in_scan = ncomps; - if (termchar == ':') { - if (! read_scan_integer(fp, &val, &termchar) || termchar != ' ') - goto bogus; - scans[nscans-1].Ss = (int) val; - if (! read_scan_integer(fp, &val, &termchar) || termchar != ' ') - goto bogus; - scans[nscans-1].Se = (int) val; - if (! read_scan_integer(fp, &val, &termchar) || termchar != ' ') - goto bogus; - scans[nscans-1].Ah = (int) val; - if (! read_scan_integer(fp, &val, &termchar)) - goto bogus; - scans[nscans-1].Al = (int) val; - } else { - /* set non-progressive parameters */ - scans[nscans-1].Ss = 0; - scans[nscans-1].Se = DCTSIZE2-1; - scans[nscans-1].Ah = 0; - scans[nscans-1].Al = 0; - } - if (termchar != ';' && termchar != EOF) { - bogus: - pm_message("Invalid scan entry format in file %s", filename); - fclose(fp); - return FALSE; - } - } - - if (termchar != EOF) { - pm_message("Non-numeric data in file %s", filename); - fclose(fp); - return FALSE; - } - - if (nscans > 0) { - /* Stash completed scan list in cinfo structure. NOTE: in - * this program, JPOOL_IMAGE is the right lifetime for this - * data, but if you want to compress multiple images you'd - * want JPOOL_PERMANENT. - */ - const unsigned int scan_info_size = nscans * sizeof(jpeg_scan_info); - jpeg_scan_info * const scan_info = - (jpeg_scan_info *) - (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, - scan_info_size); - memcpy(scan_info, scans, scan_info_size); - cinfo->scan_info = scan_info; - cinfo->num_scans = nscans; - } - - fclose(fp); - return TRUE; -} - - -static boolean -set_quant_slots (j_compress_ptr cinfo, char *arg) -/* Process a quantization-table-selectors parameter string, of the form - * N[,N,...] - * If there are more components than parameters, the last value is replicated. - */ -{ - int val = 0; /* default table # */ - int ci; - char ch; - - for (ci = 0; ci < MAX_COMPONENTS; ci++) { - if (*arg) { - ch = ','; /* if not set by sscanf, will be ',' */ - if (sscanf(arg, "%d%c", &val, &ch) < 1) - return FALSE; - if (ch != ',') /* syntax check */ - return FALSE; - if (val < 0 || val >= NUM_QUANT_TBLS) { - pm_message("Invalid quantization table number: %d. " - "JPEG quantization tables are numbered 0..%d", - val, NUM_QUANT_TBLS - 1); - return FALSE; - } - cinfo->comp_info[ci].quant_tbl_no = val; - while (*arg && *arg++ != ',') - /* advance to next segment of arg string */ - ; - } else { - /* reached end of parameter, set remaining components to last tbl*/ - cinfo->comp_info[ci].quant_tbl_no = val; - } - } - return TRUE; -} - - -static boolean -set_sample_factors (j_compress_ptr cinfo, char *arg) -/* Process a sample-factors parameter string, of the form - * HxV[,HxV,...] - * If there are more components than parameters, "1x1" is assumed for the rest. - */ -{ - int ci, val1, val2; - char ch1, ch2; - - for (ci = 0; ci < MAX_COMPONENTS; ci++) { - if (*arg) { - ch2 = ','; /* if not set by sscanf, will be ',' */ - if (sscanf(arg, "%d%c%d%c", &val1, &ch1, &val2, &ch2) < 3) - return FALSE; - if ((ch1 != 'x' && ch1 != 'X') || ch2 != ',') /* syntax check */ - return FALSE; - if (val1 <= 0 || val1 > 4) { - pm_message("Invalid sampling factor: %d. " - "JPEG sampling factors must be 1..4", val1); - return FALSE; - } - if (val2 <= 0 || val2 > 4) { - pm_message("Invalid sampling factor: %d. " - "JPEG sampling factors must be 1..4", val2); - return FALSE; - } - cinfo->comp_info[ci].h_samp_factor = val1; - cinfo->comp_info[ci].v_samp_factor = val2; - while (*arg && *arg++ != ',') - /* advance to next segment of arg string */ - ; - } else { - /* reached end of parameter, set remaining components - to 1x1 sampling */ - cinfo->comp_info[ci].h_samp_factor = 1; - cinfo->comp_info[ci].v_samp_factor = 1; - } - } - return TRUE; -} - int -main(int argc, char ** argv) { +main(int argc, + char ** argv) { struct cmdlineInfo cmdline; struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; - FILE *input_file; + FILE * input_file; FILE * output_file; int height; /* height of the input image in rows, as specified by its header */ diff --git a/converter/other/pnmtopalm/Makefile b/converter/other/pnmtopalm/Makefile index d7bf2829..3d802ed6 100644 --- a/converter/other/pnmtopalm/Makefile +++ b/converter/other/pnmtopalm/Makefile @@ -25,7 +25,8 @@ $(BINARIES): %: %.o palmcolormap.o $(NETPBMLIB) $(LIBOPT) $(MATHLIB) $(LDLIBS) $(RPATH) $(LADD) gen_palm_colormap : % : %.c palmcolormap.o - $(CC) -I importinc $(CFLAGS) $(LDFLAGS) -o $@ $< palmcolormap.o \ + $(CC) -I importinc $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ \ + $< palmcolormap.o \ $(LIBOPTS) $(MATHLIB) $(LDLIBS) $(LADD) diff --git a/converter/other/pnmtopng.c b/converter/other/pnmtopng.c index ed245b93..4e267b99 100644 --- a/converter/other/pnmtopng.c +++ b/converter/other/pnmtopng.c @@ -383,7 +383,7 @@ parseCommandLine(int argc, char ** argv, 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); + optParseOptions3(&argc, argv, opt, sizeof(opt), 0); /* Uses and sets argc, argv, and some of *cmdlineP and others. */ @@ -962,7 +962,7 @@ analyzeAlpha(FILE * const ifp, gray const alphaMaxval, bool * const allOpaqueP, bool * const singleColorIsTransP, - pixel* const alphaTranscolorP) { + pixel * const alphaTranscolorP) { /*---------------------------------------------------------------------------- Get information about the alpha mask, in combination with the masked image, that Caller can use to choose the most efficient way to @@ -993,7 +993,7 @@ analyzeAlpha(FILE * const ifp, */ foundTransparentPixel = FALSE; /* initial assumption */ pm_seek2(ifp, &rasterPos, sizeof(rasterPos)); - for (row = 0 ; row < rows && !foundTransparentPixel ; ++row) { + for (row = 0; row < rows && !foundTransparentPixel; ++row) { int col; pnm_readpnmrow(ifp, xelrow, cols, maxval, format); for (col = 0; col < cols && !foundTransparentPixel; ++col) { @@ -2238,12 +2238,12 @@ convertpnm(struct cmdlineInfo const cmdline, of the input image. */ int transexact; - /* boolean: the user wants only the exact color he specified to be - transparent; not just something close to it. - */ + /* boolean: the user wants only the exact color he specified to be + transparent; not just something close to it. + */ int transparent; bool alpha; - /* There will be an alpha mask */ + /* There will be an alpha mask */ unsigned int pnm_meaningful_bits; pixel backcolor; /* The background color, with maxval equal to that of the input @@ -2700,7 +2700,7 @@ main(int argc, char *argv[]) { int errorlevel; - pnm_init (&argc, argv); + pnm_init(&argc, argv); parseCommandLine(argc, argv, &cmdline); |