diff options
Diffstat (limited to 'analyzer/pnmpsnr.c')
-rw-r--r-- | analyzer/pnmpsnr.c | 453 |
1 files changed, 340 insertions, 113 deletions
diff --git a/analyzer/pnmpsnr.c b/analyzer/pnmpsnr.c index 1160fff6..af74e8c8 100644 --- a/analyzer/pnmpsnr.c +++ b/analyzer/pnmpsnr.c @@ -8,30 +8,91 @@ * Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de> */ +#include <assert.h> #include <string.h> #include <stdio.h> #include <math.h> #include "pm_c_util.h" +#include "mallocvar.h" #include "nstring.h" #include "pam.h" +#include "shhopt.h" + + + +struct CmdlineInfo { + /* All the information the user supplied in the command line, + in a form easy for the program to use. + */ + const char * inputFile1Name; /* Name of first input file */ + const char * inputFile2Name; /* Name of second input file */ + unsigned int rgb; +}; + + + +static void +parseCommandLine(int argc, const char ** argv, + struct CmdlineInfo * const cmdlineP) { +/*---------------------------------------------------------------------------- + Note that the file spec array we return is stored in the storage that + was passed to as as the argv array. +-----------------------------------------------------------------------------*/ + optEntry * option_def; + /* Instructions to pm_optParseOptions3 on how to parse our options. + */ + optStruct3 opt; + + unsigned int option_def_index; + + MALLOCARRAY_NOFAIL(option_def, 100); + + option_def_index = 0; /* incremented by OPTENT3 */ + OPTENT3(0, "rgb", OPT_FLAG, NULL, &cmdlineP->rgb, 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 */ + + pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0); + /* Uses and sets argc, argv, and some of *cmdlineP and others */ + + if (argc-1 < 2) + pm_error("Takes two arguments: names of the two files to compare"); + else { + cmdlineP->inputFile1Name = argv[1]; + cmdlineP->inputFile2Name = argv[2]; + + if (argc-1 > 2) + pm_error("Too many arguments (%u). The only arguments are " + "the names of the two files to compare", argc-1); + } + + free(option_def); +} + -#define MAXFILES 16 static int -udiff(unsigned int const subtrahend, unsigned int const subtractor) { - return subtrahend-subtractor; +udiff(unsigned int const subtrahend, + unsigned int const subtractor) { + + return subtrahend - subtractor; } + static double square(double const arg) { - return(arg*arg); + return(arg * arg); } + static void -validate_input(const struct pam pam1, const struct pam pam2) { +validateInput(struct pam const pam1, + struct pam const pam2) { if (pam1.width != pam2.width) pm_error("images are not the same width, so can't be compared. " @@ -52,159 +113,325 @@ validate_input(const struct pam pam1, const struct pam pam2) { "maxval of one of them.", (unsigned int) pam1.maxval, (unsigned int) pam2.maxval); - if (strcmp(pam1.tuple_type, pam2.tuple_type) != 0) + if (!streq(pam1.tuple_type, pam2.tuple_type)) pm_error("images are not of the same type. The tuple types are " "'%s' and '%s', respectively.", pam1.tuple_type, pam2.tuple_type); - if (strcmp(pam1.tuple_type, PAM_PBM_TUPLETYPE) != 0 && - strcmp(pam1.tuple_type, PAM_PGM_TUPLETYPE) != 0 && - strcmp(pam1.tuple_type, PAM_PPM_TUPLETYPE) != 0) + if (!streq(pam1.tuple_type, PAM_PBM_TUPLETYPE) && + !streq(pam1.tuple_type, PAM_PGM_TUPLETYPE) && + !streq(pam1.tuple_type, PAM_PPM_TUPLETYPE)) pm_error("Images are not of a PNM type. Tuple type is '%s'", pam1.tuple_type); } +enum ColorSpaceId { + COLORSPACE_GRAYSCALE, + COLORSPACE_YCBCR, + COLORSPACE_RGB +}; -static void -psnr_color(tuple const tuple1, - tuple const tuple2, - double * const ySqDiffP, - double * const cbSqDiffP, - double * const crSqDiffP) { +typedef struct { + + enum ColorSpaceId id; + + unsigned int componentCt; + + const char * componentName[3]; + /* Only first 'componentCt' elements are valid */ + +} ColorSpace; + + +struct SqDiff { +/*---------------------------------------------------------------------------- + The square-differences of the components of two pixels, for some + component set. +-----------------------------------------------------------------------------*/ + double sqDiff[3]; +}; + + + +static struct SqDiff +zeroSqDiff() { + + struct SqDiff retval; + unsigned int i; + + for (i = 0; i < 3; ++i) + retval.sqDiff[i] = 0.0; + + return retval; +} + + + +static struct SqDiff +sqDiffSum(ColorSpace const colorSpace, + struct SqDiff const addend, + struct SqDiff const adder) { + + struct SqDiff retval; + unsigned int i; + + for (i = 0; i < colorSpace.componentCt; ++i) + retval.sqDiff[i] = addend.sqDiff[i] + adder.sqDiff[i]; + + return retval; +} + + + +#define Y_INDEX 0 +#define CB_INDEX 1 +#define CR_INDEX 2 + +static ColorSpace +yCbCrColorSpace() { + + ColorSpace retval; + + retval.id = COLORSPACE_YCBCR; + + retval.componentCt = 3; + + retval.componentName[Y_INDEX] = "Y"; + retval.componentName[CR_INDEX] = "CR"; + retval.componentName[CB_INDEX] = "CB"; + + return retval; +} + + + +static struct SqDiff +sqDiffYCbCr(tuple const tuple1, + tuple const tuple2) { + + struct SqDiff retval; double y1, y2, cb1, cb2, cr1, cr2; pnm_YCbCrtuple(tuple1, &y1, &cb1, &cr1); pnm_YCbCrtuple(tuple2, &y2, &cb2, &cr2); - *ySqDiffP = square(y1 - y2); - *cbSqDiffP = square(cb1 - cb2); - *crSqDiffP = square(cr1 - cr2); + retval.sqDiff[Y_INDEX] = square(y1 - y2); + retval.sqDiff[CB_INDEX] = square(cb1 - cb2); + retval.sqDiff[CR_INDEX] = square(cr1 - cr2); + + return retval; +} + + + +#define R_INDEX 0 +#define G_INDEX 1 +#define B_INDEX 2 + + + +static ColorSpace +rgbColorSpace() { + + ColorSpace retval; + + retval.id = COLORSPACE_RGB; + + retval.componentCt = 3; + + retval.componentName[R_INDEX] = "Red"; + retval.componentName[G_INDEX] = "Green"; + retval.componentName[B_INDEX] = "Blue"; + + return retval; +} + + + +static struct SqDiff +sqDiffRgb(tuple const tuple1, + tuple const tuple2) { + + struct SqDiff retval; + + retval.sqDiff[R_INDEX] = + square((int)tuple1[PAM_RED_PLANE] - (int)tuple2[PAM_RED_PLANE]); + retval.sqDiff[G_INDEX] = + square((int)tuple1[PAM_GRN_PLANE] - (int)tuple2[PAM_GRN_PLANE]); + retval.sqDiff[B_INDEX] = + square((int)tuple1[PAM_BLU_PLANE] - (int)tuple2[PAM_BLU_PLANE]); + + return retval; +} + + + +static ColorSpace +grayscaleColorSpace() { + + ColorSpace retval; + + retval.id = COLORSPACE_GRAYSCALE; + + retval.componentCt = 1; + + retval.componentName[Y_INDEX] = "luminance"; + + return retval; +} + + + +static struct SqDiff +sqDiffGrayscale(tuple const tuple1, + tuple const tuple2) { + + struct SqDiff sqDiff; + + sqDiff.sqDiff[Y_INDEX] = square(udiff(tuple1[0], tuple2[0])); + + return sqDiff; +} + + + +static struct SqDiff +sumSqDiffFromRaster(struct pam * const pam1P, + struct pam * const pam2P, + ColorSpace const colorSpace) { + + struct SqDiff sumSqDiff; + tuple *tuplerow1, *tuplerow2; /* malloc'ed */ + unsigned int row; + + tuplerow1 = pnm_allocpamrow(pam1P); + tuplerow2 = pnm_allocpamrow(pam2P); + + sumSqDiff = zeroSqDiff(); + + for (row = 0; row < pam1P->height; ++row) { + unsigned int col; + + pnm_readpamrow(pam1P, tuplerow1); + pnm_readpamrow(pam2P, tuplerow2); + + assert(pam1P->width == pam2P->width); + + for (col = 0; col < pam1P->width; ++col) { + struct SqDiff sqDiff; + + switch (colorSpace.id) { + case COLORSPACE_GRAYSCALE: + sqDiff = sqDiffGrayscale(tuplerow1[col], tuplerow2[col]); + break; + case COLORSPACE_YCBCR: + sqDiff = sqDiffYCbCr(tuplerow1[col], tuplerow2[col]); + break; + case COLORSPACE_RGB: + sqDiff = sqDiffRgb(tuplerow1[col], tuplerow2[col]); + break; + } + sumSqDiff = sqDiffSum(colorSpace, sumSqDiff, sqDiff); + } + } + + pnm_freepamrow(tuplerow1); + pnm_freepamrow(tuplerow2); + + return sumSqDiff; } static void -reportPsnr(struct pam const pam, - double const ySumSqDiff, - double const crSumSqDiff, - double const cbSumSqDiff, - char const filespec1[], - char const filespec2[]) { +reportPsnr(struct pam const pam, + struct SqDiff const sumSqDiff, + ColorSpace const colorSpace, + const char * const fileName1, + const char * const fileName2) { + + double const maxSumSqDiff = square(pam.maxval) * pam.width * pam.height; + /* Maximum possible sum square difference, i.e. the sum of the squares + of the sample differences between an entirely white image and + entirely black image of the given dimensions. + */ + + unsigned int i; - bool const color = streq(pam.tuple_type, PAM_PPM_TUPLETYPE); /* The PSNR is the ratio of the maximum possible mean square difference - to the actual mean square difference. + to the actual mean square difference, which is also the ratio of + the maximum possible sum square difference to the actual sum square + difference. + + Note that in the important special case that the images are + identical, the sum square differences are identically 0.0. + No precision error; no rounding error. */ - double const yPsnr = - square(pam.maxval) / (ySumSqDiff / (pam.width * pam.height)); - /* Note that in the important special case that the images are - identical, the sum square differences are identically 0.0. No - precision error; no rounding error. - */ + pm_message("PSNR between '%s' and '%s':", fileName1, fileName2); - if (color) { - double const cbPsnr = - square(pam.maxval) / (cbSumSqDiff / (pam.width * pam.height)); - double const crPsnr = - square(pam.maxval) / (crSumSqDiff / (pam.width * pam.height)); + for (i = 0; i < colorSpace.componentCt; ++i) { + const char * label; - pm_message("PSNR between %s and %s:", filespec1, filespec2); - if (ySumSqDiff > 0) - pm_message("Y color component: %.2f dB", 10 * log10(yPsnr)); - else - pm_message("Y color component does not differ."); - if (cbSumSqDiff > 0) - pm_message("Cb color component: %.2f dB", 10 * log10(cbPsnr)); - else - pm_message("Cb color component does not differ."); - if (crSumSqDiff > 0) - pm_message("Cr color component: %.2f dB", 10 * log10(crPsnr)); - else - pm_message("Cr color component does not differ."); - } else { - if (ySumSqDiff > 0) - pm_message("PSNR between %s and %s: %.2f dB", - filespec1, filespec2, 10 * log10(yPsnr)); + pm_asprintf(&label, "%s:", colorSpace.componentName[i]); + + if (sumSqDiff.sqDiff[i] > 0) + pm_message(" %-6.6s %.2f dB", + label, + 10 * log10(maxSumSqDiff/sumSqDiff.sqDiff[i])); else - pm_message("Images %s and %s don't differ.", - filespec1, filespec2); + pm_message(" %-6.6s no difference", label); + + pm_strfree(label); } } int -main (int argc, char **argv) { - char *filespec1, *filespec2; /* specs of two files to compare */ - FILE *file1, *file2; +main (int argc, const char **argv) { + FILE * if1P; + FILE * if2P; struct pam pam1, pam2; - bool color; - /* It's a color image */ - double ySumSqDiff, crSumSqDiff, cbSumSqDiff; - tuple *tuplerow1, *tuplerow2; /* malloc'ed */ - int row; + ColorSpace colorSpace; - pnm_init(&argc, argv); + struct CmdlineInfo cmdline; - if (argc-1 < 2) - pm_error("Takes two arguments: specifications of the two files."); - else { - filespec1 = argv[1]; - filespec2 = argv[2]; - } - - file1 = pm_openr(filespec1); - file2 = pm_openr(filespec2); + pm_proginit(&argc, argv); - pnm_readpaminit(file1, &pam1, PAM_STRUCT_SIZE(tuple_type)); - pnm_readpaminit(file2, &pam2, PAM_STRUCT_SIZE(tuple_type)); + parseCommandLine(argc, argv, &cmdline); - validate_input(pam1, pam2); + if1P = pm_openr(cmdline.inputFile1Name); + if2P = pm_openr(cmdline.inputFile2Name); - if (strcmp(pam1.tuple_type, PAM_PPM_TUPLETYPE) == 0) - color = TRUE; - else - color = FALSE; + pnm_readpaminit(if1P, &pam1, PAM_STRUCT_SIZE(tuple_type)); + pnm_readpaminit(if2P, &pam2, PAM_STRUCT_SIZE(tuple_type)); - tuplerow1 = pnm_allocpamrow(&pam1); - tuplerow2 = pnm_allocpamrow(&pam2); - - ySumSqDiff = 0.0; - cbSumSqDiff = 0.0; - crSumSqDiff = 0.0; + validateInput(pam1, pam2); - for (row = 0; row < pam1.height; ++row) { - int col; - - pnm_readpamrow(&pam1, tuplerow1); - pnm_readpamrow(&pam2, tuplerow2); - - for (col = 0; col < pam1.width; ++col) { - if (color) { - double ySqDiff, cbSqDiff, crSqDiff; - psnr_color(tuplerow1[col], tuplerow2[col], - &ySqDiff, &cbSqDiff, &crSqDiff); - ySumSqDiff += ySqDiff; - cbSumSqDiff += cbSqDiff; - crSumSqDiff += crSqDiff; - - } else { - unsigned int const yDiffSq = - square(udiff(tuplerow1[col][0], tuplerow2[col][0])); - ySumSqDiff += yDiffSq; - } - } - } + if (streq(pam1.tuple_type, PAM_PPM_TUPLETYPE)) { + if (cmdline.rgb) + colorSpace = rgbColorSpace(); + else + colorSpace = yCbCrColorSpace(); + } else + colorSpace = grayscaleColorSpace(); - reportPsnr(pam1, ySumSqDiff, crSumSqDiff, cbSumSqDiff, - filespec1, filespec2); + { + struct SqDiff const sumSqDiff = + sumSqDiffFromRaster(&pam1, &pam2, colorSpace); - pnm_freepamrow(tuplerow1); - pnm_freepamrow(tuplerow2); + reportPsnr(pam1, sumSqDiff, colorSpace, + cmdline.inputFile1Name, cmdline.inputFile2Name); + } + pm_close(if2P); + pm_close(if1P); return 0; } + + + |