about summary refs log tree commit diff
path: root/analyzer
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2023-06-28 17:21:21 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2023-06-28 17:21:21 +0000
commit0d513aca5cbbb8db0a9d127e101ac3b534cc8bf0 (patch)
tree3e8db9f13fb33464324c6986e7d80540a42a86c7 /analyzer
parent7dd37058c4c8e0f6ca272e329162a52f958e4951 (diff)
downloadnetpbm-mirror-0d513aca5cbbb8db0a9d127e101ac3b534cc8bf0.tar.gz
netpbm-mirror-0d513aca5cbbb8db0a9d127e101ac3b534cc8bf0.tar.xz
netpbm-mirror-0d513aca5cbbb8db0a9d127e101ac3b534cc8bf0.zip
promote Stable to Super Stable
git-svn-id: http://svn.code.sf.net/p/netpbm/code/super_stable@4557 9d0c8265-081b-0410-96cb-a4ca84ce46f8
Diffstat (limited to 'analyzer')
-rw-r--r--analyzer/Makefile32
-rw-r--r--analyzer/pamfile.c248
-rw-r--r--analyzer/pamfind.c222
-rw-r--r--analyzer/pamgetcolor.c573
-rw-r--r--analyzer/pamsumm.c121
-rw-r--r--analyzer/pamtable.c200
-rw-r--r--analyzer/pamtilt.c2
-rw-r--r--analyzer/pgmtexture.c66
-rw-r--r--analyzer/pnmpsnr.c260
-rw-r--r--analyzer/ppmhist.c228
10 files changed, 1687 insertions, 265 deletions
diff --git a/analyzer/Makefile b/analyzer/Makefile
index c96a6d0a..11466662 100644
--- a/analyzer/Makefile
+++ b/analyzer/Makefile
@@ -14,9 +14,26 @@ include $(BUILDDIR)/config.mk
 # This package is so big, it's useful even when some parts won't 
 # build.
 
-PORTBINARIES = pamfile pammosaicknit pamslice pamsumm pamtilt \
-               pbmminkowski pgmhist pnmhistmap ppmhist pgmminkowski
-MATHBINARIES = pamsharpmap pamsharpness pgmtexture pnmpsnr 
+PORTBINARIES = \
+  pamfind \
+  pamgetcolor \
+  pamfile \
+  pammosaicknit \
+  pamslice \
+  pamsumm \
+  pamtable \
+  pamtilt \
+  pbmminkowski \
+  pgmhist \
+  pnmhistmap \
+  ppmhist \
+  pgmminkowski \
+
+MATHBINARIES = \
+  pamsharpmap \
+  pamsharpness \
+  pgmtexture \
+  pnmpsnr \
 
 BINARIES = $(PORTBINARIES) $(MATHBINARIES)
 SCRIPT =
@@ -30,12 +47,14 @@ OBJECTS = $(BINARIES:%=%.o)
 MERGEBINARIES = $(BINARIES)
 MERGE_OBJECTS = $(MERGEBINARIES:%=%.o2)
 
+HAVE_MERGE_COMPAT=YES
+
 .PHONY: all
 all: $(BINARIES)
 
 include $(SRCDIR)/common.mk
 
-install.bin: install.bin.local
+install.bin install.merge: install.bin.local
 .PHONY: install.bin.local
 install.bin.local: $(PKGDIR)/bin
 # Remember that $(SYMLINK) might just be a copy command.
@@ -44,5 +63,10 @@ install.bin.local: $(PKGDIR)/bin
 	cd $(PKGDIR)/bin ; \
 	$(SYMLINK) pamfile$(EXE) pnmfile$(EXE)
 
+mergecomptrylist:
+	cat /dev/null >$@
+	echo "TRY(\"pgmslice\", main_pamslice);" >>$@
+	echo "TRY(\"pnmfile\",  main_pamfile);"  >>$@
+
 FORCE:
 
diff --git a/analyzer/pamfile.c b/analyzer/pamfile.c
index a7c3c5ac..7b61f6fd 100644
--- a/analyzer/pamfile.c
+++ b/analyzer/pamfile.c
@@ -10,21 +10,24 @@
 ** implied warranty.
 */
 
+#include <stdbool.h>
 #include "pm_c_util.h"
 #include "mallocvar.h"
 #include "nstring.h"
 #include "shhopt.h"
 #include "pam.h"
 
+enum ReportFormat {RF_HUMAN, RF_COUNT, RF_MACHINE, RF_SIZE};
+
 struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
     int inputFileCount;  /* Number of input files */
     const char ** inputFilespec;  /* Filespecs of input files */
-    unsigned int allimages;  /* -allimages or -count */
-    unsigned int count;      /* -count */
-    unsigned int comments;   /* -comments */
+    unsigned int allimages;
+    unsigned int comments;
+    enum ReportFormat reportFormat;
 };
 
 
@@ -42,35 +45,49 @@ parseCommandLine(int argc, const char ** argv,
     optStruct3 opt;
 
     unsigned int option_def_index;
+    unsigned int countSpec, machineSpec, sizeSpec;
 
     MALLOCARRAY_NOFAIL(option_def, 100);
-    
+
     option_def_index = 0;   /* incremented by OPTENT3 */
     OPTENT3(0,   "allimages", OPT_FLAG,  NULL, &cmdlineP->allimages,   0);
-    OPTENT3(0,   "count",     OPT_FLAG,  NULL, &cmdlineP->count,       0);
+    OPTENT3(0,   "count",     OPT_FLAG,  NULL, &countSpec,             0);
     OPTENT3(0,   "comments",  OPT_FLAG,  NULL, &cmdlineP->comments,    0);
+    OPTENT3(0,   "machine",   OPT_FLAG,  NULL, &machineSpec,           0);
+    OPTENT3(0,   "size",      OPT_FLAG,  NULL, &sizeSpec,              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 */
-    
+    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 */
 
     cmdlineP->inputFilespec = (const char **)&argv[1];
     cmdlineP->inputFileCount = argc - 1;
 
+    if (machineSpec + sizeSpec + countSpec > 1)
+        pm_error("You can specify only one of -machine, -size, and -count");
+    else if (machineSpec)
+        cmdlineP->reportFormat = RF_MACHINE;
+    else if (sizeSpec)
+        cmdlineP->reportFormat = RF_SIZE;
+    else if (countSpec)
+        cmdlineP->reportFormat = RF_COUNT;
+    else
+        cmdlineP->reportFormat = RF_HUMAN;
+
     free(option_def);
 }
 
 
 
 static void
-dumpHeader(struct pam const pam) {
+dumpHeaderHuman(struct pam const pam) {
 
     switch (pam.format) {
     case PAM_FORMAT:
-        printf("PAM, %d by %d by %d maxval %ld\n", 
+        printf("PAM, %d by %d by %d maxval %ld\n",
                pam.width, pam.height, pam.depth, pam.maxval);
         printf("    Tuple type: %s\n", pam.tuple_type);
         break;
@@ -84,22 +101,22 @@ dumpHeader(struct pam const pam) {
         break;
 
 	case PGM_FORMAT:
-        printf("PGM plain, %d by %d  maxval %ld\n", 
+        printf("PGM plain, %d by %d  maxval %ld\n",
                pam.width, pam.height, pam.maxval);
         break;
 
 	case RPGM_FORMAT:
-        printf("PGM raw, %d by %d  maxval %ld\n", 
+        printf("PGM raw, %d by %d  maxval %ld\n",
                pam.width, pam.height, pam.maxval);
         break;
 
 	case PPM_FORMAT:
-        printf("PPM plain, %d by %d  maxval %ld\n", 
+        printf("PPM plain, %d by %d  maxval %ld\n",
                pam.width, pam.height, pam.maxval);
         break;
 
 	case RPPM_FORMAT:
-        printf("PPM raw, %d by %d  maxval %ld\n", 
+        printf("PPM raw, %d by %d  maxval %ld\n",
                pam.width, pam.height, pam.maxval);
         break;
     }
@@ -108,23 +125,79 @@ dumpHeader(struct pam const pam) {
 
 
 static void
+dumpHeaderMachine(struct pam const pam) {
+
+    const char * formatString;
+    bool plain;
+
+    switch (pam.format) {
+    case PAM_FORMAT:
+        formatString = "PAM";
+        plain = false;
+        break;
+
+	case PBM_FORMAT:
+        formatString = "PBM";
+        plain = TRUE;
+        break;
+
+	case RPBM_FORMAT:
+        formatString = "PBM";
+        plain = false;
+        break;
+
+	case PGM_FORMAT:
+        formatString = "PGM";
+        plain = TRUE;
+        break;
+
+	case RPGM_FORMAT:
+        formatString = "PGM";
+        plain = false;
+        break;
+
+	case PPM_FORMAT:
+        formatString = "PPM";
+        plain = TRUE;
+        break;
+
+	case RPPM_FORMAT:
+        formatString = "PPM";
+        plain = false;        break;
+    }
+
+    printf("%s %s %d %d %d %ld %s\n", formatString,
+           plain ? "PLAIN" : "RAW", pam.width, pam.height,
+           pam.depth, pam.maxval, pam.tuple_type);
+
+}
+
+
+static void
+dumpHeaderSize(struct pam const pam) {
+
+    printf("%d %d\n", pam.width, pam.height);
+}
+
+
+static void
 dumpComments(const char * const comments) {
 
     const char * p;
     bool startOfLine;
-    
+
     printf("Comments:\n");
 
     for (p = &comments[0], startOfLine = TRUE; *p; ++p) {
         if (startOfLine)
             printf("  #");
-        
+
         fputc(*p, stdout);
-        
+
         if (*p == '\n')
             startOfLine = TRUE;
         else
-            startOfLine = FALSE;
+            startOfLine = false;
     }
     if (!startOfLine)
         fputc('\n', stdout);
@@ -133,14 +206,36 @@ dumpComments(const char * const comments) {
 
 
 static void
-doOneImage(const char * const name,
-           unsigned int const imageDoneCount,
-           FILE *       const fileP,
-           bool         const allimages,
-           bool         const justCount,
-           bool         const wantComments,
-           bool *       const eofP) {
-                    
+readToNextImage(const struct pam * const pamP,
+                bool *             const eofP) {
+
+    tuple * tuplerow;
+    unsigned int row;
+
+    tuplerow = pnm_allocpamrow(pamP);
+
+    for (row = 0; row < pamP->height; ++row)
+        pnm_readpamrow(pamP, tuplerow);
+
+    pnm_freepamrow(tuplerow);
+
+    {
+        int eof;
+        pnm_nextimage(pamP->file, &eof);
+        *eofP = eof;
+    }
+}
+
+
+static void
+doOneImage(const char *      const name,
+           unsigned int      const imageDoneCt,
+           FILE *            const fileP,
+           enum ReportFormat const reportFormat,
+           bool              const allimages,
+           bool              const wantComments,
+           bool *            const eofP) {
+
     struct pam pam;
     const char * comments;
     enum pm_check_code checkRetval;
@@ -148,76 +243,71 @@ doOneImage(const char * const name,
     pam.comment_p = &comments;
 
     pnm_readpaminit(fileP, &pam, PAM_STRUCT_SIZE(comment_p));
-        
-    if (!justCount) {
+
+    switch (reportFormat) {
+    case RF_COUNT:
+        break;
+    case RF_SIZE:
+        dumpHeaderSize(pam);
+        break;
+    case RF_MACHINE:
+        printf("%s: ", name);
+        dumpHeaderMachine(pam);
+        break;
+    case RF_HUMAN:
         if (allimages)
-            printf("%s:\tImage %d:\t", name, imageDoneCount);
-        else 
+            printf("%s:\tImage %d:\t", name, imageDoneCt);
+        else
             printf("%s:\t", name);
-            
-        dumpHeader(pam);
+
+        dumpHeaderHuman(pam);
+
         if (wantComments)
             dumpComments(comments);
     }
     pm_strfree(comments);
 
     pnm_checkpam(&pam, PM_CHECK_BASIC, &checkRetval);
-    if (allimages) {
-        tuple * tuplerow;
-        unsigned int row;
-        
-        tuplerow = pnm_allocpamrow(&pam);
-        
-        for (row = 0; row < pam.height; ++row) 
-            pnm_readpamrow(&pam, tuplerow);
-        
-        pnm_freepamrow(tuplerow);
-        
-        {
-            int eof;
-            pnm_nextimage(fileP, &eof);
-            *eofP = eof;
-        }
-    }
+
+    if (allimages)
+        readToNextImage(&pam, eofP);
 }
 
 
 
 static void
-describeOneFile(const char * const name,
-                FILE *       const fileP,
-                bool         const allimages,
-                bool         const justCount,
-                bool         const wantComments) {
+describeOneFile(const char *      const name,
+                FILE *            const fileP,
+                enum ReportFormat const reportFormat,
+                bool              const allimages,
+                bool              const wantComments) {
 /*----------------------------------------------------------------------------
    Describe one image stream (file).  Its name, for purposes of display,
    is 'name'.  The file is open as *fileP and positioned to the beginning.
 
+   'reportFormat' tells which of the various sets of information we provide.
+
    'allimages' means report on every image in the stream and read all of
    every image from it, as opposed to reading just the header of the first
    image and reporting just on that.
 
-   'justCount' means don't tell anything about the stream except how
-   many images are in it.  Pretty useless without 'allimages'.
-
    'wantComments' means to show the comments from the image header.
-   Meaningless with 'justCount'.
+   Meaningful only when 'reportFormat' is RF_HUMAN.
 -----------------------------------------------------------------------------*/
-    unsigned int imageDoneCount;
+    unsigned int imageDoneCt;
         /* Number of images we've processed so far */
     bool eof;
-    
-    eof = FALSE;
-    imageDoneCount = 0;
 
-    while (!eof && (imageDoneCount < 1 || allimages)) {
-        doOneImage(name, imageDoneCount, fileP,
-                   allimages, justCount, wantComments,
+    for (eof = false, imageDoneCt = 0;
+         !eof && (imageDoneCt < 1 || allimages);
+        ++imageDoneCt
+        ) {
+        doOneImage(name, imageDoneCt, fileP,
+                   reportFormat, allimages, wantComments,
                    &eof);
-        ++imageDoneCount;
     }
-    if (justCount)
-        printf("%s:\t%u images\n", name, imageDoneCount);
+    if (reportFormat == RF_COUNT)
+        printf("%s:\t%u images\n", name, imageDoneCt);
 }
 
 
@@ -232,19 +322,29 @@ main(int argc, const char *argv[]) {
     parseCommandLine(argc, argv, &cmdline);
 
     if (cmdline.inputFileCount == 0)
-        describeOneFile("stdin", stdin, cmdline.allimages || cmdline.count,
-                        cmdline.count, cmdline.comments);
+        describeOneFile("stdin", stdin, cmdline.reportFormat,
+                        cmdline.allimages ||
+                            cmdline.reportFormat == RF_COUNT,
+                        cmdline.comments);
     else {
         unsigned int i;
         for (i = 0; i < cmdline.inputFileCount; ++i) {
             FILE * ifP;
+
             ifP = pm_openr(cmdline.inputFilespec[i]);
-            describeOneFile(cmdline.inputFilespec[i], ifP, 
-                            cmdline.allimages || cmdline.count,
-                            cmdline.count, cmdline.comments);
+
+            describeOneFile(cmdline.inputFilespec[i], ifP,
+                            cmdline.reportFormat,
+                            cmdline.allimages ||
+                                cmdline.reportFormat == RF_COUNT,
+                            cmdline.comments);
+
             pm_close(ifP);
 	    }
 	}
-    
+
     return 0;
 }
+
+
+
diff --git a/analyzer/pamfind.c b/analyzer/pamfind.c
new file mode 100644
index 00000000..860c01ff
--- /dev/null
+++ b/analyzer/pamfind.c
@@ -0,0 +1,222 @@
+#include <nstring.h>
+#include <pam.h>
+
+#include "pm_c_util.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+
+typedef struct {
+    unsigned int * target;
+    unsigned int   targetDepth;
+    const char *   color;  /* NULL means not specified */
+    const char *   inputFileName;
+} CmdLineInfo;
+
+
+static CmdLineInfo
+parsedCommandLine(int                 argc,
+                  const char ** const argv) {
+
+    optEntry * option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    CmdLineInfo cmdLine;
+
+    unsigned int targetSpec, colorSpec;
+    const char ** target;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "target",  OPT_STRINGLIST, &target,         &targetSpec, 0);
+    OPTENT3(0,   "color",   OPT_STRING,     &cmdLine.color,  &colorSpec,  0);
+    OPTENT3(0,  0,          OPT_END,        NULL,            NULL,        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);
+
+    if (targetSpec) {
+        if (colorSpec)
+            pm_error("You cannot specify both -target and -color");
+        else {
+            unsigned int i;
+
+            cmdLine.color = NULL;
+
+            cmdLine.target = NULL;  /* initial value */
+
+            for (i = 0, cmdLine.targetDepth = 0; target[i]; ++i) {
+                unsigned int sampleVal;
+                const char * error;
+
+                pm_string_to_uint(target[i], &sampleVal, &error);
+                if (error) {
+                    pm_error("Invalid sample value in -target option: '%s'.  "
+                             "%s", target[i], error);
+                }
+
+                REALLOCARRAY(cmdLine.target, i+1);
+
+                cmdLine.target[cmdLine.targetDepth++] = sampleVal;
+            }
+
+            free(target);
+        }
+    } else if (!colorSpec)
+        pm_error("You must specify either -target or -color");
+
+    if (argc-1 < 1)
+        cmdLine.inputFileName = "-";
+    else {
+        cmdLine.inputFileName = argv[1];
+
+        if (argc-1 > 1)
+            pm_error("Too many arguments: %u.  "
+                     "The only possible argument is the input file name",
+                     argc-1);
+    }
+    free(option_def);
+
+    return cmdLine;
+}
+
+
+
+static void
+freeCmdLine(CmdLineInfo const cmdLine) {
+
+    if (!cmdLine.color)
+        pnm_freepamtuple(cmdLine.target);
+}
+
+
+
+static tuple
+targetValue(CmdLineInfo  const cmdLine,
+            struct pam * const inpamP) {
+/*----------------------------------------------------------------------------
+   The tuple value user wants us to find in the image, per 'cmdLine'.
+
+   The return value is to be interpreted per *inpamP.
+-----------------------------------------------------------------------------*/
+    tuple retval;
+
+    if (cmdLine.color) {
+        if (inpamP->depth != 3)
+            pm_error("You specified -color, but the input image has "
+                     "depth %u, not 3", inpamP->depth);
+        else
+            retval = pnm_parsecolor(cmdLine.color, inpamP->maxval);
+    } else {
+        if (cmdLine.targetDepth != inpamP->depth)
+            pm_error("You specified a %u-tuple for -target, "
+                     "but the input image of of depth %u",
+                     cmdLine.targetDepth, inpamP->depth);
+        else {
+            unsigned int i;
+
+            retval = pnm_allocpamtuple(inpamP);
+
+            for (i = 0; i < inpamP->depth; ++i)
+                retval[i] = cmdLine.target[i];
+        }
+    }
+
+    return retval;
+}
+
+
+
+static void
+printHeader(FILE *       const ofP,
+            struct pam * const inpamP,
+            tuple        const target) {
+
+    unsigned int plane;
+
+    fprintf(ofP, "Locations containing tuple ");
+
+    fprintf(ofP, "(");
+
+    for (plane = 0; plane < inpamP->depth; ++plane) {
+        fprintf(ofP, "%u", (unsigned)target[plane]);
+        if (plane + 1 < inpamP->depth)
+            fprintf(ofP, "/");
+    }
+
+    fprintf(ofP, ")");
+
+    fprintf(ofP, "/%u", (unsigned)inpamP->maxval);
+
+    fprintf(ofP, ":\n");
+}
+
+
+
+static void
+pamfind(FILE *       const ifP,
+        struct pam * const inpamP,
+        CmdLineInfo  const cmdLine,
+        FILE *       const ofP) {
+
+    pnm_readpaminit(ifP, inpamP, PAM_STRUCT_SIZE(tuple_type));
+
+    {
+        tuple * const inputRow = pnm_allocpamrow(inpamP);
+        tuple   const target   = targetValue(cmdLine, inpamP);
+
+        unsigned int row;
+
+        printHeader(ofP, inpamP, target);
+
+        for (row = 0; row < inpamP->height; ++row) {
+            unsigned int col;
+
+            pnm_readpamrow(inpamP, inputRow);
+
+            for (col = 0; col < inpamP->width; ++col) {
+
+                if (pnm_tupleequal(inpamP, target, inputRow[col])) {
+                    fprintf(ofP, "(%u, %u)\n", row, col);
+                }
+            }
+        }
+        pnm_freepamtuple(target);
+        pnm_freepamrow(inputRow);
+    }
+}
+
+
+
+int
+main(int argc, const char *argv[]) {
+
+    FILE * ifP;
+    CmdLineInfo cmdLine;
+    struct pam  inpam;
+
+    pm_proginit(&argc, argv);
+
+    cmdLine = parsedCommandLine(argc, argv);
+
+    ifP = pm_openr(cmdLine.inputFileName);
+
+    pamfind(ifP, &inpam, cmdLine, stdout);
+
+    freeCmdLine(cmdLine);
+
+    pm_close(inpam.file);
+
+    return 0;
+}
+
+
+
diff --git a/analyzer/pamgetcolor.c b/analyzer/pamgetcolor.c
new file mode 100644
index 00000000..430f3b07
--- /dev/null
+++ b/analyzer/pamgetcolor.c
@@ -0,0 +1,573 @@
+#include <string.h>
+#include <nstring.h>
+#include <pm_gamma.h>
+#include <pam.h>
+
+#include "pm_c_util.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+typedef unsigned int  uint;
+
+typedef struct {
+/*----------------------------------------------------------------------------
+  Specification of a circular "region" over which to measure the average color
+-----------------------------------------------------------------------------*/
+    uint         x;        /* coordinates of the center                      */
+    uint         y;        /* of the region;                                 */
+    const char * label;    /* optional label supplied on the command line    */
+} RegSpec;
+
+typedef struct {
+/*----------------------------------------------------------------------------
+  Represents a single color measurement over a "region"
+-----------------------------------------------------------------------------*/
+    uint         area;     /* area in pixels over which to average the color */
+    /* cumulative normalized intensity-proportiunal value of the region:     */
+    double       color[3];
+} RegData;
+
+typedef struct {
+/*----------------------------------------------------------------------------
+  All the information the user supplied in the command line, in a form easy
+  for the program to use.
+-----------------------------------------------------------------------------*/
+    uint         linear;
+    uint         radius;
+    uint         regN;      /* number of regions                             */
+    uint         maxLbLen;  /* maximum label length                          */
+    RegSpec *    regSpecs;
+        /* list of points to sample, dymamically allocated*/
+    const char * formatStr; /* output color format as string                 */
+    uint         formatId;  /* the Id of the selected color format           */
+    uint         formatArg; /* the argument to the color formatting function */
+    const char * infile;
+} CmdLineInfo;
+
+/* Generic pointer to a color-formatting function. Returns the textual
+   representation of the color <tuple> in terms of the image pointed-to
+   by <pamP>. <param> is a generic integer parameter that depends on the
+   specific funcion and may denote precison or maxval.
+*/
+typedef const char *
+(*FormatColor)(struct pam * const pamP,
+               tuple        const color,
+               uint         const param);
+
+typedef struct ColorFormat {
+/*----------------------------------------------------------------------------
+  The color format specification
+-----------------------------------------------------------------------------*/
+    char        const * id;
+        /* format id (compared against the -format command-line argument) */
+    FormatColor const   formatColor;
+        /* function that returns converts a color into this format */
+    char        const * argName;
+        /* meaning of the <param> argument of <formatColor>() */
+    uint        const   defParam;
+        /* default value of that argument        */
+    uint        const   maxParam;
+        /* maximum value of that argument        */
+} ColorFormat;
+
+
+
+static const char *
+fcInt(struct pam * const pamP,
+      tuple        const color,
+      uint         const param) {
+/*----------------------------------------------------------------------------
+  Format 'color' as an integer tuple with maxval 'param'
+-----------------------------------------------------------------------------*/
+    return pnm_colorspec_rgb_integer(pamP, color, param);
+}
+
+
+
+static const char *
+fcNorm(struct pam * const pamP,
+       tuple        const color,
+       uint         const param) {
+/*----------------------------------------------------------------------------
+  Format 'color' as normalized tuple with precision 'param'
+-----------------------------------------------------------------------------*/
+    return pnm_colorspec_rgb_norm(pamP, color, param);
+}
+
+
+
+static const char *
+fcX11(struct pam * const pamP,
+      tuple        const color,
+      uint         const param) {
+/*----------------------------------------------------------------------------
+  Format 'color' as hexadecimal tuple with 'param' digits
+-----------------------------------------------------------------------------*/
+    return pnm_colorspec_rgb_x11(pamP, color, param);
+}
+
+
+
+static int const defaultFormat = 0;
+
+/* Table with the full information about color formats */
+ColorFormat const formats[ 3 ] = {
+    /*   Id     Function  Argument name  Default  Max   */
+    {   "int",  &fcInt,   "maxval",      255,     65535  },
+    {   "norm", &fcNorm,  "digit count",   3,         6  },
+    {   "x11",  &fcX11,   "digit count",   2,         4  }
+};
+
+
+
+static inline uint
+sqri(int const v) {
+
+    return v * v;
+}
+
+
+
+static RegSpec
+parsedRegSpec(const char * const s) {
+/*----------------------------------------------------------------------------
+  The region specification represented by command line argument 's'.
+
+  's' is of the format x,y[:label].
+-----------------------------------------------------------------------------*/
+    char * end;
+    char * start;
+    RegSpec res;
+
+    start = (char *)s;
+
+    res.x = strtol(start, &end, 10);
+    do {
+        if (start == end)
+            break; /* x not parsed */
+        start = end;
+        if (*end != ',')
+            break;  /* no comma after x */
+        start = end + 1;
+
+        res.y = strtol(start, &end, 10);
+        if (start == end)
+            break; /* y not parsed */
+
+        /* these multiple returns to avoid goto and deep nesting: */
+        if (*end == '\0') { /* no label specified */
+            res.label = (char *)s;
+            return res;
+        }
+        if (*end == ':') { /* a label specified */
+            res.label = end + 1;
+            if (*res.label == '\0')
+                break; /* empty label */
+            return res;
+        }
+    } while (false);
+
+    pm_error("Wrong region specification: %s", s);
+
+    return res; /* to avoid the false warning that nothing is returned */
+}
+
+
+
+static void
+parseColorFmt(const char * const formatStr,
+              uint *       const formatIdP,
+              uint *       const formatArgP) {
+/*----------------------------------------------------------------------------
+  Parse the color format specification string 'formatStr' as
+  *formatIdP and *formatArgP.
+
+  A format specification string is of format format[:arg].
+-----------------------------------------------------------------------------*/
+    const char *  const errSpec = "Wrong color format specification: ";
+
+    const char *  colonLoc; /* location of the colon in the specification */
+    uint          n, f;
+    const ColorFormat * formatP;
+    uint formatId;
+    bool found;
+
+    colonLoc  = strchr(formatStr, ':');
+    if (colonLoc != NULL) n = colonLoc - formatStr;
+    else                  n = strlen(formatStr);
+
+    for (f = 0, found = false; f < ARRAY_SIZE(formats) && !found; ++f) {
+        if (strncmp(formatStr, formats[f].id, n) == 0) {
+            found = true;
+            formatId = f;
+        }
+    }
+    if (!found)
+        pm_error("Color format not recognized.");
+
+    *formatIdP = formatId;
+
+    formatP = &formats[formatId];
+
+    if (colonLoc) {
+        long int arg;
+        const char * argStart;
+        char * argEnd;
+
+        argStart = colonLoc + 1;
+
+        if (*argStart == '\0')
+            pm_error("%sthe colon should be followed by %s.",
+                errSpec, formatP->argName);
+
+        arg = strtol(argStart, &argEnd, 10);
+
+        if (*argEnd != '\0')
+            pm_error("%sfailed to parse the %s: %s.",
+                errSpec, formatP->argName, argStart);
+
+        if (arg < 1)
+            pm_error("%s%s must be greater than zero.",
+                errSpec, formatP->argName);
+
+        if (arg > formatP->maxParam)
+            pm_error("%s%s cannot exceed %i.",
+                errSpec, formatP->argName, formatP->maxParam);
+
+        *formatArgP = arg;
+    } else
+        *formatArgP = formatP->defParam;
+}
+
+
+
+static CmdLineInfo
+parsedCommandLine(int                 argc,
+                  const char ** const argv) {
+
+    optEntry * option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    CmdLineInfo cmdLine;
+
+    uint infileSpec, radiusSpec, formatSpec, linearSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "infile",    OPT_STRING, &cmdLine.infile,    &infileSpec, 0);
+    OPTENT3(0, "radius",    OPT_INT,    &cmdLine.radius,    &radiusSpec, 0);
+    OPTENT3(0, "format",    OPT_STRING, &cmdLine.formatStr, &formatSpec, 0);
+    OPTENT3(0, "linear",    OPT_FLAG,   &cmdLine.linear,    &linearSpec, 0);
+    OPTENT3(0,  0,          OPT_END,    NULL,               NULL,        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);
+
+    if (!infileSpec)
+        cmdLine.infile = "-";
+
+    if (!radiusSpec)
+        cmdLine.radius = 0;
+
+    if (formatSpec) {
+        parseColorFmt(cmdLine.formatStr,
+                      &cmdLine.formatId, &cmdLine.formatArg);
+    } else {
+        cmdLine.formatId  = defaultFormat;
+        cmdLine.formatArg = formats[defaultFormat].defParam;
+    }
+
+    if (!linearSpec)
+        cmdLine.radius = 0;
+
+    if (argc-1 < 1)
+        pm_error("No regions specified.");
+
+    cmdLine.regN = argc - 1;
+
+    MALLOCARRAY(cmdLine.regSpecs, cmdLine.regN);
+
+    if (!cmdLine.regSpecs)
+        pm_error("Could not get memory for %u region specifications",
+                 cmdLine.regN);
+
+    {
+        uint r;
+        uint maxLbLen;
+
+        for (r = 0, maxLbLen = 0; r < argc - 1; ++r) {
+            size_t lbLen;
+            cmdLine.regSpecs[r] = parsedRegSpec(argv[r+1]);
+            lbLen = strlen(cmdLine.regSpecs[r].label);
+            maxLbLen = MAX(maxLbLen, lbLen);
+        }
+        cmdLine.maxLbLen = maxLbLen;
+    }
+
+    free(option_def);
+
+    return cmdLine;
+}
+
+
+
+static void
+freeCommandLine(CmdLineInfo const cmdLine) {
+
+    free(cmdLine.regSpecs);
+}
+
+
+
+static RegData *
+allocRegSamples(uint n) {
+/*----------------------------------------------------------------------------
+  Allocate an array of <n> initialized region samples.  The array should be
+  freed after use.
+-----------------------------------------------------------------------------*/
+    uint r;
+    RegData * regSamples;
+    regSamples = calloc(n, sizeof(RegData));
+
+    for (r = 0; r < n; ++r) {
+        uint l;
+
+        regSamples[r].area = 0;
+
+        for (l = 0; l < 3; ++l)
+            regSamples[r].color[l] = 0.0;
+    }
+    return regSamples;
+}
+
+
+
+static uint
+getYmax(struct pam * const pamP,
+        CmdLineInfo  const cmdLine) {
+/*----------------------------------------------------------------------------
+  Find the maximum row in the image that contains a pixel from a region.
+-----------------------------------------------------------------------------*/
+    uint ymax, r, ycmax;
+    ycmax = 0;
+
+    for (r = 0; r < cmdLine.regN; ++r) {
+        RegSpec spec = cmdLine.regSpecs[r];
+        if (spec.y >= pamP->height || spec.x >= pamP->width)
+            pm_error("Region at %i,%i is outside the image boundaries.",
+                     spec.x, spec.y);
+
+        if (spec.y > ycmax)
+            ycmax = spec.y;
+    }
+    ymax = MIN(pamP->height - 1, ycmax + cmdLine.radius);
+
+    return ymax;
+}
+
+
+
+static void
+readChord(RegData *    const dataP,
+          uint         const linear,
+          struct pam * const pamP,
+          tuple *      const row,
+          uint         const x0,
+          uint         const x1) {
+/*----------------------------------------------------------------------------
+  Update region sample *dataP with the data from horizontal chord lying in row
+  'row' and going from 'x0' to 'x1'. 'linear' means tuples in 'row' are the
+  intensity-linear values as opposed to normal libnetpbm gamma-adjusted
+  values.
+-----------------------------------------------------------------------------*/
+    uint x;
+
+    for (x = x0; x <= x1; ++x) {
+        uint l;
+
+        for (l = 0; l < 3; ++l) {
+            double val;
+
+            val = (double)row[x][l] / pamP->maxval;
+            /* convert to intensity because brightness is not additive: */
+            if (!linear)
+                val = pm_ungamma709(val);
+            dataP->color[l] += val;
+        }
+        ++dataP->area;
+    }
+}
+
+
+
+static void
+processRow(tuple *             const row,
+           uint                const y,
+           struct pam *        const pamP,
+           const CmdLineInfo * const cmdLineP,
+           RegData *           const regSamples) {
+/*----------------------------------------------------------------------------
+  Read a row from image described by *pamP into 'row', and update region
+  samples regSamples[] from it.  'y' is the position of the row.
+-----------------------------------------------------------------------------*/
+    uint r;
+
+    pnm_readpamrow(pamP, row);
+
+    for (r = 0; r < cmdLineP->regN; ++r) {
+        RegSpec   const spec = cmdLineP->regSpecs[r];
+        RegData * const dataP = &regSamples[r];
+        int       const yd = (int)spec.y - (int)y;
+
+        if (abs(yd) > cmdLineP->radius) {
+            /* Row is entirely above or below the region; Avoid the slow root
+               operation
+            */
+        } else {
+            uint const xd2 = sqri(cmdLineP->radius) - sqri(yd);
+            uint const xd  = ROUNDU(sqrt((double)xd2));
+
+            int x0, x1;
+
+            x0 = spec.x - xd;  /* initial value */
+            x1 = spec.x + xd;  /* initial value */
+
+            /* clip horizontal chord to image boundaries: */
+            if (x0 < 0)
+                x0 = 0;
+            if (x1 >= pamP->width)
+                x1 = pamP->width - 1;
+
+            readChord(dataP, cmdLineP->linear, pamP, row, x0, x1);
+        }
+    }
+}
+
+
+
+static RegData *
+colorsFmImage(struct pam * const pamP,
+              CmdLineInfo  const cmdLine) {
+/*----------------------------------------------------------------------------
+  Color data for the regions requested by 'cmdLine' in the image described by
+  *pamP.
+-----------------------------------------------------------------------------*/
+    uint      y, ymax;
+    RegData * samplesP;
+    tuple *   row;
+    FILE *    ifP;
+
+    ifP = pm_openr(cmdLine.infile);
+
+    pnm_readpaminit(ifP, pamP, PAM_STRUCT_SIZE(tuple_type));
+
+    ymax = getYmax(pamP, cmdLine);
+
+    samplesP = allocRegSamples(cmdLine.regN);
+    row      = pnm_allocpamrow(pamP);
+
+    for (y = 0; y <= ymax; ++y)
+        processRow(row, y, pamP, &cmdLine, samplesP);
+
+    pnm_freepamrow(row);
+    pm_close(ifP);
+
+    return samplesP;
+}
+
+
+
+static const char *
+outputColorSpec(RegData      const data,
+                CmdLineInfo  const cmdLine,
+                struct pam * const pamP,
+                tuple        const tup) {
+/*----------------------------------------------------------------------------
+  Color of region sample 'data' formatted for output as requested by
+  'cmdLine'.
+
+  *pamP tells how to interpret 'data'.
+
+  'tup' is working space for internal use.
+-----------------------------------------------------------------------------*/
+    uint l;
+
+    for (l = 0; l < 3; ++l)
+        tup[l] = pm_gamma709(data.color[l]/data.area) * pamP->maxval;
+
+    return formats[cmdLine.formatId].
+        formatColor(pamP, tup, cmdLine.formatArg);
+}
+
+
+
+static void
+printColors(struct pam *    const pamP,
+            CmdLineInfo     const cmdLine,
+            FILE *          const ofP,
+            const RegData * const regSamples) {
+/*----------------------------------------------------------------------------
+  Print the colors regSamples[] to *ofP in the format
+  requested by 'cmdLine'.
+
+  *pamP tells how to interpret regSamples[]
+-----------------------------------------------------------------------------*/
+    char  fmt[20];
+    uint  r;
+    tuple tup;
+
+    tup = pnm_allocpamtuple(pamP);
+
+    pm_snprintf(fmt, sizeof(fmt), "%%%is: %%s\n", cmdLine.maxLbLen);
+
+    for (r = 0; r < cmdLine.regN; ++r) {
+        RegSpec      spec;
+        RegData      data;
+        const char * color;
+
+        spec  = cmdLine.regSpecs[r];
+
+        data  = regSamples[r];
+
+        color = outputColorSpec(data, cmdLine, pamP, tup);
+
+        fprintf(ofP, fmt, spec.label, color);
+
+        pm_strfree(color);
+    }
+    pnm_freepamtuple(tup);
+}
+
+
+
+int
+main(int argc, const char *argv[]) {
+
+    RegData *   regSamples;
+    CmdLineInfo cmdLine;
+    struct pam  pam;
+
+    pm_proginit(&argc, argv);
+
+    cmdLine = parsedCommandLine(argc, argv);
+
+    regSamples = colorsFmImage(&pam, cmdLine);
+
+    printColors(&pam, cmdLine, stdout, regSamples);
+
+    freeCommandLine(cmdLine);
+    free(regSamples);
+
+    return 0;
+}
+
+
+
diff --git a/analyzer/pamsumm.c b/analyzer/pamsumm.c
index c427fa7d..9b74e789 100644
--- a/analyzer/pamsumm.c
+++ b/analyzer/pamsumm.c
@@ -1,43 +1,38 @@
-/******************************************************************************
+/*=============================================================================
                                pamsumm
-*******************************************************************************
+===============================================================================
   Summarize all the samples of a PAM image with various functions.
 
   By Bryan Henderson, San Jose CA 2004.02.07.
 
   Contributed to the public domain
-
-
-******************************************************************************/
-
+=============================================================================*/
 #include "pm_c_util.h"
 #include "pam.h"
 #include "shhopt.h"
 #include "mallocvar.h"
 
-enum function {FN_ADD, FN_MEAN, FN_MIN, FN_MAX};
+enum Function {FN_ADD, FN_MEAN, FN_MIN, FN_MAX};
 
-struct cmdlineInfo {
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
-    const char *inputFilespec;  /* Filespec of input file */
-    enum function function;
-    unsigned int normalize;
-    unsigned int brief;
-    unsigned int verbose;
+    const char *  inputFileName;  /* Name of input file */
+    enum Function function;
+    unsigned int  normalize;
+    unsigned int  brief;
+    unsigned int  verbose;
 };
 
 
+
 static void
-parseCommandLine(int argc, char ** const argv,
-                 struct cmdlineInfo * const cmdlineP) {
-/*----------------------------------------------------------------------------
-   Note that the file spec array we return is stored in the storage that
-   was passed to us as the argv array.
------------------------------------------------------------------------------*/
-    optEntry *option_def = malloc(100*sizeof(optEntry));
-        /* Instructions to OptParseOptions2 on how to parse our options.
+parseCommandLine(int argc, const char ** const argv,
+                 struct CmdlineInfo * const cmdlineP) {
+
+    optEntry * option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
          */
     optStruct3 opt;
 
@@ -45,7 +40,9 @@ parseCommandLine(int argc, char ** const argv,
 
     unsigned int sumSpec, meanSpec, minSpec, maxSpec;
 
-    option_def_index = 0;   /* incremented by OPTENTRY */
+    MALLOCARRAY(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
     OPTENT3(0,   "sum",       OPT_FLAG,  NULL, &sumSpec,             0);
     OPTENT3(0,   "mean",      OPT_FLAG,  NULL, &meanSpec,            0);
     OPTENT3(0,   "min",       OPT_FLAG,  NULL, &minSpec,             0);
@@ -58,7 +55,7 @@ parseCommandLine(int argc, char ** const argv,
     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, argv, opt, sizeof(opt), 0);
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
     if (sumSpec + minSpec + maxSpec > 1)
@@ -72,22 +69,24 @@ parseCommandLine(int argc, char ** const argv,
         cmdlineP->function = FN_MIN;
     } else if (maxSpec) {
         cmdlineP->function = FN_MAX;
-    } else 
+    } else
         pm_error("You must specify one of -sum, -min, -max, or -mean");
-        
+
     if (argc-1 > 1)
-        pm_error("Too many arguments (%d).  File spec is the only argument.",
+        pm_error("Too many arguments (%d).  File name is the only argument.",
                  argc-1);
 
     if (argc-1 < 1)
-        cmdlineP->inputFilespec = "-";
-    else 
-        cmdlineP->inputFilespec = argv[1];
-    
+        cmdlineP->inputFileName = "-";
+    else
+        cmdlineP->inputFileName = argv[1];
+
+    free(option_def);
 }
 
 
-struct accum {
+
+struct Accum {
     union {
         double sum;
         unsigned int min;
@@ -98,8 +97,8 @@ struct accum {
 
 
 static void
-initAccumulator(struct accum * const accumulatorP,
-                enum function  const function) {
+initAccumulator(struct Accum * const accumulatorP,
+                enum Function  const function) {
 
     switch(function) {
     case FN_ADD:  accumulatorP->u.sum = 0.0;      break;
@@ -114,8 +113,8 @@ initAccumulator(struct accum * const accumulatorP,
 static void
 aggregate(struct pam *   const inpamP,
           tuple *        const tupleRow,
-          enum function  const function,
-          struct accum * const accumulatorP) {
+          enum Function  const function,
+          struct Accum * const accumulatorP) {
 
     unsigned int col;
 
@@ -123,11 +122,11 @@ aggregate(struct pam *   const inpamP,
         unsigned int plane;
         for (plane = 0; plane < inpamP->depth; ++plane) {
             switch(function) {
-            case FN_ADD:  
-            case FN_MEAN: 
+            case FN_ADD:
+            case FN_MEAN:
                 accumulatorP->u.sum += tupleRow[col][plane];
             break;
-            case FN_MIN:  
+            case FN_MIN:
                 if (tupleRow[col][plane] < accumulatorP->u.min)
                     accumulatorP->u.min = tupleRow[col][plane];
                 break;
@@ -135,7 +134,7 @@ aggregate(struct pam *   const inpamP,
                 if (tupleRow[col][plane] > accumulatorP->u.min)
                     accumulatorP->u.min = tupleRow[col][plane];
                 break;
-            } 
+            }
         }
     }
 }
@@ -143,18 +142,18 @@ aggregate(struct pam *   const inpamP,
 
 
 static void
-printSummary(struct accum  const accumulator,
+printSummary(struct Accum  const accumulator,
              unsigned int  const scale,
              unsigned int  const count,
-             enum function const function,
-             bool          const normalize,
+             enum Function const function,
+             bool          const mustNormalize,
              bool          const brief) {
 
-    switch(function) {
-    case FN_ADD: {  
+    switch (function) {
+    case FN_ADD: {
         const char * const intro = brief ? "" : "the sum of all samples is ";
 
-        if (normalize)
+        if (mustNormalize)
             printf("%s%f\n", intro, accumulator.u.sum/scale);
         else
             printf("%s%u\n", intro, (unsigned int)accumulator.u.sum);
@@ -163,27 +162,27 @@ printSummary(struct accum  const accumulator,
     case FN_MEAN: {
         const char * const intro = brief ? "" : "the mean of all samples is ";
 
-        if (normalize)
+        if (mustNormalize)
             printf("%s%f\n", intro, accumulator.u.sum/count/scale);
         else
             printf("%s%f\n", intro, accumulator.u.sum/count);
     }
     break;
     case FN_MIN: {
-        const char * const intro = 
+        const char * const intro =
             brief ? "" : "the minimum of all samples is ";
 
-        if (normalize)
+        if (mustNormalize)
             printf("%s%f\n", intro, (double)accumulator.u.min/scale);
         else
             printf("%s%u\n", intro, accumulator.u.min);
     }
     break;
     case FN_MAX: {
-        const char * const intro = 
+        const char * const intro =
             brief ? "" : "the maximum of all samples is ";
 
-        if (normalize)
+        if (mustNormalize)
             printf("%s%f\n", intro, (double)accumulator.u.max/scale);
         else
             printf("%s%u\n", intro, accumulator.u.max);
@@ -195,20 +194,20 @@ printSummary(struct accum  const accumulator,
 
 
 int
-main(int argc, char *argv[]) {
+main(int argc, const char *argv[]) {
 
-    FILE* ifP;
-    tuple* inputRow;   /* Row from input image */
-    int row;
-    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    tuple * inputRow;   /* Row from input image */
+    unsigned int row;
+    struct CmdlineInfo cmdline;
     struct pam inpam;   /* Input PAM image */
-    struct accum accumulator;
+    struct Accum accumulator;
 
-    pnm_init(&argc, argv);
+    pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
 
-    ifP = pm_openr(cmdline.inputFilespec);
+    ifP = pm_openr(cmdline.inputFileName);
 
     pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
 
@@ -216,17 +215,17 @@ main(int argc, char *argv[]) {
 
     initAccumulator(&accumulator, cmdline.function);
 
-    for (row = 0; row < inpam.height; row++) {
+    for (row = 0; row < inpam.height; ++row) {
         pnm_readpamrow(&inpam, inputRow);
 
         aggregate(&inpam, inputRow, cmdline.function, &accumulator);
     }
     printSummary(accumulator, (unsigned)inpam.maxval,
-                 inpam.height * inpam.width * inpam.depth, 
+                 inpam.height * inpam.width * inpam.depth,
                  cmdline.function, cmdline.normalize, cmdline.brief);
 
     pnm_freepamrow(inputRow);
     pm_close(inpam.file);
-    
+
     return 0;
 }
diff --git a/analyzer/pamtable.c b/analyzer/pamtable.c
new file mode 100644
index 00000000..2835469a
--- /dev/null
+++ b/analyzer/pamtable.c
@@ -0,0 +1,200 @@
+/*=============================================================================
+                               pamtable
+===============================================================================
+  Print the raster as a table of numbers.
+
+  By Bryan Henderson, San Jose CA 2017.04.15.
+
+  Contributed to the public domain
+
+=============================================================================*/
+#include <math.h>
+#include "pm_c_util.h"
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+
+struct CmdlineInfo {
+    /* 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  verbose;
+};
+
+
+static void
+parseCommandLine(int argc, const char ** const argv,
+                 struct CmdlineInfo * const cmdlineP) {
+
+    optEntry * option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    MALLOCARRAY(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+
+    OPTENT3(0,   "verbose",   OPT_FLAG,  NULL, &cmdlineP->verbose,   0);
+        /* For future expansion */
+
+    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 > 1)
+        pm_error("Too many arguments (%d).  File name is the only argument.",
+                 argc-1);
+
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else
+        cmdlineP->inputFileName = argv[1];
+
+    free(option_def);
+}
+
+
+
+typedef struct {
+
+    const char * sampleFmt;
+       /* Printf format of a sample, e.g. %3u */
+
+    const char * interSampleGutter;
+       /* What we print between samples within a tuple */
+
+    const char * interTupleGutter;
+       /* What we print between tuples within a row */
+
+} Format;
+
+
+
+static const char *
+sampleFormat(const struct pam * const pamP) {
+/*----------------------------------------------------------------------------
+   The printf format string for a single sample in the output table.
+
+   E.g "%03u".
+
+   This format does not include any spacing between samples.
+-----------------------------------------------------------------------------*/
+    unsigned int const decimalWidth = ROUNDU(ceil(log10(pamP->maxval + 1)));
+
+    const char * retval;
+
+    pm_asprintf(&retval, "%%%uu", decimalWidth);
+
+    return retval;
+}
+
+
+
+static void
+makeFormat(const struct pam * const pamP,
+           Format *           const formatP) {
+
+    formatP->sampleFmt = sampleFormat(pamP);
+
+    formatP->interSampleGutter = " ";
+
+    formatP->interTupleGutter = pamP->depth > 1 ? "|" : " ";
+}
+
+
+
+static void
+unmakeFormat(Format * const formatP) {
+
+    pm_strfree(formatP->sampleFmt);
+}
+
+
+
+static void
+printRow(const struct pam * const pamP,
+         tuple *            const tupleRow,
+         Format             const format,
+         FILE *             const ofP) {
+
+    unsigned int col;
+
+    for (col = 0; col < pamP->width; ++col) {
+        unsigned int plane;
+
+        if (col > 0)
+            fputs(format.interTupleGutter, ofP);
+
+        for (plane = 0; plane < pamP->depth; ++plane) {
+
+            if (plane > 0)
+                fputs(format.interSampleGutter, ofP);
+
+            fprintf(ofP, format.sampleFmt, tupleRow[col][plane]);
+        }
+    }
+
+    fputs("\n", ofP);
+}
+
+
+
+static void
+printRaster(FILE *             const ifP,
+            const struct pam * const pamP,
+            FILE *             const ofP) {
+
+    Format format;
+
+    tuple * inputRow;   /* Row from input image */
+    unsigned int row;
+
+    makeFormat(pamP, &format);
+
+    inputRow = pnm_allocpamrow(pamP);
+
+    for (row = 0; row < pamP->height; ++row) {
+        pnm_readpamrow(pamP, inputRow);
+
+        printRow(pamP, inputRow, format, ofP);
+    }
+
+    pnm_freepamrow(inputRow);
+
+    unmakeFormat(&format);
+}
+
+
+
+int
+main(int argc, const char *argv[]) {
+
+    FILE * ifP;
+    struct CmdlineInfo cmdline;
+    struct pam inpam;   /* Input PAM image */
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    printRaster(ifP, &inpam, stdout);
+
+    pm_close(inpam.file);
+
+    return 0;
+}
+
+
+
diff --git a/analyzer/pamtilt.c b/analyzer/pamtilt.c
index 302c6247..70338545 100644
--- a/analyzer/pamtilt.c
+++ b/analyzer/pamtilt.c
@@ -10,7 +10,7 @@
   All work has been contributed to the public domain by its authors.
 =============================================================================*/
 
-#define _XOPEN_SOURCE   /* get M_PI in math.h */
+#define _XOPEN_SOURCE 500  /* get M_PI in math.h */
 
 #include <assert.h>
 #include <math.h>
diff --git a/analyzer/pgmtexture.c b/analyzer/pgmtexture.c
index ea0db9b0..4e0dd4d5 100644
--- a/analyzer/pgmtexture.c
+++ b/analyzer/pgmtexture.c
@@ -34,9 +34,9 @@
 ** ANY SUCH ITEM. LICENSEE AND/OR USER AGREES TO INDEMNIFY AND HOLD
 ** TAES AND TAMUS HARMLESS FROM ANY CLAIMS ARISING OUT OF THE USE OR
 ** POSSESSION OF SUCH ITEMS.
-** 
+**
 ** Modification History:
-** 24 Jun 91 - J. Michael Carstensen <jmc@imsor.dth.dk> supplied fix for 
+** 24 Jun 91 - J. Michael Carstensen <jmc@imsor.dth.dk> supplied fix for
 **             correlation function.
 **
 ** 05 Oct 05 - Marc Breithecker <Marc.Breithecker@informatik.uni-erlangen.de>
@@ -154,7 +154,7 @@ matrix (unsigned int const nrl,
 
 
 
-static void 
+static void
 results (const char *  const name,
          const float * const a) {
 
@@ -170,7 +170,7 @@ results (const char *  const name,
 
 
 
-static void 
+static void
 simplesrt (unsigned int  const n,
            float *       const arr) {
 
@@ -193,7 +193,7 @@ simplesrt (unsigned int  const n,
 
 
 
-static void 
+static void
 mkbalanced (float **     const a,
             unsigned int const n) {
 
@@ -243,7 +243,7 @@ mkbalanced (float **     const a,
 
 
 
-static void 
+static void
 reduction (float **     const a,
            unsigned int const n) {
 
@@ -265,7 +265,7 @@ reduction (float **     const a,
             for (j = m - 1; j <= n; ++j)
                 SWAP(a[i][j], a[m][j]);
             for (j = 1; j <= n; j++)
-                SWAP(a[j][i], a[j][m]); 
+                SWAP(a[j][i], a[j][m]);
             a[j][i] = a[j][i];
         }
         if (x != 0.0) {
@@ -305,7 +305,7 @@ norm(float **     const a,
 
 
 
-static void 
+static void
 hessenberg(float **     const a,
            unsigned int const n,
            float *      const wr,
@@ -347,7 +347,7 @@ hessenberg(float **     const a,
                     float const z = sqrt(fabs(q));
                     x += t;
                     if (q >= 0.0) {
-                        float const z2 = p + sign(z, p); 
+                        float const z2 = p + sign(z, p);
                         wr[nn - 1] = wr[nn] = x + z2;
                         if (z2)
                             wr[nn] = x - w / z2;
@@ -389,7 +389,7 @@ hessenberg(float **     const a,
                         if (m == l)
                             break;
                         u = fabs(a[m][m - 1]) * (fabs(q) + fabs(r));
-                        v = fabs(p) * (fabs(a[m - 1][m - 1]) + fabs(z) + 
+                        v = fabs(p) * (fabs(a[m - 1][m - 1]) + fabs(z) +
                                        fabs(a[m + 1][m + 1]));
                         if (u + v == v)
                             break;
@@ -457,7 +457,7 @@ hessenberg(float **     const a,
 
 
 
-static float 
+static float
 f1_a2m(float **     const p,
        unsigned int const ng) {
 /*----------------------------------------------------------------------------
@@ -481,12 +481,12 @@ f1_a2m(float **     const p,
 
 
 
-static float 
+static float
 f2_contrast(float **     const p,
             unsigned int const ng) {
 /*----------------------------------------------------------------------------
    Contrast
-   
+
    The contrast feature is a difference moment of the P matrix and is a
    measure of the contrast or the amount of local variations present in an
    image.
@@ -511,7 +511,7 @@ f2_contrast(float **     const p,
 
 
 
-static float 
+static float
 f3_corr(float **     const p,
         unsigned int const ng) {
 /*----------------------------------------------------------------------------
@@ -521,11 +521,12 @@ f3_corr(float **     const p,
    the image.
 -----------------------------------------------------------------------------*/
     unsigned int i;
-    float sumSqrx, sumSqry, tmp;
+    float sumSqrx;
+    float tmp;
     float * px;
     float meanx, meany, stddevx, stddevy;
 
-    sumSqrx = 0.0; sumSqry = 0.0;
+    sumSqrx = 0.0;
     meanx = 0.0; meany = 0.0;
 
     px = vector(0, ng);
@@ -548,7 +549,6 @@ f3_corr(float **     const p,
     }
 
     meany = meanx;
-    sumSqry = sumSqrx;
     stddevx = sqrt(sumSqrx - (SQR(meanx)));
     stddevy = stddevx;
 
@@ -563,7 +563,7 @@ f3_corr(float **     const p,
 
 
 
-static float 
+static float
 f4_var (float **     const p,
         unsigned int const ng) {
 /*----------------------------------------------------------------------------
@@ -587,7 +587,7 @@ f4_var (float **     const p,
 
 
 
-static float 
+static float
 f5_idm (float **     const p,
         unsigned int const ng) {
 /*----------------------------------------------------------------------------
@@ -606,7 +606,7 @@ f5_idm (float **     const p,
 
 
 
-static float 
+static float
 f6_savg (float **     const p,
          unsigned int const ng) {
 /*----------------------------------------------------------------------------
@@ -634,7 +634,7 @@ f6_savg (float **     const p,
 
 
 
-static float 
+static float
 f7_svar (float **     const p,
          unsigned int const ng,
          float        const s) {
@@ -663,7 +663,7 @@ f7_svar (float **     const p,
 
 
 
-static float 
+static float
 f8_sentropy (float **     const p,
              unsigned int const ng) {
 /*----------------------------------------------------------------------------
@@ -691,7 +691,7 @@ f8_sentropy (float **     const p,
 
 
 
-static float 
+static float
 f9_entropy (float **     const p,
             unsigned int const ng) {
 /*----------------------------------------------------------------------------
@@ -710,9 +710,9 @@ f9_entropy (float **     const p,
 
 
 
-static float 
-f10_dvar (float **     const p,
-          unsigned int const ng) {
+static float
+f10_dvar(float **     const p,
+         unsigned int const ng) {
 /*----------------------------------------------------------------------------
    Difference Variance
 -----------------------------------------------------------------------------*/
@@ -731,7 +731,7 @@ f10_dvar (float **     const p,
     for (i = 0; i < ng; ++i) {
         unsigned int j;
         for (j = 0; j < ng; ++j)
-            pxpy[abs(i - j)] += p[i][j];
+            pxpy[abs((int)i - (int)j)] += p[i][j];
     }
     /* Now calculate the variance of Pxpy (Px-y) */
     for (i = 0, sum = 0.0, sumSqr = 0.0; i < ng; ++i) {
@@ -746,7 +746,7 @@ f10_dvar (float **     const p,
 
 
 
-static float 
+static float
 f11_dentropy (float **     const p,
               unsigned int const ng) {
 /*----------------------------------------------------------------------------
@@ -764,7 +764,7 @@ f11_dentropy (float **     const p,
     for (i = 0; i < ng; ++i) {
         unsigned int j;
         for (j = 0; j < ng; ++j)
-            pxpy[abs(i - j)] += p[i][j];
+            pxpy[abs((int)i - (int)j)] += p[i][j];
     }
     for (i = 0, sum = 0.0; i < ng; ++i)
         sum += pxpy[i] * log10(pxpy[i] + EPSILON);
@@ -774,7 +774,7 @@ f11_dentropy (float **     const p,
 
 
 
-static float 
+static float
 f12_icorr (float **     const p,
            unsigned int const ng) {
 /*----------------------------------------------------------------------------
@@ -820,8 +820,8 @@ f12_icorr (float **     const p,
 
 
 
-static float 
-f13_icorr (float **     const p, 
+static float
+f13_icorr (float **     const p,
            unsigned int const ng) {
 /*----------------------------------------------------------------------------
   Information Measures of Correlation
@@ -866,7 +866,7 @@ f13_icorr (float **     const p,
 
 
 
-static float 
+static float
 f14_maxcorr (float **     const p,
              unsigned int const ng) {
 /*----------------------------------------------------------------------------
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);
diff --git a/analyzer/ppmhist.c b/analyzer/ppmhist.c
index 9b526606..c4ab3581 100644
--- a/analyzer/ppmhist.c
+++ b/analyzer/ppmhist.c
@@ -22,7 +22,7 @@ enum sort {SORT_BY_FREQUENCY, SORT_BY_RGB};
 
 enum ColorFmt {FMT_DECIMAL, FMT_HEX, FMT_FLOAT, FMT_PPMPLAIN};
 
-struct cmdline_info {
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
@@ -38,7 +38,7 @@ struct cmdline_info {
 
 static void
 parseCommandLine(int argc, const char ** argv,
-                 struct cmdline_info * const cmdlineP) {
+                 struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that the file spec array we return is stored in the storage that
    was passed to us as the argv array.
@@ -172,7 +172,7 @@ 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'.
+  states its 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
@@ -230,21 +230,21 @@ separateInvalidItems(colorhist_vector const chv,
                      unsigned int  *  const validColorCtP) {
 /*----------------------------------------------------------------------------
   Move invalid color entries from chv to chvInvalid.
-  Count how many color entries are valid. 
+  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
@@ -261,7 +261,7 @@ sortHistogramForensic(enum sort        const sortFn,
 
     {
         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;
@@ -269,7 +269,7 @@ sortHistogramForensic(enum sort        const sortFn,
 
         qsort((void*) chv, validColorCt,
               sizeof(struct colorhist_item), compare_function);
-        
+
         qsort((void*) chvInvalid, colorCt - validColorCt,
               sizeof(struct colorhist_item), compare_function);
     }
@@ -295,20 +295,104 @@ sortHistogramNormal(enum sort        const sortFn,
 }
 
 
+typedef struct {
+    unsigned int nTotal;
+        /* Number of colors; sum of all the following */
+    unsigned int nBlack;
+        /* 1 if black is present; 0 otherwise */
+    unsigned int nWhite;
+        /* 1 if white is present; 0 otherwise */
+    unsigned int nGray;
+        /* Number of gray shades, not including black and white */
+    unsigned int nColor;
+        /* number of colors other than black, white, and gray */
+
+} ColorSummary;
+
+
+
+static ColorSummary
+colorSummary(colorhist_vector const chv,
+             unsigned int     const colorCt,
+             pixval           const maxval) {
+
+    ColorSummary retval;
+
+    unsigned int i;
+
+    retval.nTotal = colorCt;
+
+    for (i = 0,
+             retval.nBlack = 0,
+             retval.nWhite = 0,
+             retval.nGray = 0,
+             retval.nColor = 0;
+         i < colorCt;
+         ++i) {
+
+        pixel const color = chv[i].color;
+        pixval const r = PPM_GETR(color);
+        pixval const g = PPM_GETG(color);
+        pixval const b = PPM_GETB(color);
+
+        if (r == 0 && g == 0 && b == 0)
+            ++retval.nBlack;
+        else if (r == maxval && g == maxval && b == maxval)
+            ++retval.nWhite;
+        else if (r == g && r ==b)
+            ++retval.nGray;
+        else
+            ++retval.nColor;
+    }
+    assert(retval.nBlack + retval.nWhite + retval.nGray + retval.nColor ==
+           retval.nTotal);
+
+    return retval;
+}
+
+
+static void
+printColorSummary(ColorSummary const colorSummary,
+                  const char * const prefix) {
+
+    printf("%sSummary: %u colors: %u black, %u white, %u gray, %u color\n",
+           prefix,
+           colorSummary.nTotal,
+           colorSummary.nBlack,
+           colorSummary.nWhite,
+           colorSummary.nGray,
+           colorSummary.nColor);
+
+    printf("\n");
+}
+
+
+
+typedef struct {
+/*----------------------------------------------------------------------------
+   A map of color name to color.
+
+   The actual information is outside of this structure; we just point to it.
+-----------------------------------------------------------------------------*/
+    unsigned int  n;
+
+    pixel *       color;
+
+    const char ** name;
+} ColorDict;
+
+
 
 static const char *
-colornameLabel(pixel        const color,
-               pixval       const maxval,
-               unsigned int const nDictColor,
-               pixel        const dictColors[],
-               const char * const dictColornames[]) {
+colornameLabel(pixel     const color,
+               pixval    const maxval,
+               ColorDict const colorDict) {
 /*----------------------------------------------------------------------------
    Return the name of the color 'color' or the closest color in the
    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
-   dictionary.
+   'colorDict' is the color dictionary.
 
    Return the name in static storage within this subroutine.
 -----------------------------------------------------------------------------*/
@@ -322,16 +406,16 @@ colornameLabel(pixel        const color,
 
     PPM_DEPTH(color255, color, maxval, 255);
 
-    colorIndex = ppm_findclosestcolor(dictColors, nDictColor, &color255);
+    colorIndex = ppm_findclosestcolor(colorDict.color, colorDict.n, &color255);
 
-    assert(colorIndex >= 0 && colorIndex < nDictColor);
+    assert(colorIndex >= 0 && colorIndex < colorDict.n);
 
-    if (PPM_EQUAL(dictColors[colorIndex], color))
+    if (PPM_EQUAL(colorDict.color[colorIndex], color))
         STRSCPY(retval, " ");
     else
         STRSCPY(retval, "*");
 
-    STRSCAT(retval, dictColornames[colorIndex]);
+    STRSCAT(retval, colorDict.name[colorIndex]);
 
     return retval;
 }
@@ -343,13 +427,22 @@ printColors(colorhist_vector const chv,
             int              const colorCt,
             pixval           const maxval,
             enum ColorFmt    const colorFmt,
-            unsigned int     const nKnown,
-            pixel            const knownColors[],
-            const char *     const colornames[]) {
+            bool             const withColorName,
+            ColorDict        const colorDict) {
+/*----------------------------------------------------------------------------
+   Print to Standard Output the list of colors, one per line in 'chv',
+   of which there are 'colorCt'.
+
+   Print the color in format 'colorFmt'.
 
-    int i;
+   If 'withColorName' is true, we add the name of each color to the line.
+   'oclorDict' is a list of known names of colors. If the color is not in the
+   list, we add the name of the color closest to it whose name we know,
+   prefixed by "*".
+-----------------------------------------------------------------------------*/
+    unsigned int i;
 
-    for (i = 0; i < colorCt; 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);
@@ -360,9 +453,8 @@ printColors(colorhist_vector const chv,
 
         const char * colornameValue;
 
-        if (colornames)
-            colornameValue = colornameLabel(chv[i].color, maxval,
-                                            nKnown, knownColors, colornames);
+        if (withColorName)
+            colornameValue = colornameLabel(chv[i].color, maxval, colorDict);
         else
             colornameValue = "";
 
@@ -393,6 +485,47 @@ printColors(colorhist_vector const chv,
 
 
 static void
+printHistogram(colorhist_vector const chv,
+               unsigned int     const colorCt,
+               pixval           const maxval,
+               enum ColorFmt    const colorFmt,
+               bool             const wantHeader,
+               bool             const wantColorName) {
+
+    ColorDict colorDict;
+
+    if (colorFmt == FMT_PPMPLAIN)
+        printf("P3\n# color map\n%d 1\n%d\n", colorCt, maxval);
+
+    if (wantHeader) {
+        const char * commentDelim = colorFmt == FMT_PPMPLAIN ? "#" : " ";
+
+        printColorSummary(colorSummary(chv, colorCt, maxval), commentDelim);
+
+        printf("%s  r     g     b   \t lum \t count  %s\n",
+               commentDelim, wantColorName ? "name" : "");
+        printf("%s----- ----- ----- \t-----\t------- %s\n",
+               commentDelim, wantColorName ? "----" : "");
+    }
+    if (wantColorName) {
+        bool const mustOpenTrue = TRUE;
+        ppm_readcolordict(NULL, mustOpenTrue,
+                          &colorDict.n, &colorDict.name, &colorDict.color,
+                          NULL);
+    }
+
+    printColors(chv, colorCt, maxval,
+                colorFmt, wantColorName, colorDict);
+
+    if (wantColorName) {
+        free(colorDict.color);
+        free(colorDict.name);
+    }
+}
+
+
+
+static void
 summarizeInvalidPixels(unsigned long int const validPixelCt,
                        unsigned long int const invalidPixelCt,
                        pixval            const maxval) {
@@ -430,7 +563,7 @@ printInvalidSamples(colorhist_vector const chv,
     unsigned long int invalidPixelCt;
 
     for (i = 0, validPixelCt = 0; i < validColorCt; ++i)
-        validPixelCt += chv[i].value; 
+        validPixelCt += chv[i].value;
 
     for (i = 0, invalidPixelCt = 0; i < invalidColorCt; ++i) {
         pixval       const r     = PPM_GETR(chvInvalid[i].color);
@@ -438,7 +571,7 @@ printInvalidSamples(colorhist_vector const chv,
         pixval       const b     = PPM_GETB(chvInvalid[i].color);
         unsigned int const count = chvInvalid[i].value;
 
-        invalidPixelCt += chvInvalid[i].value; 
+        invalidPixelCt += chvInvalid[i].value;
 
         switch(colorFmt) {
         case FMT_FLOAT:
@@ -470,7 +603,7 @@ printInvalidSamples(colorhist_vector const chv,
 int
 main(int argc, const char *argv[]) {
 
-    struct cmdline_info cmdline;
+    struct CmdlineInfo cmdline;
     FILE * ifP;
     colorhist_vector chv;
     colorhist_vector chvInvalid;
@@ -479,9 +612,6 @@ main(int argc, const char *argv[]) {
     pixval mmaxval;
     int format;
     int colorCt;
-    unsigned int dictColorCt;
-    const char ** dictColornames;
-    pixel * dictColors;
     unsigned int validColorCt;
 
     pm_proginit(&argc, argv);
@@ -512,38 +642,13 @@ main(int argc, const char *argv[]) {
         validColorCt = colorCt;
     }
 
-    /* And print the histogram. */
-    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 ? '#' : ' ';
-        printf("%c  r     g     b   \t lum \t count  %s\n",
-               commentDelim, cmdline.colorname ? "name" : "");
-        printf("%c----- ----- ----- \t-----\t------- %s\n",
-               commentDelim, cmdline.colorname ? "----" : "");
-    }
-    if (cmdline.colorname) {
-        bool const mustOpenTrue = TRUE;
-        ppm_readcolordict(NULL, mustOpenTrue,
-                          &dictColorCt, &dictColornames, &dictColors, NULL);
-    } else {
-        dictColors = NULL;
-        dictColornames = NULL;
-    }
-
-    printColors(chv, validColorCt, maxval,
-                cmdline.colorFmt, dictColorCt, dictColors, dictColornames);
+    printHistogram(chv, validColorCt, maxval,
+                   cmdline.colorFmt, !cmdline.noheader, cmdline.colorname);
 
     if (colorCt > validColorCt)
         printInvalidSamples(chv, chvInvalid, colorCt, validColorCt,
                             maxval, cmdline.colorFmt);
 
-    if (dictColors)
-        free(dictColors);
-    if (dictColornames)
-        free(dictColornames);
-
     ppm_freecolorhist(chv);
 
     if (chvInvalid)
@@ -553,4 +658,3 @@ main(int argc, const char *argv[]) {
 }
 
 
-