/* ppmtoilbm.c - read a portable pixmap and produce an IFF ILBM file ** ** Copyright (C) 1989 by Jef Poskanzer. ** Modified by Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de) ** 20/Jun/93: ** - 24-bit capability (new options -24if, -24force) ** - HAM8 capability (well, anything from HAM3 to HAM(MAXPLANES)) ** - now writes up to 8 (16) planes (new options -maxplanes, -fixplanes) ** - colormap file (new option -map) ** - write colormap only (new option -cmaponly) ** - only writes CAMG chunk if it is a HAM-picture ** 29/Aug/93: ** - operates row-by-row whenever possible ** - faster colorscaling with lookup-table (~20% faster on HAM pictures) ** - options -ham8 and -ham6 now imply -hamforce ** 27/Nov/93: ** - byterun1 compression (this is now default) with new options: ** -compress, -nocompress, -cmethod, -savemem ** - floyd-steinberg error diffusion (for std+mapfile and HAM) ** - new options: -lace and -hires --> write CAMG chunk ** - LUT for luminance calculation (used by ppm_to_ham) ** 23/Oct/94: ** - rework of mapfile handling ** - added RGB8 & RGBN image types ** - added maskplane and transparent color capability ** - 24-bit & direct color modified to n-bit deep ILBM ** - removed "-savemem" option ** 22/Feb/95: ** - minor bugfixes ** - fixed "-camg 0" behaviour: now writes a CAMG chunk with value 0 ** - "-24if" is now default ** - "-mmethod" and "-cmethod" options accept numeric args and keywords ** - direct color (DCOL) reimplemented ** - mapfile useable for HAM ** - added HAM colormap "fixed" ** 29/Mar/95: ** - added HAM colormap "rgb4" and "rgb5" (compute with 4/5-bit table) ** - added IFF text chunks ** ** Feb 2010: afu ** - added dimension check to prevent short int from overflowing. ** ** June 2015: afu ** - moved byterun1 (or Packbits) compression to lib/util/runlenth.c ** - fixed bug with HAM -nocompress ** ** TODO: ** - multipalette capability (PCHG chunk) for std and HAM ** ** ** std HAM deep cmap RGB8 RGBN ** -------+-----+-----+-----+-----+-----+----- ** BMHD yes yes yes yes yes yes ** CMAP yes (1) no yes no no ** BODY yes yes yes no yes yes ** CAMG (2) yes (2) no yes yes ** nPlanes 1-16 3-16 3-48 0 25 13 ** ** (1): grayscale colormap ** (2): only if "-lace", "-hires" or "-camg" option used ** ** 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. */ #include #include #include "pm_c_util.h" #include "mallocvar.h" #include "ppm.h" #include "pbm.h" #include "runlength.h" #include "ilbm.h" #include "lum.h" /*#define DEBUG*/ #define MODE_RGB8 6 /* RGB8: 8-bit RGB */ #define MODE_RGBN 5 /* RGBN: 4-bit RGB */ #define MODE_CMAP 4 /* ILBM: colormap only */ #define MODE_DCOL 3 /* ILBM: direct color */ #define MODE_DEEP 2 /* ILBM: deep (24-bit) */ #define MODE_HAM 1 /* ILBM: hold-and-modify (HAM) */ #define MODE_NONE 0 /* ILBM: colormapped */ #define HAMMODE_GRAY 0 /* HAM colormap: grayscale */ #define HAMMODE_FIXED 1 /* HAM colormap: 7 "rays" in RGB cube */ #define HAMMODE_MAPFILE 2 /* HAM colormap: loaded from mapfile */ #define HAMMODE_RGB4 3 /* HAM colormap: compute, 4bit RGB */ #define HAMMODE_RGB5 4 /* HAM colormap: compute, 5bit RGB */ #define ECS_MAXPLANES 5 #define ECS_HAMPLANES 6 #define AGA_MAXPLANES 8 #define AGA_HAMPLANES 8 #define HAMMAXPLANES 10 /* maximum planes for HAM */ #define DEF_MAXPLANES ECS_MAXPLANES #define DEF_HAMPLANES ECS_HAMPLANES #define DEF_COMPRESSION cmpByteRun1 #define DEF_DEEPPLANES 8 #define DEF_DCOLPLANES 5 #define DEF_IFMODE MODE_DEEP #define INT16MAX 32767 #define PAD(n) (ODD(n) ? 1 : 0) /* pad to a word */ /* global data */ static unsigned char *coded_rowbuf; /* buffer for uncompressed scanline */ static unsigned char *compr_rowbuf; /* buffer for compressed scanline */ static pixel **pixels; /* PPM image (NULL for row-by-row operation) */ static pixel *pixrow; /* current row in PPM image (pointer into pixels array, or buffer for row-by-row operation) */ static long viewportmodes = 0; static int slicesize = 1; /* rows per slice for multipalette images - NOT USED */ static unsigned char compmethod = DEF_COMPRESSION; /* default compression */ static unsigned char maskmethod = mskNone; static pixel *transpColor = NULL; /* transparent color */ static short transpIndex = -1; /* index of transparent color */ static short hammapmode = HAMMODE_GRAY; static short sortcmap = 0; /* sort colormap */ static FILE *maskfile = NULL; static bit *maskrow = NULL; static int maskcols, maskformat; #define TOTALPLANES(nplanes) ((nplanes) + ((maskmethod == mskHasMask) ? 1 : 0)) #define ROWS_PER_BLOCK 1024 typedef struct bodyblock { int used; unsigned char *row[ROWS_PER_BLOCK]; int len[ROWS_PER_BLOCK]; struct bodyblock *next; } bodyblock; static bodyblock firstblock = { 0 }; static bodyblock *cur_block = &firstblock; static char *anno_chunk, *auth_chunk, *name_chunk, *text_chunk, *copyr_chunk; /* flags */ static short compr_force = 0; /* force compressed output, even if the image got larger - NOT USED */ static short gen_camg = 0; /* write CAMG chunk */ #define WORSTCOMPR(bytes) ((bytes) + (bytes)/128 + 1) #define DO_COMPRESS (compmethod != cmpNone) #define NEWDEPTH(pix, table) PPM_ASSIGN((pix), (table)[PPM_GETR(pix)], (table)[PPM_GETG(pix)], (table)[PPM_GETB(pix)]) #define putByte(b) (void)(putc((unsigned char)(b), stdout)) /************ other utility functions ************/ static void writeBytes(unsigned char * const buffer, int const bytes) { if( fwrite(buffer, 1, bytes, stdout) != bytes ) pm_error("write error"); } static int * makeValTable(int const oldmaxval, int const newmaxval) { unsigned int i; int * table; MALLOCARRAY_NOFAIL(table, oldmaxval + 1); for (i = 0; i <= oldmaxval; ++i) table[i] = ROUNDDIV(i * newmaxval, oldmaxval); return table; } static int gFormat; static int gCols; static int gMaxval; static void initRead(FILE * const fp, int * const colsP, int * const rowsP, pixval * const maxvalP, int * const formatP, int const readall) { ppm_readppminit(fp, colsP, rowsP, maxvalP, formatP); if( *rowsP >INT16MAX || *colsP >INT16MAX ) pm_error ("Input image is too large."); if( readall ) { int row; pixels = ppm_allocarray(*colsP, *rowsP); for( row = 0; row < *rowsP; row++ ) ppm_readppmrow(fp, pixels[row], *colsP, *maxvalP, *formatP); /* pixels = ppm_readppm(fp, colsP, rowsP, maxvalP); */ } else { pixrow = ppm_allocrow(*colsP); } gCols = *colsP; gMaxval = *maxvalP; gFormat = *formatP; } static pixel * nextPixrow(FILE * const fp, int const row) { if( pixels ) pixrow = pixels[row]; else { ppm_readppmrow(fp, pixrow, gCols, gMaxval, gFormat); } if( maskrow ) { int col; if( maskfile ) pbm_readpbmrow(maskfile, maskrow, maskcols, maskformat); else { for( col = 0; col < gCols; col++ ) maskrow[col] = PBM_BLACK; } if( transpColor ) { for( col = 0; col < gCols; col++ ) if( PPM_EQUAL(pixrow[col], *transpColor) ) maskrow[col] = PBM_WHITE; } } return pixrow; } /************ ILBM functions ************/ static int lengthOfTextChunks(void) { int len, n; len = 0; if( anno_chunk ) { n = strlen(anno_chunk); len += 4 + 4 + n + PAD(n); /* ID chunksize text */ } if( auth_chunk ) { n = strlen(auth_chunk); len += 4 + 4 + n + PAD(n); /* ID chunksize text */ } if( name_chunk ) { n = strlen(name_chunk); len += 4 + 4 + n + PAD(n); /* ID chunksize text */ } if( copyr_chunk ) { n = strlen(copyr_chunk); len += 4 + 4 + n + PAD(n); /* ID chunksize text */ } if( text_chunk ) { n = strlen(text_chunk); len += 4 + 4 + n + PAD(n); /* ID chunksize text */ } return len; } static void writeTextChunks(void) { int n; if( anno_chunk ) { n = strlen(anno_chunk); pm_writebiglong(stdout, ID_ANNO); pm_writebiglong(stdout, n); writeBytes((unsigned char *)anno_chunk, n); if( ODD(n) ) putByte(0); } if( auth_chunk ) { n = strlen(auth_chunk); pm_writebiglong(stdout, ID_AUTH); pm_writebiglong(stdout, n); writeBytes((unsigned char *)auth_chunk, n); if( ODD(n) ) putByte(0); } if( copyr_chunk ) { n = strlen(copyr_chunk); pm_writebiglong(stdout, ID_copy); pm_writebiglong(stdout, n); writeBytes((unsigned char *)copyr_chunk, n); if( ODD(n) ) putByte(0); } if( name_chunk ) { n = strlen(name_chunk); pm_writebiglong(stdout, ID_NAME); pm_writebiglong(stdout, n); writeBytes((unsigned char *)name_chunk, n); if( ODD(n) ) putByte(0); } if( text_chunk ) { n = strlen(text_chunk); pm_writebiglong(stdout, ID_TEXT); pm_writebiglong(stdout, n); writeBytes((unsigned char *)text_chunk, n); if( ODD(n) ) putByte(0); } } static void writeCmap(pixel * const colormap, int const colors, int const maxval) { int cmapsize, i; cmapsize = 3 * colors; /* write colormap */ pm_writebiglong(stdout, ID_CMAP); pm_writebiglong(stdout, cmapsize); if( maxval != MAXCOLVAL ) { int *table; pm_message("maxval is not %d - automatically rescaling colors", MAXCOLVAL); table = makeValTable(maxval, MAXCOLVAL); for( i = 0; i < colors; i++ ) { putByte(table[PPM_GETR(colormap[i])]); putByte(table[PPM_GETG(colormap[i])]); putByte(table[PPM_GETB(colormap[i])]); } free(table); } else { for( i = 0; i < colors; i++ ) { putByte(PPM_GETR(colormap[i])); putByte(PPM_GETG(colormap[i])); putByte(PPM_GETB(colormap[i])); } } if( ODD(cmapsize) ) putByte(0); } static void writeBmhd(int const cols, int const rows, int const nPlanes) { unsigned char xasp, yasp; xasp = 10; /* initial value */ yasp = 10; /* initial value */ if( viewportmodes & vmLACE ) xasp *= 2; if( viewportmodes & vmHIRES ) yasp *= 2; pm_writebiglong(stdout, ID_BMHD); pm_writebiglong(stdout, BitMapHeaderSize); pm_writebigshort(stdout, cols); pm_writebigshort(stdout, rows); pm_writebigshort(stdout, 0); /* x-offset */ pm_writebigshort(stdout, 0); /* y-offset */ putByte(nPlanes); /* no of planes */ putByte(maskmethod); /* masking */ putByte(compmethod); /* compression */ putByte(BMHD_FLAGS_CMAPOK); /* flags */ if( maskmethod == mskHasTransparentColor ) pm_writebigshort(stdout, transpIndex); else pm_writebigshort(stdout, 0); putByte(xasp); /* x-aspect */ putByte(yasp); /* y-aspect */ pm_writebigshort(stdout, cols); /* pageWidth */ pm_writebigshort(stdout, rows); /* pageHeight */ } /************ compression ************/ static void storeBodyrow(unsigned char * const row, int const len) { int idx; idx = cur_block->used; /* initial value */ if (idx >= ROWS_PER_BLOCK) { MALLOCVAR_NOFAIL(cur_block->next); cur_block = cur_block->next; cur_block->used = idx = 0; cur_block->next = NULL; } MALLOCARRAY_NOFAIL(cur_block->row[idx], len); cur_block->len[idx] = len; memcpy(cur_block->row[idx], row, len); ++cur_block->used; } static unsigned int compressRow(unsigned int const bytes) { size_t compressedByteCt; switch (compmethod) { case cmpByteRun1: pm_rlenc_compressbyte( coded_rowbuf, compr_rowbuf, PM_RLE_PACKBITS, bytes, &compressedByteCt); break; default: pm_error("compressRow(): unknown compression method %d", compmethod); } storeBodyrow(compr_rowbuf, compressedByteCt); assert((unsigned)compressedByteCt == compressedByteCt); return (unsigned)compressedByteCt; } static const unsigned char bitmask[] = {1, 2, 4, 8, 16, 32, 64, 128}; static long encodeRow(FILE * const outfile, /* if non-NULL, write uncompressed row to this file */ rawtype * const rawrow, int const cols, int const nPlanes) { /* encode algorithm by Johan Widen (jw@jwdata.se) */ int plane, bytes; long retbytes = 0; bytes = ilbm_rowByteCt(cols); /* Encode and write raw bytes in plane-interleaved form. */ for( plane = 0; plane < nPlanes; plane++ ) { register int col, cbit; register rawtype *rp; register unsigned char *cp; int mask; mask = 1 << plane; cbit = -1; cp = coded_rowbuf-1; rp = rawrow; for( col = 0; col < cols; col++, cbit--, rp++ ) { if( cbit < 0 ) { cbit = 7; *++cp = 0; } if( *rp & mask ) *cp |= bitmask[cbit]; } if( outfile ) { writeBytes(coded_rowbuf, bytes); retbytes += bytes; } else retbytes += compressRow(bytes); } return retbytes; } static long encodeMaskrow(FILE * const ofP, rawtype * const rawrow, int const cols) { int col; for( col = 0; col < cols; col++ ) { if( maskrow[col] == PBM_BLACK ) rawrow[col] = 1; else rawrow[col] = 0; } return encodeRow(ofP, rawrow, cols, 1); } static void writeCamg(void) { pm_writebiglong(stdout, ID_CAMG); pm_writebiglong(stdout, CAMGChunkSize); pm_writebiglong(stdout, viewportmodes); } static void reportTooManyColors(int const ifmode, int const maxplanes, int const hamplanes, DirectColor const dcol, int const deepbits) { int const maxcolors = 1 << maxplanes; switch( ifmode ) { case MODE_HAM: pm_message("too many colors for %d planes - " "proceeding to write a HAM%d file", maxplanes, hamplanes); pm_message("if you want a non-HAM file, try doing a 'pnmquant %d'", maxcolors); break; case MODE_DCOL: pm_message("too many colors for %d planes - " "proceeding to write a %d:%d:%d direct color ILBM", maxplanes, dcol.r, dcol.g, dcol.b); pm_message("if you want a non-direct color file, " "try doing a 'pnmquant %d'", maxcolors); break; case MODE_DEEP: pm_message("too many colors for %d planes - " "proceeding to write a %d-bit \'deep\' ILBM", maxplanes, deepbits*3); pm_message("if you want a non-deep file, " "try doing a 'pnmquant %d'", maxcolors); break; default: pm_error("too many colors for %d planes - " "try doing a 'pnmquant %d'", maxplanes, maxcolors); break; } } static int getIntVal(const char * const string, const char * const option, int const bot, int const top) { int val; if (sscanf(string, "%d", &val) != 1 ) pm_error("option \"%s\" needs integer argument", option); if (val < bot || val > top) pm_error("option \"%s\" argument value out of range (%d..%d)", option, bot, top); return val; } static int getComprMethod(const char * const string) { int retval; if( pm_keymatch(string, "none", 1) || pm_keymatch(string, "0", 1) ) retval = cmpNone; else if( pm_keymatch(string, "byterun1", 1) || pm_keymatch(string, "1", 1) ) retval = cmpByteRun1; else pm_error("unknown compression method: %s", string); return retval; } static int getMaskType(const char * const string) { int retval; if( pm_keymatch(string, "none", 1) || pm_keymatch(string, "0", 1) ) retval = mskNone; else if( pm_keymatch(string, "plane", 1) || pm_keymatch(string, "maskplane", 1) || pm_keymatch(string, "1", 1) ) retval = mskHasMask; else if( pm_keymatch(string, "transparentcolor", 1) || pm_keymatch(string, "2", 1) ) retval = mskHasTransparentColor; else if( pm_keymatch(string, "lasso", 1) || pm_keymatch(string, "3", 1) ) retval = mskLasso; else pm_error("unknown masking method: %s", string); return retval; } static int getHammapMode(const char * const string) { int retval; if( pm_keymatch(string, "grey", 1) || pm_keymatch(string, "gray", 1) ) retval = HAMMODE_GRAY; else if( pm_keymatch(string, "fixed", 1) ) retval = HAMMODE_FIXED; else if( pm_keymatch(string, "rgb4", 4) ) retval = HAMMODE_RGB4; else if( pm_keymatch(string, "rgb5", 4) ) retval = HAMMODE_RGB5; else pm_error("unknown HAM colormap selection mode: %s", string); return retval; } /************ colormap file ************/ static void ppmToCmap(pixel * const colorrow, int const colors, int const maxval) { int formsize, cmapsize; cmapsize = colors * 3; formsize = 4 + /* ILBM */ 4 + 4 + BitMapHeaderSize + /* BMHD size header */ 4 + 4 + cmapsize + PAD(cmapsize) + /* CMAP size colormap */ lengthOfTextChunks(); pm_writebiglong(stdout, ID_FORM); pm_writebiglong(stdout, formsize); pm_writebiglong(stdout, ID_ILBM); writeBmhd(0, 0, 0); writeTextChunks(); writeCmap(colorrow, colors, maxval); } /************ HAM ************/ typedef struct { long count; pixval r, g, b; } hentry; #ifndef LITERAL_FN_DEF_MATCH static qsort_comparison_fn hcmp; #endif static int hcmp(const void * const a, const void * const b) { /* reverse sort, highest count first */ const hentry * const vaP = a; const hentry * const vbP = b; return(vbP->count - vaP->count); } static pixel * computeHamCmap(int const cols, int const rows, int const maxval, int const maxcolors, int * const colorsP, int const hbits) { int colors; hentry *hmap; pixel *cmap; pixval hmaxval; int i, r, g, b, col, row, *htable; unsigned long dist, maxdist; pm_message("initializing HAM colormap..."); colors = 1<<(3*hbits); MALLOCARRAY(hmap, colors); if (hmap == NULL) pm_error("Unable to allocate memory for HAM colormap."); hmaxval = pm_bitstomaxval(hbits); i = 0; for( r = 0; r <= hmaxval; r++ ) { for( g = 0; g <= hmaxval; g++ ) { for( b = 0; b <= hmaxval; b++ ) { hmap[i].r = r; hmap[i].g = g; hmap[i].b = b; hmap[i].count = 0; i++; } } } htable = makeValTable(maxval, hmaxval); for( row = 0; row < rows; row++ ) { unsigned int col; for( col = 0; col < cols; ++col) { pixel const p = pixels[row][col]; pixval const r = PPM_GETR(p); pixval const g = PPM_GETG(p); pixval const b = PPM_GETB(p); i = (htable[r]<<(2*hbits)) + (htable[g]<= 0; i-- ) { if( hmap[i].count ) break; } colors = i+1; if( colors > maxcolors ) { pm_message("selecting HAM colormap from %d colors...", colors); for( maxdist = 1; ; maxdist++ ) { for( col = colors-1; col > 0; col-- ) { r = hmap[col].r; g = hmap[col].g; b = hmap[col].b; for( i = 0; i < col; i++ ) { register int tmp; tmp = hmap[i].r - r; dist = tmp * tmp; tmp = hmap[i].g - g; dist += tmp * tmp; tmp = hmap[i].b - b; dist += tmp * tmp; if( dist <= maxdist ) { unsigned int sum = hmap[i].count + hmap[col].count; hmap[i].r = ROUNDDIV(hmap[i].r * hmap[i].count + r * hmap[col].count, sum); hmap[i].g = ROUNDDIV(hmap[i].g * hmap[i].count + g * hmap[col].count, sum); hmap[i].b = ROUNDDIV(hmap[i].b * hmap[i].count + b * hmap[col].count, sum); hmap[i].count = sum; hmap[col] = hmap[i]; /* temp store */ for( tmp = i-1; tmp >= 0 && hmap[tmp].count < hmap[col].count; tmp-- ) hmap[tmp+1] = hmap[tmp]; hmap[tmp+1] = hmap[col]; for( tmp = col; tmp < colors-1; tmp++ ) hmap[tmp] = hmap[tmp+1]; if( --colors <= maxcolors ) goto out; break; } } } #ifdef DEBUG pm_message("\tmaxdist=%ld: %d colors left", maxdist, colors); #endif } } out: pm_message("%d colors in HAM colormap", colors); cmap = ppm_allocrow(colors); *colorsP = colors; for( i = 0; i < colors; i++ ) { r = hmap[i].r; g = hmap[i].g; b = hmap[i].b; PPM_ASSIGN(cmap[i], r, g, b); } ppm_freerow(hmap); return cmap; } static void writeBodyRows(void) { bodyblock *b; int i; long total = 0; for( b = &firstblock; b != NULL; b = b->next ) { for( i = 0; i < b->used; i++ ) { writeBytes(b->row[i], b->len[i]); total += b->len[i]; } } if( ODD(total) ) putByte(0); } static long doHamBody(FILE * const ifP, FILE * const ofP, int const cols, int const rows, pixval const maxval, pixval const hammaxval, int const nPlanes, pixel * const colormap, int const colors) { int col, row, i; rawtype *raw_rowbuf; colorhash_table cht, cht2; long bodysize = 0; int *itoh; /* table image -> ham */ int usehash = 1; int colbits; int hamcode_red, hamcode_green, hamcode_blue; MALLOCARRAY_NOFAIL(raw_rowbuf, cols); cht = ppm_colorrowtocolorhash(colormap, colors); cht2 = ppm_alloccolorhash(); colbits = pm_maxvaltobits(hammaxval); hamcode_red = HAMCODE_RED << colbits; hamcode_green = HAMCODE_GREEN << colbits; hamcode_blue = HAMCODE_BLUE << colbits; itoh = makeValTable(maxval, hammaxval); for( row = 0; row < rows; row++ ) { int noprev; int spr, spg, spb; /* scaled values of previous pixel */ int upr, upg, upb; /* unscaled values of previous pixel, for floyd */ pixel *prow; noprev = 1; prow = nextPixrow(ifP, row); for( col = 0; col < cols; ++col ) { pixel const p = prow[col]; /* unscaled values of current pixel */ pixval const ur = PPM_GETR(p); pixval const ug = PPM_GETG(p); pixval const ub = PPM_GETB(p); /* scaled values of current pixel */ int const sr = itoh[ur]; int const sg = itoh[ug]; int const sb = itoh[ub]; i = ppm_lookupcolor(cht, &p); if( i == -1 ) { /* no matching color in cmap, find closest match */ int ucr, ucg, ucb; /* unscaled values of colormap entry */ if( hammapmode == HAMMODE_GRAY ) { if( maxval <= 255 ) /* Use fast approximation to 0.299 r + 0.587 g + 0.114 b. */ i = (int)ppm_fastlumin(p); else /* Can't use fast approximation, so fall back on floats. */ i = (int)(PPM_LUMIN(p) + 0.5); /* -IUW added '+ 0.5' */ i = itoh[i]; } else { i = ppm_lookupcolor(cht2, &p); if( i == -1 ) { i = ppm_findclosestcolor(colormap, colors, &p); if( usehash ) { if( ppm_addtocolorhash(cht2, &p, i) < 0 ) { pm_message("out of memory " "adding to hash table, " "proceeding without it"); usehash = 0; } } } } ucr = PPM_GETR(colormap[i]); ucg = PPM_GETG(colormap[i]); ucb = PPM_GETB(colormap[i]); if( noprev ) { /* no previous pixel, must use colormap */ raw_rowbuf[col] = i; /* + (HAMCODE_CMAP << colbits) */ upr = ucr; upg = ucg; upb = ucb; spr = itoh[upr]; spg = itoh[upg]; spb = itoh[upb]; noprev = 0; } else { register long di, dr, dg, db; int scr, scg, scb; /* scaled values of colormap entry */ scr = itoh[ucr]; scg = itoh[ucg]; scb = itoh[ucb]; /* compute distances for the four options */ #if 1 dr = abs(sg - spg) + abs(sb - spb); dg = abs(sr - spr) + abs(sb - spb); db = abs(sr - spr) + abs(sg - spg); di = abs(sr - scr) + abs(sg - scg) + abs(sb - scb); #else dr = (sg - spg)*(sg - spg) + (sb - spb)*(sb - spb); dg = (sr - spr)*(sr - spr) + (sb - spb)*(sb - spb); db = (sr - spr)*(sr - spr) + (sg - spg)*(sg - spg); di = (sr - scr)*(sr - scr) + (sg - scg)*(sg - scg) + (sb - scb)*(sb - scb); #endif if( di <= dr && di <= dg && di <= db ) { /* prefer colormap lookup */ raw_rowbuf[col] = i; upr = ucr; upg = ucg; upb = ucb; spr = scr; spg = scg; spb = scb; } else if( db <= dr && db <= dg ) { raw_rowbuf[col] = sb + hamcode_blue; spb = sb; upb = ub; } else if( dr <= dg ) { raw_rowbuf[col] = sr + hamcode_red; spr = sr; upr = ur; } else { raw_rowbuf[col] = sg + hamcode_green; spg = sg; upg = ug; } } } else { /* prefect match in cmap */ raw_rowbuf[col] = i; /* + (HAMCODE_CMAP << colbits) */ upr = PPM_GETR(colormap[i]); upg = PPM_GETG(colormap[i]); upb = PPM_GETB(colormap[i]); spr = itoh[upr]; spg = itoh[upg]; spb = itoh[upb]; } } bodysize += encodeRow(ofP, raw_rowbuf, cols, nPlanes); if( maskmethod == mskHasMask ) bodysize += encodeMaskrow(ofP, raw_rowbuf, cols); } if( ofP && ODD(bodysize) ) putByte(0); free(itoh); /* clean up */ free(raw_rowbuf); return bodysize; } static void ppmToHam(FILE * const ifP, int const cols, int const rows, int const maxval, pixel * const colormapArg, int const colorsArg, int const cmapmaxvalArg, int const hamplanes) { int hamcolors, nPlanes, i, hammaxval; long oldsize, bodysize, formsize, cmapsize; int * table; int colors; pixel * colormap; int cmapmaxval; table = NULL; /* initial value */ colors = colorsArg; /* initial value*/ colormap = colormapArg; /* initial value */ cmapmaxval = cmapmaxvalArg; /* initial value */ if( maskmethod == mskHasTransparentColor ) { pm_message("masking method '%s' not usable with HAM - " "using '%s' instead", mskNAME[mskHasTransparentColor], mskNAME[mskHasMask]); maskmethod = mskHasMask; } hamcolors = 1 << (hamplanes-2); hammaxval = pm_bitstomaxval(hamplanes-2); if( colors == 0 ) { /* no colormap, make our own */ switch( hammapmode ) { case HAMMODE_GRAY: colors = hamcolors; MALLOCARRAY_NOFAIL(colormap, colors); table = makeValTable(hammaxval, MAXCOLVAL); for( i = 0; i < colors; i++ ) PPM_ASSIGN(colormap[i], table[i], table[i], table[i]); free(table); cmapmaxval = MAXCOLVAL; break; case HAMMODE_FIXED: { int entries, val; double step; /* generate a colormap of 7 "rays" in an RGB color cube: r, g, b, r+g, r+b, g+b, r+g+b we need one colormap entry for black, so the number of entries per ray is (maxcolors-1)/7 */ entries = (hamcolors-1)/7; colors = 7*entries+1; MALLOCARRAY_NOFAIL(colormap, colors); step = (double)MAXCOLVAL / (double)entries; PPM_ASSIGN(colormap[0], 0, 0, 0); for( i = 1; i <= entries; i++ ) { val = (int)((double)i * step); PPM_ASSIGN(colormap[ i], val, 0, 0); /* r */ PPM_ASSIGN(colormap[ entries+i], 0, val, 0); /* g */ PPM_ASSIGN(colormap[2*entries+i], 0, 0, val); /* b */ PPM_ASSIGN(colormap[3*entries+i], val, val, 0); /* r+g */ PPM_ASSIGN(colormap[4*entries+i], val, 0, val); /* r+b */ PPM_ASSIGN(colormap[5*entries+i], 0, val, val); /* g+b */ PPM_ASSIGN(colormap[6*entries+i], val, val, val); /*r+g+b*/ } cmapmaxval = MAXCOLVAL; } break; case HAMMODE_RGB4: colormap = computeHamCmap(cols, rows, maxval, hamcolors, &colors, 4); cmapmaxval = 15; break; case HAMMODE_RGB5: colormap = computeHamCmap(cols, rows, maxval, hamcolors, &colors, 5); cmapmaxval = 31; break; default: pm_error("ppm_to_ham(): unknown hammapmode - can't happen"); } } else { hammapmode = HAMMODE_MAPFILE; if( colors > hamcolors ) { pm_message("colormap too large - using first %d colors", hamcolors); colors = hamcolors; } } if( cmapmaxval != maxval ) { int i, *table; pixel *newcmap; newcmap = ppm_allocrow(colors); table = makeValTable(cmapmaxval, maxval); for( i = 0; i < colors; i++ ) PPM_ASSIGN(newcmap[i], table[PPM_GETR(colormap[i])], table[PPM_GETG(colormap[i])], table[PPM_GETB(colormap[i])]); free(table); ppm_freerow(colormap); colormap = newcmap; } if( sortcmap ) ppm_sortcolorrow(colormap, colors, PPM_STDSORT); nPlanes = hamplanes; cmapsize = colors * 3; bodysize = oldsize = rows * TOTALPLANES(nPlanes) * ilbm_rowByteCt(cols); if( DO_COMPRESS ) { bodysize = doHamBody(ifP, NULL, cols, rows, maxval, hammaxval, nPlanes, colormap, colors); /*bodysize = doHamBody(ifP, NULL, cols, rows, maxval, hammaxval, nPlanes, colbits, nocolor);*/ if( bodysize > oldsize ) pm_message("warning - %s compression increases BODY size " "by %ld%%", cmpNAME[compmethod], 100*(bodysize-oldsize)/oldsize); else pm_message("BODY compression (%s): %ld%%", cmpNAME[compmethod], 100*(oldsize-bodysize)/oldsize); } formsize = 4 + /* ILBM */ 4 + 4 + BitMapHeaderSize + /* BMHD size header */ 4 + 4 + CAMGChunkSize + /* CAMG size viewportmodes */ 4 + 4 + cmapsize + PAD(cmapsize) + /* CMAP size colormap */ 4 + 4 + bodysize + PAD(bodysize) + /* BODY size data */ lengthOfTextChunks(); pm_writebiglong(stdout, ID_FORM); pm_writebiglong(stdout, formsize); pm_writebiglong(stdout, ID_ILBM); writeBmhd(cols, rows, nPlanes); writeTextChunks(); writeCamg(); /* HAM requires CAMG chunk */ writeCmap(colormap, colors, maxval); /* write body */ pm_writebiglong(stdout, ID_BODY); pm_writebiglong(stdout, bodysize); if( DO_COMPRESS ) writeBodyRows(); else doHamBody(ifP, stdout, cols, rows, maxval, hammaxval, nPlanes, colormap, colors); } /************ deep (24-bit) ************/ static long doDeepBody(FILE * const ifP, FILE * const ofP, int const cols, int const rows, pixval const maxval, int const bitspercolor) { int row, col; pixel *pP; int *table = NULL; long bodysize = 0; rawtype *redbuf, *greenbuf, *bluebuf; int newmaxval; MALLOCARRAY_NOFAIL(redbuf, cols); MALLOCARRAY_NOFAIL(greenbuf, cols); MALLOCARRAY_NOFAIL(bluebuf, cols); newmaxval = pm_bitstomaxval(bitspercolor); if( maxval != newmaxval ) { pm_message("maxval is not %d - automatically rescaling colors", newmaxval); table = makeValTable(maxval, newmaxval); } for( row = 0; row < rows; row++ ) { pP = nextPixrow(ifP, row); if( table ) { for( col = 0; col < cols; col++, pP++ ) { redbuf[col] = table[PPM_GETR(*pP)]; greenbuf[col] = table[PPM_GETG(*pP)]; bluebuf[col] = table[PPM_GETB(*pP)]; } } else { for( col = 0; col < cols; col++, pP++ ) { redbuf[col] = PPM_GETR(*pP); greenbuf[col] = PPM_GETG(*pP); bluebuf[col] = PPM_GETB(*pP); } } bodysize += encodeRow(ofP, redbuf, cols, bitspercolor); bodysize += encodeRow(ofP, greenbuf, cols, bitspercolor); bodysize += encodeRow(ofP, bluebuf, cols, bitspercolor); if( maskmethod == mskHasMask ) bodysize += encodeMaskrow(ofP, redbuf, cols); } if( ofP && ODD(bodysize) ) putByte(0); /* clean up */ if( table ) free(table); free(redbuf); free(greenbuf); free(bluebuf); return bodysize; } static void ppmToDeep(FILE * const ifP, int const cols, int const rows, int const maxval, int const bitspercolor) { int nPlanes; long bodysize, oldsize, formsize; if( maskmethod == mskHasTransparentColor ) { pm_message("masking method '%s' not usable with deep ILBM - " "using '%s' instead", mskNAME[mskHasTransparentColor], mskNAME[mskHasMask]); maskmethod = mskHasMask; } nPlanes = 3*bitspercolor; bodysize = oldsize = rows * TOTALPLANES(nPlanes) * ilbm_rowByteCt(cols); if( DO_COMPRESS ) { bodysize = doDeepBody(ifP, NULL, cols, rows, maxval, bitspercolor); if( bodysize > oldsize ) pm_message("warning - %s compression increases BODY size by %ld%%", cmpNAME[compmethod], 100*(bodysize-oldsize)/oldsize); else pm_message("BODY compression (%s): %ld%%", cmpNAME[compmethod], 100*(oldsize-bodysize)/oldsize); } formsize = 4 + /* ILBM */ 4 + 4 + BitMapHeaderSize + /* BMHD size header */ 4 + 4 + bodysize + PAD(bodysize) + /* BODY size data */ lengthOfTextChunks(); if( gen_camg ) formsize += 4 + 4 + CAMGChunkSize; /* CAMG size viewportmodes */ pm_writebiglong(stdout, ID_FORM); pm_writebiglong(stdout, formsize); pm_writebiglong(stdout, ID_ILBM); writeBmhd(cols, rows, nPlanes); writeTextChunks(); if( gen_camg ) writeCamg(); /* write body */ pm_writebiglong(stdout, ID_BODY); pm_writebiglong(stdout, bodysize); if( DO_COMPRESS ) writeBodyRows(); else doDeepBody(ifP, stdout, cols, rows, maxval, bitspercolor); } /************ direct color ************/ static long doDcolBody(FILE * const ifP, FILE * const ofP, int const cols, int const rows, pixval const maxval, DirectColor * const dcol) { int row, col; pixel *pP; long bodysize = 0; rawtype *redbuf, *greenbuf, *bluebuf; int *redtable, *greentable, *bluetable; MALLOCARRAY_NOFAIL(redbuf, cols); MALLOCARRAY_NOFAIL(greenbuf, cols); MALLOCARRAY_NOFAIL(bluebuf, cols); redtable = makeValTable(maxval, pm_bitstomaxval(dcol->r)); greentable = makeValTable(maxval, pm_bitstomaxval(dcol->g)); bluetable = makeValTable(maxval, pm_bitstomaxval(dcol->b)); for( row = 0; row < rows; row++ ) { pP = nextPixrow(ifP, row); for( col = 0; col < cols; col++, pP++ ) { redbuf[col] = redtable[PPM_GETR(*pP)]; greenbuf[col] = greentable[PPM_GETG(*pP)]; bluebuf[col] = bluetable[PPM_GETB(*pP)]; } bodysize += encodeRow(ofP, redbuf, cols, dcol->r); bodysize += encodeRow(ofP, greenbuf, cols, dcol->g); bodysize += encodeRow(ofP, bluebuf, cols, dcol->b); if( maskmethod == mskHasMask ) bodysize += encodeMaskrow(ofP, redbuf, cols); } if( ofP && ODD(bodysize) ) putByte(0); /* clean up */ free(redtable); free(greentable); free(bluetable); free(redbuf); free(greenbuf); free(bluebuf); return bodysize; } static void ppmToDcol(FILE * const ifP, int const cols, int const rows, int const maxval, DirectColor * const dcol) { int nPlanes; long bodysize, oldsize, formsize; if( maskmethod == mskHasTransparentColor ) { pm_message("masking method '%s' not usable with deep ILBM - " "using '%s' instead", mskNAME[mskHasTransparentColor], mskNAME[mskHasMask]); maskmethod = mskHasMask; } nPlanes = dcol->r + dcol->g + dcol->b; bodysize = oldsize = rows * TOTALPLANES(nPlanes) * ilbm_rowByteCt(cols); if( DO_COMPRESS ) { bodysize = doDcolBody(ifP, NULL, cols, rows, maxval, dcol); if( bodysize > oldsize ) pm_message("warning - %s compression increases BODY size by %ld%%", cmpNAME[compmethod], 100*(bodysize-oldsize)/oldsize); else pm_message("BODY compression (%s): %ld%%", cmpNAME[compmethod], 100*(oldsize-bodysize)/oldsize); } formsize = 4 + /* ILBM */ 4 + 4 + BitMapHeaderSize + /* BMHD size header */ 4 + 4 + DirectColorSize + /* DCOL size dcol */ 4 + 4 + bodysize + PAD(bodysize) + /* BODY size data */ lengthOfTextChunks(); if( gen_camg ) formsize += 4 + 4 + CAMGChunkSize; /* CAMG size viewportmodes */ pm_writebiglong(stdout, ID_FORM); pm_writebiglong(stdout, formsize); pm_writebiglong(stdout, ID_ILBM); writeBmhd(cols, rows, nPlanes); writeTextChunks(); pm_writebiglong(stdout, ID_DCOL); pm_writebiglong(stdout, DirectColorSize); putByte(dcol->r); putByte(dcol->g); putByte(dcol->b); putByte(0); /* pad */ if( gen_camg ) writeCamg(); /* write body */ pm_writebiglong(stdout, ID_BODY); pm_writebiglong(stdout, bodysize); if( DO_COMPRESS ) writeBodyRows(); else doDcolBody(ifP, stdout, cols, rows, maxval, dcol); } /************ normal colormapped ************/ static long doStdBody(FILE * const ifP, FILE * const ofP, int const cols, int const rows, pixval const maxval, pixel * const colormap, int const colors, int const nPlanes) { int row, col, i; pixel *pP; rawtype *raw_rowbuf; long bodysize = 0; int usehash = 1; colorhash_table cht; MALLOCARRAY_NOFAIL(raw_rowbuf, cols); cht = ppm_colorrowtocolorhash(colormap, colors); for( row = 0; row < rows; row++ ) { pixel *prow; prow = nextPixrow(ifP, row); for( col = 0; col < cols; ++col ) { pP = &prow[col]; if( maskmethod == mskHasTransparentColor && maskrow[col] == PBM_WHITE ) i = transpIndex; else { /* Check hash table to see if we have already matched this color. */ i = ppm_lookupcolor(cht, pP); if( i == -1 ) { i = ppm_findclosestcolor(colormap, colors, pP); /* No; search colormap for closest match. */ if( usehash ) { if( ppm_addtocolorhash(cht, pP, i) < 0 ) { pm_message("out of memory adding to hash table, " "proceeding without it"); usehash = 0; } } } } raw_rowbuf[col] = i; } bodysize += encodeRow(ofP, raw_rowbuf, cols, nPlanes); if( maskmethod == mskHasMask ) bodysize += encodeMaskrow(ofP, raw_rowbuf, cols); } if( ofP && ODD(bodysize) ) putByte(0); /* clean up */ ppm_freecolorhash(cht); free(raw_rowbuf); return bodysize; } static void ppmToStd(FILE * const ifP, int const cols, int const rows, int const maxval, pixel * const colormapArg, int const colorsArg, int const cmapmaxvalArg, int const maxcolors, int const nPlanes) { long formsize, cmapsize, bodysize, oldsize; int colors; pixel * colormap; int cmapmaxval; colors = colorsArg; /* initial value */ colormap = colormapArg; /* initial value */ cmapmaxval = cmapmaxvalArg; /* initial value */ if( maskmethod == mskHasTransparentColor ) { if( transpColor ) { transpIndex = ppm_addtocolorrow(colormap, &colors, maxcolors, transpColor); } else if( colors < maxcolors ) transpIndex = colors; if( transpIndex < 0 ) { pm_message("too many colors for masking method '%s' - " "using '%s' instead", mskNAME[mskHasTransparentColor], mskNAME[mskHasMask]); maskmethod = mskHasMask; } } if( cmapmaxval != maxval ) { int i, *table; pixel *newcmap; newcmap = ppm_allocrow(colors); table = makeValTable(cmapmaxval, maxval); for (i = 0; i < colors; ++i) PPM_ASSIGN(newcmap[i], table[PPM_GETR(colormap[i])], table[PPM_GETG(colormap[i])], table[PPM_GETB(colormap[i])]); free(table); colormap = newcmap; } if( sortcmap ) ppm_sortcolorrow(colormap, colors, PPM_STDSORT); bodysize = oldsize = rows * TOTALPLANES(nPlanes) * ilbm_rowByteCt(cols); if( DO_COMPRESS ) { bodysize = doStdBody(ifP, NULL, cols, rows, maxval, colormap, colors, nPlanes); if( bodysize > oldsize ) pm_message("warning - %s compression increases BODY size by %ld%%", cmpNAME[compmethod], 100*(bodysize-oldsize)/oldsize); else pm_message("BODY compression (%s): %ld%%", cmpNAME[compmethod], 100*(oldsize-bodysize)/oldsize); } cmapsize = colors * 3; formsize = 4 + /* ILBM */ 4 + 4 + BitMapHeaderSize + /* BMHD size header */ 4 + 4 + cmapsize + PAD(cmapsize) + /* CMAP size colormap */ 4 + 4 + bodysize + PAD(bodysize) + /* BODY size data */ lengthOfTextChunks(); if( gen_camg ) formsize += 4 + 4 + CAMGChunkSize; /* CAMG size viewportmodes */ pm_writebiglong(stdout, ID_FORM); pm_writebiglong(stdout, formsize); pm_writebiglong(stdout, ID_ILBM); writeBmhd(cols, rows, nPlanes); writeTextChunks(); if( gen_camg ) writeCamg(); writeCmap(colormap, colors, maxval); /* write body */ pm_writebiglong(stdout, ID_BODY); pm_writebiglong(stdout, bodysize); if( DO_COMPRESS ) writeBodyRows(); else doStdBody(ifP, stdout, cols, rows, maxval, colormap, colors, nPlanes); } /************ RGB8 ************/ static void ppmToRgb8(FILE * const ifP, int const cols, int const rows, int const maxval) { long bodysize, oldsize, formsize; pixel *pP; int *table = NULL; int row, col1, col2, compr_len, len; unsigned char *compr_row; maskmethod = 0; /* no masking - RGB8 uses genlock bits */ compmethod = 4; /* RGB8 files are always compressed */ MALLOCARRAY_NOFAIL(compr_row, cols * 4); if( maxval != 255 ) { pm_message("maxval is not 255 - automatically rescaling colors"); table = makeValTable(maxval, 255); } oldsize = cols * rows * 4; bodysize = 0; for( row = 0; row < rows; row++ ) { pP = nextPixrow(ifP, row); compr_len = 0; for( col1 = 0; col1 < cols; col1 = col2 ) { col2 = col1 + 1; if( maskrow ) { while( col2 < cols && PPM_EQUAL(pP[col1], pP[col2]) && maskrow[col1] == maskrow[col2] ) col2++; } else { while( col2 < cols && PPM_EQUAL(pP[col1], pP[col2]) ) col2++; } len = col2 - col1; while( len ) { int count; count = (len > 127 ? 127 : len); len -= count; if( table ) { compr_row[compr_len++] = table[PPM_GETR(pP[col1])]; compr_row[compr_len++] = table[PPM_GETG(pP[col1])]; compr_row[compr_len++] = table[PPM_GETB(pP[col1])]; } else { compr_row[compr_len++] = PPM_GETR(pP[col1]); compr_row[compr_len++] = PPM_GETG(pP[col1]); compr_row[compr_len++] = PPM_GETB(pP[col1]); } compr_row[compr_len] = count; if( maskrow && maskrow[col1] == PBM_WHITE ) compr_row[compr_len] |= 1<<7; /* genlock bit */ ++compr_len; } } storeBodyrow(compr_row, compr_len); bodysize += compr_len; } pm_message("BODY compression: %ld%%", 100*(oldsize-bodysize)/oldsize); formsize = 4 + /* RGB8 */ 4 + 4 + BitMapHeaderSize + /* BMHD size header */ 4 + 4 + CAMGChunkSize + /* CAMG size viewportmode */ 4 + 4 + bodysize + PAD(bodysize) + /* BODY size data */ lengthOfTextChunks(); /* write header */ pm_writebiglong(stdout, ID_FORM); pm_writebiglong(stdout, formsize); pm_writebiglong(stdout, ID_RGB8); writeBmhd(cols, rows, 25); writeTextChunks(); writeCamg(); /* RGB8 requires CAMG chunk */ pm_writebiglong(stdout, ID_BODY); pm_writebiglong(stdout, bodysize); writeBodyRows(); } /************ RGBN ************/ static void ppmToRgbn(FILE * const ifP, int const cols, int const rows, int const maxval) { long bodysize, oldsize, formsize; pixel *pP; int *table = NULL; int row, col1, col2, compr_len, len; unsigned char *compr_row; maskmethod = 0; /* no masking - RGBN uses genlock bits */ compmethod = 4; /* RGBN files are always compressed */ MALLOCARRAY_NOFAIL(compr_row, cols * 2); if( maxval != 15 ) { pm_message("maxval is not 15 - automatically rescaling colors"); table = makeValTable(maxval, 15); } oldsize = cols * rows * 2; bodysize = 0; for( row = 0; row < rows; row++ ) { pP = nextPixrow(ifP, row); compr_len = 0; for( col1 = 0; col1 < cols; col1 = col2 ) { col2 = col1 + 1; if( maskrow ) { while( col2 < cols && PPM_EQUAL(pP[col1], pP[col2]) && maskrow[col1] == maskrow[col2] ) col2++; } else { while( col2 < cols && PPM_EQUAL(pP[col1], pP[col2]) ) col2++; } len = col2 - col1; while( len ) { int count; count = (len > 65535 ? 65535 : len); len -= count; if( table ) { compr_row[compr_len] = table[PPM_GETR(pP[col1])] << 4; compr_row[compr_len] |= table[PPM_GETG(pP[col1])]; ++compr_len; compr_row[compr_len] = table[PPM_GETB(pP[col1])] << 4; } else { compr_row[compr_len] = PPM_GETR(pP[col1]) << 4; compr_row[compr_len] |= PPM_GETG(pP[col1]); ++compr_len; compr_row[compr_len] = PPM_GETB(pP[col1]) << 4; } if( maskrow && maskrow[col1] == PBM_WHITE ) compr_row[compr_len] |= 1<<3; /* genlock bit */ if( count <= 7 ) compr_row[compr_len++] |= count; /* 3 bit repeat count */ else { ++compr_len; /* 3 bit repeat count = 0 */ if( count <= 255 ) compr_row[compr_len++] = (unsigned char)count; /* byte repeat count */ else { compr_row[compr_len++] = (unsigned char)0; /* byte repeat count = 0 */ compr_row[compr_len++] = (count >> 8) & 0xff; /* word repeat count MSB */ compr_row[compr_len++] = count & 0xff; /* word repeat count LSB */ } } } } storeBodyrow(compr_row, compr_len); bodysize += compr_len; } pm_message("BODY compression: %ld%%", 100*(oldsize-bodysize)/oldsize); formsize = 4 + /* RGBN */ 4 + 4 + BitMapHeaderSize + /* BMHD size header */ 4 + 4 + CAMGChunkSize + /* CAMG size viewportmode */ 4 + 4 + bodysize + PAD(bodysize) + /* BODY size data */ lengthOfTextChunks(); /* write header */ pm_writebiglong(stdout, ID_FORM); pm_writebiglong(stdout, formsize); pm_writebiglong(stdout, ID_RGBN); writeBmhd(cols, rows, 13); writeTextChunks(); writeCamg(); /* RGBN requires CAMG chunk */ pm_writebiglong(stdout, ID_BODY); pm_writebiglong(stdout, bodysize); writeBodyRows(); } /************ multipalette ************/ #ifdef ILBM_PCHG static pixel *ppmslice[2]; /* need 2 for laced ILBMs, else 1 */ void ppmToPchg() { /* read first slice build a colormap from this slice select up to colors build colormap from selected colors map slice to colormap write slice while( !finished ) { read next slice compute distances for each pixel and select up to unused colors in this slice modify selected colors to the ones with maximum(?) distance map slice to colormap write slice } for HAM use a different mapping: compute distance to closest color in colormap if( there is no matching color in colormap ) { compute distances for the three "modify" cases use the shortest distance from the four cases } */ } #endif int main(int argc, char ** argv) { FILE * ifP; int argn, rows, cols, format, nPlanes; int ifmode, forcemode, maxplanes, fixplanes, mode; int hamplanes; int deepbits; /* bits per color component in deep ILBM */ DirectColor dcol; #define MAXCOLORS (1<= argc ) pm_error("-maxplanes requires a value"); maxplanes = getIntVal(argv[argn], argv[argn-1], 1, MAXPLANES); fixplanes = 0; } else if( pm_keymatch(argv[argn], "-fixplanes", 4) || pm_keymatch(argv[argn], "-fp", 3) ) { if( ++argn >= argc ) pm_error("-fixplanes requires a value"); fixplanes = getIntVal(argv[argn], argv[argn-1], 1, MAXPLANES); maxplanes = fixplanes; } else if( pm_keymatch(argv[argn], "-mapfile", 4) ) { if( ++argn >= argc ) pm_error("-mapfile requires a value"); mapfile = argv[argn]; } else if( pm_keymatch(argv[argn], "-mmethod", 3) ) { if( ++argn >= argc ) pm_error("-mmethod requires a value"); maskmethod = getMaskType(argv[argn]); switch( maskmethod ) { case mskNone: case mskHasMask: case mskHasTransparentColor: break; default: pm_error("This program does not know how to handle " "masking method '%s'", mskNAME[maskmethod]); } } else if( pm_keymatch(argv[argn], "-maskfile", 4) ) { if( ++argn >= argc ) pm_error("-maskfile requires a value"); maskfile = pm_openr(argv[argn]); if( maskmethod == mskNone ) maskmethod = mskHasMask; } else if( pm_keymatch(argv[argn], "-transparent", 3) ) { if( ++argn >= argc ) pm_error("-transparent requires a value"); transpname = argv[argn]; if( maskmethod == mskNone ) maskmethod = mskHasTransparentColor; } else if( pm_keymatch(argv[argn], "-sortcmap", 5) ) sortcmap = 1; else if( pm_keymatch(argv[argn], "-cmaponly", 3) ) { forcemode = MODE_CMAP; } else if( pm_keymatch(argv[argn], "-lace", 2) ) { slicesize = 2; viewportmodes |= vmLACE; gen_camg = 1; } else if( pm_keymatch(argv[argn], "-nolace", 4) ) { slicesize = 1; viewportmodes &= ~vmLACE; } else if( pm_keymatch(argv[argn], "-hires", 3) ) { viewportmodes |= vmHIRES; gen_camg = 1; } else if( pm_keymatch(argv[argn], "-nohires", 5) ) viewportmodes &= ~vmHIRES; else if( pm_keymatch(argv[argn], "-camg", 5) ) { char *tail; long value = 0L; if( ++argn >= argc ) pm_error("-camg requires a value"); value = strtol(argv[argn], &tail, 16); if(argv[argn] == tail) pm_error("-camg requires a value"); viewportmodes |= value; gen_camg = 1; } else if( pm_keymatch(argv[argn], "-ecs", 2) ) { maxplanes = ECS_MAXPLANES; hamplanes = ECS_HAMPLANES; } else if( pm_keymatch(argv[argn], "-aga", 3) ) { maxplanes = AGA_MAXPLANES; hamplanes = AGA_HAMPLANES; } else if( pm_keymatch(argv[argn], "-hamplanes", 5) ) { if( ++argn >= argc ) pm_error("-hamplanes requires a value"); hamplanes = getIntVal(argv[argn], argv[argn-1], 3, HAMMAXPLANES); } else if( pm_keymatch(argv[argn], "-hambits", 5) ) { if( ++argn >= argc ) pm_usage("-hambits requires a value"); hamplanes = getIntVal(argv[argn], argv[argn-1], 3, HAMMAXPLANES-2) +2; } else if( pm_keymatch(argv[argn], "-ham6", 5) ) { hamplanes = ECS_HAMPLANES; forcemode = MODE_HAM; } else if( pm_keymatch(argv[argn], "-ham8", 5) ) { hamplanes = AGA_HAMPLANES; forcemode = MODE_HAM; } else if( pm_keymatch(argv[argn], "-hammap", 5) ) { if( ++argn >= argc ) pm_error("-hammap requires a value"); hammapmode = getHammapMode(argv[argn]); } else if( pm_keymatch(argv[argn], "-hamif", 5) ) ifmode = MODE_HAM; else if( pm_keymatch(argv[argn], "-nohamif", 7) ) { if( ifmode == MODE_HAM ) ifmode = MODE_NONE; } else if( pm_keymatch(argv[argn], "-hamforce", 4) ) forcemode = MODE_HAM; else if( pm_keymatch(argv[argn], "-nohamforce", 6) ) { if( forcemode == MODE_HAM ) forcemode = MODE_NONE; } else if( pm_keymatch(argv[argn], "-24if", 4) ) { ifmode = MODE_DEEP; deepbits = 8; } else if( pm_keymatch(argv[argn], "-no24if", 6) ) { if( ifmode == MODE_DEEP ) ifmode = MODE_NONE; } else if( pm_keymatch(argv[argn], "-24force", 3) ) { forcemode = MODE_DEEP; deepbits = 8; } else if( pm_keymatch(argv[argn], "-no24force", 5) ) { if( forcemode == MODE_DEEP ) forcemode = MODE_NONE; } else if( pm_keymatch(argv[argn], "-deepplanes", 6) ) { if( ++argn >= argc ) pm_error("-deepplanes requires a value"); deepbits = getIntVal(argv[argn], argv[argn-1], 3, 3*MAXPLANES); if( deepbits % 3 != 0 ) pm_error("option \"%s\" argument value must be divisible by 3", argv[argn-1]); deepbits /= 3; } else if( pm_keymatch(argv[argn], "-deepbits", 6) ) { if( ++argn >= argc ) pm_error("-deepbits requires a value"); deepbits = getIntVal(argv[argn], argv[argn-1], 1, MAXPLANES); } else if( pm_keymatch(argv[argn], "-deepif", 6) ) ifmode = MODE_DEEP; else if( pm_keymatch(argv[argn], "-nodeepif", 8) ) { if( ifmode == MODE_DEEP ) ifmode = MODE_NONE; } else if( pm_keymatch(argv[argn], "-deepforce", 5) ) forcemode = MODE_DEEP; else if( pm_keymatch(argv[argn], "-nodeepforce", 7) ) { if( forcemode == MODE_DEEP ) forcemode = MODE_NONE; } else if( pm_keymatch(argv[argn], "-dcif", 4) ) ifmode = MODE_DCOL; else if( pm_keymatch(argv[argn], "-nodcif", 6) ) { if( ifmode == MODE_DCOL ) ifmode = MODE_NONE; } else if( pm_keymatch(argv[argn], "-dcforce", 4) ) forcemode = MODE_DCOL; else if( pm_keymatch(argv[argn], "-nodcforce", 6) ) { if( forcemode == MODE_DCOL ) forcemode = MODE_NONE; } else if( pm_keymatch(argv[argn], "-dcbits", 4) || pm_keymatch(argv[argn], "-dcplanes", 4) ) { if( argc - argn < 4 ) pm_error("-dcbits requires 4 arguments"); dcol.r = getIntVal(argv[argn+1], argv[argn], 1, MAXPLANES); dcol.g = getIntVal(argv[argn+2], argv[argn], 1, MAXPLANES); dcol.b = getIntVal(argv[argn+3], argv[argn], 1, MAXPLANES); argn += 3; } else if( pm_keymatch(argv[argn], "-normal", 4) ) { ifmode = forcemode = MODE_NONE; compmethod = DEF_COMPRESSION; } else if( pm_keymatch(argv[argn], "-compress", 4) ) { compr_force = 1; if( compmethod == cmpNone ) #if DEF_COMPRESSION == cmpNone compmethod = cmpByteRun1; #else compmethod = DEF_COMPRESSION; #endif } else if( pm_keymatch(argv[argn], "-nocompress", 4) ) { compr_force = 0; compmethod = cmpNone; } else if( pm_keymatch(argv[argn], "-cmethod", 4) ) { if( ++argn >= argc ) pm_error("-cmethod requires a value"); compmethod = getComprMethod(argv[argn]); } else if( pm_keymatch(argv[argn], "-nofloyd", 5) || pm_keymatch(argv[argn], "-nofs", 5) ) defunctArgs++; else if( pm_keymatch(argv[argn], "-annotation", 3) ) { if( ++argn >= argc ) pm_error("-annotation requires a value"); anno_chunk = argv[argn]; } else if( pm_keymatch(argv[argn], "-author", 3) ) { if( ++argn >= argc ) pm_error("-author requires a value"); auth_chunk = argv[argn]; } else if( pm_keymatch(argv[argn], "-copyright", 4) ) { if( ++argn >= argc ) pm_error("-copyright requires a value"); copyr_chunk = argv[argn]; } else if( pm_keymatch(argv[argn], "-name", 3) ) { if( ++argn >= argc ) pm_error("-name requires a value"); name_chunk = argv[argn]; } else if( pm_keymatch(argv[argn], "-text", 3) ) { if( ++argn >= argc ) pm_error("-text requires a value"); text_chunk = argv[argn]; } else pm_error("invalid option: %s", argv[argn]); ++argn; } if( argn < argc ) { ifP = pm_openr(argv[argn]); ++argn; } else ifP = stdin; if( argn != argc ) pm_error("Program takes no arguments."); mode = forcemode; switch(forcemode) { case MODE_HAM: if (hammapmode == HAMMODE_RGB4 || hammapmode == HAMMODE_RGB5) initRead(ifP, &cols, &rows, &maxval, &format, 1); else initRead(ifP, &cols, &rows, &maxval, &format, 0); break; case MODE_DCOL: case MODE_DEEP: mapfile = NULL; initRead(ifP, &cols, &rows, &maxval, &format, 0); break; case MODE_RGB8: mapfile = NULL; initRead(ifP, &cols, &rows, &maxval, &format, 0); break; case MODE_RGBN: mapfile = NULL; initRead(ifP, &cols, &rows, &maxval, &format, 0); break; case MODE_CMAP: /* Figure out the colormap. */ pm_message("computing colormap..."); colormap = ppm_mapfiletocolorrow(ifP, MAXCOLORS, &colors, &cmapmaxval); if (colormap == NULL) pm_error("too many colors - try doing a 'pnmquant %d'", MAXCOLORS); pm_message("%d colors found", colors); break; default: if (mapfile) initRead(ifP, &cols, &rows, &maxval, &format, 0); else { initRead(ifP, &cols, &rows, &maxval, &format, 1); /* read file into memory */ pm_message("computing colormap..."); colormap = ppm_computecolorrow(pixels, cols, rows, MAXCOLORS, &colors); if (colormap) { cmapmaxval = maxval; pm_message("%d colors found", colors); nPlanes = pm_maxvaltobits(colors-1); if (fixplanes > nPlanes) nPlanes = fixplanes; } else { /* too many colors */ mode = ifmode; reportTooManyColors(ifmode, maxplanes, hamplanes, dcol, deepbits ); } } } if (mapfile) { FILE * mapfp; pm_message("reading colormap file..."); mapfp = pm_openr(mapfile); colormap = ppm_mapfiletocolorrow(mapfp, MAXCOLORS, &colors, &cmapmaxval); pm_close(mapfp); if (colormap == NULL) pm_error("too many colors in mapfile for %d planes", maxplanes); if (colors == 0) pm_error("empty colormap??"); pm_message("%d colors found in colormap", colors); nPlanes = pm_maxvaltobits(colors-1); } if (maskmethod != mskNone) { if (transpname) { MALLOCVAR_NOFAIL(transpColor); *transpColor = ppm_parsecolor(transpname, maxval); } if (maskfile) { int maskrows; pbm_readpbminit(maskfile, &maskcols, &maskrows, &maskformat); if (maskcols < cols || maskrows < rows) pm_error("maskfile too small - try scaling it"); if (maskcols > cols || maskrows > rows) pm_message("warning - maskfile larger than image"); } else maskcols = rows; maskrow = pbm_allocrow(maskcols); } if (mode != MODE_CMAP) { unsigned int i; MALLOCARRAY_NOFAIL(coded_rowbuf, ilbm_rowByteCt(cols)); for (i = 0; i < ilbm_rowByteCt(cols); ++i) coded_rowbuf[i] = 0; if (DO_COMPRESS) pm_rlenc_allocoutbuf(&compr_rowbuf, ilbm_rowByteCt(cols), PM_RLE_PACKBITS); } switch (mode) { case MODE_HAM: viewportmodes |= vmHAM; ppmToHam(ifP, cols, rows, maxval, colormap, colors, cmapmaxval, hamplanes); break; case MODE_DEEP: ppmToDeep(ifP, cols, rows, maxval, deepbits); break; case MODE_DCOL: ppmToDcol(ifP, cols, rows, maxval, &dcol); break; case MODE_RGB8: ppmToRgb8(ifP, cols, rows, maxval); break; case MODE_RGBN: ppmToRgbn(ifP, cols, rows, maxval); break; case MODE_CMAP: ppmToCmap(colormap, colors, cmapmaxval); break; default: ppmToStd(ifP, cols, rows, maxval, colormap, colors, cmapmaxval, MAXCOLORS, nPlanes); break; } pm_close(ifP); return 0; }