about summary refs log tree commit diff
path: root/analyzer/pnmpsnr.c
diff options
context:
space:
mode:
Diffstat (limited to 'analyzer/pnmpsnr.c')
-rw-r--r--analyzer/pnmpsnr.c453
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;
 }
+
+
+