From fa9ab28b7c110c8d0c9d7245a7e516597ab4adb4 Mon Sep 17 00:00:00 2001 From: giraffedata Date: Wed, 12 Mar 2014 02:06:43 +0000 Subject: Add -forensic git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@2159 9d0c8265-081b-0410-96cb-a4ca84ce46f8 --- analyzer/ppmhist.c | 315 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 270 insertions(+), 45 deletions(-) (limited to 'analyzer/ppmhist.c') diff --git a/analyzer/ppmhist.c b/analyzer/ppmhist.c index 2f6c9348..cc47bb82 100644 --- a/analyzer/ppmhist.c +++ b/analyzer/ppmhist.c @@ -20,17 +20,18 @@ enum sort {SORT_BY_FREQUENCY, SORT_BY_RGB}; -enum colorFmt {FMT_DECIMAL, FMT_HEX, FMT_FLOAT, FMT_PPMPLAIN}; +enum ColorFmt {FMT_DECIMAL, FMT_HEX, FMT_FLOAT, FMT_PPMPLAIN}; struct cmdline_info { /* All the information the user supplied in the command line, in a form easy for the program to use. */ const char * inputFileName; /* Name of input file */ - unsigned int noheader; /* -noheader option */ - enum colorFmt colorFmt; - unsigned int colorname; /* -colorname option */ - enum sort sort; /* -sort option */ + unsigned int noheader; + enum ColorFmt colorFmt; + unsigned int colorname; + enum sort sort; + unsigned int forensic; }; @@ -45,7 +46,7 @@ parseCommandLine(int argc, const char ** argv, optStruct3 opt; /* set by OPTENT3 */ optEntry * option_def; unsigned int option_def_index; - + unsigned int hexcolorOpt, floatOpt, mapOpt, nomapOpt; const char * sort_type; @@ -59,6 +60,7 @@ parseCommandLine(int argc, const char ** argv, OPTENT3(0, "float", OPT_FLAG, NULL, &floatOpt, 0); OPTENT3(0, "colorname", OPT_FLAG, NULL, &cmdlineP->colorname, 0); OPTENT3(0, "sort", OPT_STRING, &sort_type, NULL, 0); + OPTENT3(0, "forensic", OPT_FLAG, NULL, &cmdlineP->forensic, 0); opt.opt_table = option_def; opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ @@ -69,8 +71,9 @@ parseCommandLine(int argc, const char ** argv, pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0); /* Uses and sets argc, argv, and some of *cmdlineP and others. */ + free(option_def); - if (argc-1 == 0) + if (argc-1 == 0) cmdlineP->inputFileName = "-"; else if (argc-1 != 1) pm_error("Program takes zero or one argument (filename). You " @@ -84,9 +87,12 @@ parseCommandLine(int argc, const char ** argv, cmdlineP->colorFmt = FMT_HEX; else if (floatOpt) cmdlineP->colorFmt = FMT_FLOAT; - else if (mapOpt) + else if (mapOpt) { + if (cmdlineP->forensic) + pm_error("You cannot specify -map and -forensic together"); + cmdlineP->colorFmt = FMT_PPMPLAIN; - else + } else cmdlineP->colorFmt = FMT_DECIMAL; if (strcmp(sort_type, "frequency") == 0) @@ -161,8 +167,137 @@ rgbcompare(const void * const a, +static pixval +universalMaxval(pixval const maxval, + int const format) { +/*---------------------------------------------------------------------------- + A maxval that makes it impossible for a pixel to be invalid in an image that + states it maxval as 'maxval' and has format 'format'. + + E.g. in a one-byte-per-sample image, it's not possible to read a sample + value greater than 255, so a maxval of 255 makes it impossible for a sample + to be invalid. + + But: we never go above 65535, which means our maxval isn't entirely + universal. If the image is plain PPM, it could contain a pixel that + exceeds even that. +-----------------------------------------------------------------------------*/ + assert(0 < maxval && maxval < 65536); + + if (format == RPPM_FORMAT || format == RPGM_FORMAT) { + /* Raw PPM stream has either one or two bytes per pixel, depending + upon its stated maxval. + */ + if (maxval > 255) + return 65535; + else + return 255; + } else if (format == RPBM_FORMAT) { + /* A Raw PBM stream has one bit per pixel, which libnetpbm renders + as 0 or 255 when we read it. + */ + assert(maxval == 255); + return 255; + } else { + /* A plain PPM stream has essentially unlimited range in the + tokens that are supposed to be sample values. We arbitrarily draw + the line at 65535. + */ + return 65535; + } +} + + + +static bool +colorIsValid(pixel const color, + pixval const maxval) { + + pixval const r = PPM_GETR(color); + pixval const g = PPM_GETG(color); + pixval const b = PPM_GETB(color); + + return r <= maxval && g <= maxval && b <= maxval; +} + + + +static void +separateInvalidItems(colorhist_vector const chv, + colorhist_vector const chvInvalid, + pixval const maxval, + unsigned int const colorCt, + unsigned int * const validColorCtP) { +/*---------------------------------------------------------------------------- + Move invalid color entries from chv to chvInvalid. + Count how many color entries are valid. +-----------------------------------------------------------------------------*/ + unsigned int i; + unsigned int validCt; + unsigned int invalidCt; + + for (i = 0, validCt = 0, invalidCt = 0; i < colorCt; ++i) { + if (!colorIsValid(chv[i].color, maxval)) + chvInvalid[invalidCt++] = chv[i]; + else + chv[validCt++] = chv[i]; + } + *validColorCtP = validCt; +} + + + +static void +sortHistogramForensic(enum sort const sortFn, + colorhist_vector const chv, + colorhist_vector const chvInvalid, + pixval const maxval, + unsigned int const colorCt, + unsigned int * const validColorCtP) { + + unsigned int validColorCt; + + separateInvalidItems(chv, chvInvalid, maxval, colorCt, &validColorCt); + + { + int (*compare_function)(const void *, const void *); + + switch (sortFn) { + case SORT_BY_FREQUENCY: compare_function = countcompare; break; + case SORT_BY_RGB: compare_function = rgbcompare; break; + } + + qsort((void*) chv, validColorCt, + sizeof(struct colorhist_item), compare_function); + + qsort((void*) chvInvalid, colorCt - validColorCt, + sizeof(struct colorhist_item), compare_function); + } + *validColorCtP = validColorCt; +} + + + +static void +sortHistogramNormal(enum sort const sortFn, + colorhist_vector const chv, + unsigned int const colorCt) { + + int (*compare_function)(const void *, const void *); + + switch (sortFn) { + case SORT_BY_FREQUENCY: compare_function = countcompare; break; + case SORT_BY_RGB: compare_function = rgbcompare; break; + } + + qsort((void*) chv, colorCt, sizeof(struct colorhist_item), + compare_function); +} + + + static const char * -colornameLabel(pixel const color, +colornameLabel(pixel const color, pixval const maxval, unsigned int const nDictColor, pixel const dictColors[], @@ -172,15 +307,15 @@ colornameLabel(pixel const color, dictionary to it. If the name returned is not the exact color, prefix it with "*". Otherwise, prefix it with " ". - 'nDictColor', dictColors[], and dictColorNames[] are the color + 'nDictColor', dictColors[], and dictColorNames[] are the color dictionary. Return the name in static storage within this subroutine. -----------------------------------------------------------------------------*/ static char retval[32]; int colorIndex; - - pixel color255; + + pixel color255; /* The color, normalized to a maxval of 255: the maxval of a color dictionary. */ @@ -190,31 +325,31 @@ colornameLabel(pixel const color, colorIndex = ppm_findclosestcolor(dictColors, nDictColor, &color); assert(colorIndex >= 0 && colorIndex < nDictColor); - + if (PPM_EQUAL(dictColors[colorIndex], color)) STRSCPY(retval, " "); else STRSCPY(retval, "*"); - + STRSCAT(retval, dictColornames[colorIndex]); - + return retval; } - + static void -printColors(colorhist_vector const chv, - int const nColors, +printColors(colorhist_vector const chv, + int const colorCt, pixval const maxval, - enum colorFmt const colorFmt, + enum ColorFmt const colorFmt, unsigned int const nKnown, pixel const knownColors[], const char * const colornames[]) { int i; - for (i = 0; i < nColors; i++) { + for (i = 0; i < colorCt; i++) { pixval const r = PPM_GETR(chv[i].color); pixval const g = PPM_GETG(chv[i].color); pixval const b = PPM_GETB(chv[i].color); @@ -226,7 +361,7 @@ printColors(colorhist_vector const chv, const char * colornameValue; if (colornames) - colornameValue = colornameLabel(chv[i].color, maxval, + colornameValue = colornameLabel(chv[i].color, maxval, nKnown, knownColors, colornames); else colornameValue = ""; @@ -257,23 +392,97 @@ printColors(colorhist_vector const chv, +static void +summarizeInvalidPixels(unsigned long int const validPixelCt, + unsigned long int const invalidPixelCt, + pixval const maxval) { +/*---------------------------------------------------------------------------- + Print total count of valid and invalid pixels, if there are any + invalid ones. +-----------------------------------------------------------------------------*/ + if (invalidPixelCt > 0) { + unsigned long int const totalPixelCt = validPixelCt + invalidPixelCt; + + printf("\n"); + printf("** Image stream contains invalid sample values " + "(above maxval %u)\n", maxval); + printf("** Valid sample values : %lu (%5.4g%%)\n", + validPixelCt, (float) validPixelCt / totalPixelCt * 100.0); + printf("** Invalid sample values : %lu (%5.4g%%)\n", + invalidPixelCt, (float) invalidPixelCt / totalPixelCt * 100.0); + } +} + + + +static void +printInvalidSamples(colorhist_vector const chv, + colorhist_vector const chvInvalid, + int const totalColorCt, + unsigned int const validColorCt, + pixval const maxval, + enum ColorFmt const colorFmt) { + + unsigned int const invalidColorCt = totalColorCt - validColorCt; + + unsigned int i; + unsigned long int validPixelCt; + unsigned long int invalidPixelCt; + + for (i = 0, validPixelCt = 0; i < validColorCt; ++i) + validPixelCt += chv[i].value; + + for (i = 0, invalidPixelCt = 0; i < invalidColorCt; ++i) { + pixval const r = PPM_GETR(chvInvalid[i].color); + pixval const g = PPM_GETG(chvInvalid[i].color); + pixval const b = PPM_GETB(chvInvalid[i].color); + unsigned int const count = chvInvalid[i].value; + + invalidPixelCt += chvInvalid[i].value; + + switch(colorFmt) { + case FMT_FLOAT: + printf(" %1.3f %1.3f %1.3f\t\t%7d\n", + (double)r / maxval, + (double)g / maxval, + (double)b / maxval, + count); + break; + case FMT_HEX: + printf(" %04x %04x %04x\t\t%7d\n", + r, g, b, count); + break; + case FMT_DECIMAL: + printf(" %5d %5d %5d\t\t%7d\n", + r, g, b, count); + break; + case FMT_PPMPLAIN: + assert(false); + break; + } + } + + summarizeInvalidPixels(validPixelCt, invalidPixelCt, maxval); +} + + + int main(int argc, const char *argv[]) { struct cmdline_info cmdline; FILE * ifP; colorhist_vector chv; + colorhist_vector chvInvalid; int rows, cols; pixval maxval; + pixval mmaxval; int format; - int nColors; - int (*compare_function)(const void *, const void *); - /* The compare function to be used with qsort() to sort the - histogram for output - */ - unsigned int nDictColor; + int colorCt; + unsigned int dictColorCt; const char ** dictColornames; pixel * dictColors; + unsigned int validColorCt; pm_proginit(&argc, argv); @@ -283,23 +492,29 @@ main(int argc, const char *argv[]) { ppm_readppminit(ifP, &cols, &rows, &maxval, &format); - chv = ppm_computecolorhist2(ifP, cols, rows, maxval, format, 0, &nColors); + mmaxval = cmdline.forensic ? universalMaxval(maxval, format) : maxval; + + chv = ppm_computecolorhist2(ifP, cols, rows, mmaxval, format, 0, &colorCt); pm_close(ifP); - switch (cmdline.sort) { - case SORT_BY_FREQUENCY: - compare_function = countcompare; break; - case SORT_BY_RGB: - compare_function = rgbcompare; break; - } + /* Sort and produce histogram. */ + if (cmdline.forensic) { + MALLOCARRAY(chvInvalid, colorCt); + if (chvInvalid == NULL) + pm_error("out of memory generating histogram"); - qsort((char*) chv, nColors, sizeof(struct colorhist_item), - compare_function); + sortHistogramForensic(cmdline.sort, chv, chvInvalid, + maxval, colorCt, &validColorCt); + } else { + chvInvalid = NULL; + sortHistogramNormal(cmdline.sort, chv, colorCt); + validColorCt = colorCt; + } /* And print the histogram. */ - if (cmdline.colorFmt == FMT_PPMPLAIN) - printf("P3\n# color map\n%d 1\n%d\n", nColors, maxval); + if (cmdline.colorFmt == FMT_PPMPLAIN) + printf("P3\n# color map\n%d 1\n%d\n", colorCt, maxval); if (!cmdline.noheader) { const char commentDelim = cmdline.colorFmt == FMT_PPMPLAIN ? '#' : ' '; @@ -309,16 +524,20 @@ main(int argc, const char *argv[]) { commentDelim, cmdline.colorname ? "----" : ""); } if (cmdline.colorname) { - bool mustOpenTrue = TRUE; - ppm_readcolordict(NULL, mustOpenTrue, - &nDictColor, &dictColornames, &dictColors, NULL); + bool const mustOpenTrue = TRUE; + ppm_readcolordict(NULL, mustOpenTrue, + &dictColorCt, &dictColornames, &dictColors, NULL); } else { dictColors = NULL; dictColornames = NULL; } - - printColors(chv, nColors, maxval, - cmdline.colorFmt, nDictColor, dictColors, dictColornames); + + printColors(chv, validColorCt, maxval, + cmdline.colorFmt, dictColorCt, dictColors, dictColornames); + + if (colorCt > validColorCt) + printInvalidSamples(chv, chvInvalid, colorCt, validColorCt, + maxval, cmdline.colorFmt); if (dictColors) free(dictColors); @@ -327,5 +546,11 @@ main(int argc, const char *argv[]) { ppm_freecolorhist(chv); + if (chvInvalid) + ppm_freecolorhist(chvInvalid); + return 0; } + + + -- cgit 1.4.1