#include "ppm.h" #include "mallocvar.h" /* * Yep, it's a very simple algorithm, but it was something I wanted to have * available. */ struct colorToGrayEntry { pixel color; gray gray; int frequency; }; /* * BUG: This number was chosen pretty arbitrarily. The program is * probably * only useful for a very small numbers of colors - and that's * not only * because of the O(n) search that's used. The idea lends * itself primarily * to low color (read: simple, machine generated) images. */ #define MAXCOLORS 255 static gray newGrayValue(pixel *pix, struct colorToGrayEntry *colorToGrayMap, int colors) { int color; /* * Allowing this to be O(n), since the program is intended for small * n. Later, perhaps sort by color (r, then g, then b) and bsearch. */ for (color = 0; color < colors; color++) { if (PPM_EQUAL(*pix, colorToGrayMap[color].color)) return colorToGrayMap[color].gray; } pm_error("This should never happen - contact the maintainer"); return (-1); } #ifndef LITERAL_FN_DEF_MATCH static qsort_comparison_fn cmpColorToGrayEntryByIntensity; #endif static int cmpColorToGrayEntryByIntensity(const void * const a, const void * const b) { const struct colorToGrayEntry * const entry1P = a; const struct colorToGrayEntry * const entry2P = b; return entry1P->gray - entry2P->gray; } #ifndef LITERAL_FN_DEF_MATCH static qsort_comparison_fn cmpColorToGrayEntryByFrequency; #endif static int cmpColorToGrayEntryByFrequency(const void * const a, const void * const b) { const struct colorToGrayEntry * const entry1P = a; const struct colorToGrayEntry * const entry2P = b; return entry1P->frequency - entry2P->frequency; } int main(int argc, char *argv[]) { FILE *ifp; int col, cols, row, rows, color, colors, argn; int frequency; pixval maxval; pixel **pixels; pixel *pP; colorhist_vector hist; gray *grayrow; gray *gP; struct colorToGrayEntry *colorToGrayMap; ppm_init(&argc, argv); argn = 1; /* Default is to sort colors by intensity */ frequency = 0; while (argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0') { if (pm_keymatch(argv[argn], "-frequency", 2)) frequency = 1; else if (pm_keymatch(argv[argn], "-intensity", 2)) frequency = 0; else pm_usage( "[-frequency|-intensity] [ppmfile]" ); ++argn; } if (argn < argc) { ifp = pm_openr(argv[argn]); ++argn; } else ifp = stdin; pixels = ppm_readppm(ifp, &cols, &rows, &maxval); pm_close(ifp); /* all done with the input file - it's entirely in memory */ /* * Compute a histogram of the colors in the input. This is good for * both frequency, and indirectly the intensity, of a color. */ hist = ppm_computecolorhist(pixels, cols, rows, MAXCOLORS, &colors); if (hist == (colorhist_vector) 0) /* * BUG: This perhaps should use an exponential backoff, in * the number of colors, until success - cf pnmcolormap's * approach. The results are then more what's expected, but * not necessarily very useful. */ pm_error("Too many colors - Try reducing with pnmquant"); /* copy the colors into another structure for sorting */ MALLOCARRAY(colorToGrayMap, colors); for (color = 0; color < colors; color++) { colorToGrayMap[color].color = hist[color].color; colorToGrayMap[color].frequency = hist[color].value; /* * This next is derivable, of course, but it's far faster to * store it precomputed. This can be skipped, when sorting * by frequency - but again, for a small number of colors * it's a small matter. */ colorToGrayMap[color].gray = ppm_luminosity(hist[color].color); } /* * sort by intensity - sorting by frequency (in the histogram) is * worth considering as a future addition. */ if (frequency) qsort(colorToGrayMap, colors, sizeof(struct colorToGrayEntry), &cmpColorToGrayEntryByFrequency); else qsort(colorToGrayMap, colors, sizeof(struct colorToGrayEntry), &cmpColorToGrayEntryByIntensity); /* * create mapping between the n colors in input, to n evenly spaced * grayscale intensities. This is done by overwriting the neatly * formed gray values corresponding to the input-colors, with a new * set of evenly spaced gray values. Since maxval can be changed on * a lark, we just use gray levels 0..colors-1, and adjust maxval * accordingly */ maxval = colors - 1; for (color = 0; color < colors; color++) colorToGrayMap[color].gray = color; /* write pgm file, mapping colors to intensities */ pgm_writepgminit(stdout, cols, rows, maxval, 0); grayrow = pgm_allocrow(cols); for (row = 0; row < rows; row++) { for (col = 0, pP = pixels[row], gP = grayrow; col < cols; col++, pP++, gP++) *gP = newGrayValue(pP, colorToGrayMap, colors); pgm_writepgmrow(stdout, grayrow, cols, maxval, 0); } pm_close(stdout); exit(0); }