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.c260
1 files changed, 230 insertions, 30 deletions
diff --git a/analyzer/pnmpsnr.c b/analyzer/pnmpsnr.c
index af74e8c8..2363e8c3 100644
--- a/analyzer/pnmpsnr.c
+++ b/analyzer/pnmpsnr.c
@@ -2,7 +2,7 @@
  *  pnmpsnr.c: Compute error (RMSE, PSNR) between images
  *
  *
- *  Derived from pnmpnsmr by Ulrich Hafner, part of his fiasco package,
+ *  Derived from pnmpnsmr by Ullrich Hafner, part of his fiasco package,
  *  On 2001.03.04.
 
  *  Copyright (C) 1994-2000 Ullrich Hafner <hafner@bigfoot.de>
@@ -21,6 +21,33 @@
 
 
 
+struct TargetSet {
+    unsigned int targetSpec;
+    float        target;
+    unsigned int target1Spec;
+    float        target1;
+    unsigned int target2Spec;
+    float        target2;
+    unsigned int target3Spec;
+    float        target3;
+};
+
+
+
+static bool
+targetSet_compTargetSpec(struct TargetSet const targetSet) {
+/*----------------------------------------------------------------------------
+   The target set specifies individual color component targets
+   (some may be "don't care", though).
+-----------------------------------------------------------------------------*/
+    return
+        targetSet.target1Spec ||
+        targetSet.target2Spec ||
+        targetSet.target3Spec;
+}
+
+
+
 struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
@@ -28,11 +55,39 @@ struct CmdlineInfo {
     const char * inputFile1Name;  /* Name of first input file */
     const char * inputFile2Name;  /* Name of second input file */
     unsigned int rgb;
+    unsigned int machine;
+    unsigned int maxSpec;
+    float        max;
+    bool         targetMode;
+    struct TargetSet target;
 };
 
 
 
 static void
+interpretTargetSet(struct TargetSet const targetSet,
+                   bool *           const targetModeP) {
+
+    if (targetSet.targetSpec && targetSet.target <= 0.0)
+        pm_error("Nonpositive -target does not make sense");
+
+    if (targetSet.target1Spec && targetSet.target1 <= 0.0)
+        pm_error("Nonpositive -target1 does not make sense");
+
+    if (targetSet.target2Spec && targetSet.target2 <= 0.0)
+        pm_error("Nonpositive -target2 does not make sense");
+
+    if (targetSet.target3Spec && targetSet.target3 <= 0.0)
+        pm_error("Nonpositive -target3 does not make sense");
+
+    *targetModeP =
+        targetSet.targetSpec || targetSet.target1Spec ||
+        targetSet.target2Spec || targetSet.target3Spec;
+}
+
+
+
+static void
 parseCommandLine(int argc, const char ** argv,
                  struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
@@ -47,18 +102,31 @@ parseCommandLine(int argc, const char ** argv,
     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);
+    OPTENT3(0,   "rgb",      OPT_FLAG,  NULL,
+            &cmdlineP->rgb,       0);
+    OPTENT3(0,   "machine",  OPT_FLAG,  NULL,
+            &cmdlineP->machine,   0);
+    OPTENT3(0,   "max",      OPT_FLOAT, &cmdlineP->max,
+            &cmdlineP->maxSpec,   0);
+    OPTENT3(0,   "target",   OPT_FLOAT, &cmdlineP->target.target,
+            &cmdlineP->target.targetSpec,   0);
+    OPTENT3(0,   "target1",  OPT_FLOAT, &cmdlineP->target.target1,
+            &cmdlineP->target.target1Spec,   0);
+    OPTENT3(0,   "target2",  OPT_FLOAT, &cmdlineP->target.target2,
+            &cmdlineP->target.target2Spec,   0);
+    OPTENT3(0,   "target3",  OPT_FLOAT, &cmdlineP->target.target3,
+            &cmdlineP->target.target3Spec,   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) 
+    if (argc-1 < 2)
         pm_error("Takes two arguments:  names of the two files to compare");
     else {
         cmdlineP->inputFile1Name = argv[1];
@@ -70,6 +138,11 @@ parseCommandLine(int argc, const char ** argv,
     }
 
     free(option_def);
+
+    interpretTargetSet(cmdlineP->target, &cmdlineP->targetMode);
+
+    if (cmdlineP->targetMode && cmdlineP->maxSpec)
+        pm_error("-max is meaningless with -targetX");
 }
 
 
@@ -213,10 +286,10 @@ sqDiffYCbCr(tuple    const tuple1,
     struct SqDiff retval;
 
     double y1, y2, cb1, cb2, cr1, cr2;
-    
+
     pnm_YCbCrtuple(tuple1, &y1, &cb1, &cr1);
     pnm_YCbCrtuple(tuple2, &y2, &cb2, &cr2);
-    
+
     retval.sqDiff[Y_INDEX]  = square(y1  - y2);
     retval.sqDiff[CB_INDEX] = square(cb1 - cb2);
     retval.sqDiff[CR_INDEX] = square(cr1 - cr2);
@@ -308,12 +381,12 @@ sumSqDiffFromRaster(struct pam * const pam1P,
 
     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);
 
@@ -345,32 +418,121 @@ sumSqDiffFromRaster(struct pam * const pam1P,
 
 
 
-static void
-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.
-        */
+struct Psnr {
+/*----------------------------------------------------------------------------
+   The PSNR of an image, in some unspecified color space.
+-----------------------------------------------------------------------------*/
+    double psnr[3];
+};
 
-    unsigned int i;
 
 
+static struct Psnr
+psnrFromSumSqDiff(struct SqDiff const sumSqDiff,
+                  double        const maxSumSqDiff,
+                  unsigned int  const componentCt) {
+/*----------------------------------------------------------------------------
+   Compute the PSNR from the sums of the squares of the differences in the
+   pixels 'sumSqDiff' (separated by colorpspace component, where there are
+   'componentCt' components).
+
+   'maxSumSqDiff' is the 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.
+
+   Where there is no difference between the images, return infinity.
+-----------------------------------------------------------------------------*/
+
+    struct Psnr retval;
+    unsigned int i;
+
     /* The PSNR is the ratio of the maximum possible 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.
     */
 
+    for (i = 0; i < componentCt; ++i) {
+        if (sumSqDiff.sqDiff[i] > 0)
+            retval.psnr[i] = 10 * log10(maxSumSqDiff/sumSqDiff.sqDiff[i]);
+        else
+            retval.psnr[i] = 1.0/0.0;
+    }
+    return retval;
+}
+
+
+
+static bool
+psnrIsFinite(double const psnr) {
+
+    /* We would just use C standard isfinite(), but that is not standard
+       before C99.  Neither is INFINITY.
+
+       A finite PSNR, in this program, cannot be anywhere near 1,000,000,
+       because of limits of the program, so we just compare to that.
+    */
+
+    return psnr < 1000000.0;
+}
+
+
+
+static void
+reportTarget(struct Psnr      const psnr,
+             ColorSpace       const colorSpace,
+             struct TargetSet const target) {
+
+    bool hitsTarget;
+
+    if (colorSpace.componentCt == 1) {
+        if (!target.targetSpec)
+            pm_error("Image is monochrome and you specified "
+                     "-target1, -target2, or -target3 but not -target");
+
+        hitsTarget = psnr.psnr[0] >= target.target;
+    } else {
+        float compTarget[3];
+
+        unsigned int i;
+
+        assert(colorSpace.componentCt == 3);
+
+        if (targetSet_compTargetSpec(target)) {
+            compTarget[0] = target.target1Spec ? target.target1 : -1;
+            compTarget[1] = target.target2Spec ? target.target2 : -1;
+            compTarget[2] = target.target3Spec ? target.target3 : -1;
+        } else {
+            assert(target.targetSpec);
+            compTarget[0] = target.target;
+            compTarget[1] = target.target;
+            compTarget[2] = target.target;
+        }
+        for (i = 0, hitsTarget = true;
+             i < colorSpace.componentCt && hitsTarget;
+             ++i) {
+
+            if (psnr.psnr[i] < compTarget[i])
+                hitsTarget = false;
+        }
+    }
+    fprintf(stdout, "%s\n", hitsTarget ? "match" : "nomatch");
+}
+
+
+
+static void
+reportPsnrHuman(struct Psnr   const psnr,
+                ColorSpace    const colorSpace,
+                const char *  const fileName1,
+                const char *  const fileName2) {
+
+    unsigned int i;
+
     pm_message("PSNR between '%s' and '%s':", fileName1, fileName2);
 
     for (i = 0; i < colorSpace.componentCt; ++i) {
@@ -378,10 +540,8 @@ reportPsnr(struct pam    const pam,
 
         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]));
+        if (psnrIsFinite(psnr.psnr[i]))
+            pm_message("  %-6.6s %.2f dB", label, psnr.psnr[i]);
         else
             pm_message("  %-6.6s no difference", label);
 
@@ -391,13 +551,34 @@ reportPsnr(struct pam    const pam,
 
 
 
+static void
+reportPsnrMachine(struct Psnr  const psnr,
+                  unsigned int const componentCt,
+                  bool         const maxSpec,
+                  float        const max) {
+
+    unsigned int i;
+
+    for (i = 0; i < componentCt; ++i) {
+        double const clipped = maxSpec ? MIN(max, psnr.psnr[i]) : psnr.psnr[i];
+
+        if (i > 0)
+            fprintf(stdout, " ");
+
+        fprintf(stdout, "%.2f", clipped);
+    }
+    fprintf(stdout, "\n");
+}
+
+
+
 int
 main (int argc, const char **argv) {
     FILE * if1P;
     FILE * if2P;
     struct pam pam1, pam2;
     ColorSpace colorSpace;
-    
+
     struct CmdlineInfo cmdline;
 
     pm_proginit(&argc, argv);
@@ -424,8 +605,27 @@ main (int argc, const char **argv) {
         struct SqDiff const sumSqDiff =
             sumSqDiffFromRaster(&pam1, &pam2, colorSpace);
 
-        reportPsnr(pam1, sumSqDiff, colorSpace,
-                   cmdline.inputFile1Name, cmdline.inputFile2Name);
+        double const maxSumSqDiff =
+            square(pam1.maxval) * pam1.width * pam1.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.
+            */
+
+        struct Psnr const psnr =
+            psnrFromSumSqDiff(
+                sumSqDiff, maxSumSqDiff, colorSpace.componentCt);
+
+        if (cmdline.targetMode)
+            reportTarget(psnr, colorSpace, cmdline.target);
+        else if (cmdline.machine)
+            reportPsnrMachine(psnr, colorSpace.componentCt,
+                              cmdline.maxSpec, cmdline.max);
+        else
+            reportPsnrHuman(psnr, colorSpace,
+                            cmdline.inputFile1Name, cmdline.inputFile2Name);
+
+
     }
     pm_close(if2P);
     pm_close(if1P);