about summary refs log tree commit diff
path: root/editor
diff options
context:
space:
mode:
Diffstat (limited to 'editor')
-rw-r--r--editor/Makefile20
-rw-r--r--editor/pamaddnoise.c2
-rw-r--r--editor/pamaltsat.c535
-rw-r--r--editor/pambrighten.c271
-rw-r--r--editor/pamcomp.c141
-rw-r--r--editor/pamcut.c480
-rw-r--r--editor/pamditherbw.c210
-rw-r--r--editor/pamenlarge.c717
-rw-r--r--editor/pamflip/flip.h2
-rw-r--r--editor/pamflip/pamflip.c223
-rw-r--r--editor/pamflip/pamflip_sse.c44
-rw-r--r--editor/pamflip/pamflip_sse.h2
-rw-r--r--editor/pamhue.c209
-rw-r--r--editor/pamlevels.c515
-rw-r--r--editor/pammixmulti.c543
-rw-r--r--editor/pamperspective.c1
-rw-r--r--editor/pamrecolor.c2
-rw-r--r--editor/pamrubber.c2
-rw-r--r--editor/pamscale.c448
-rwxr-xr-xeditor/pamstretch-gen165
-rw-r--r--editor/pamstretch.c364
-rw-r--r--editor/pamundice.c3
-rw-r--r--editor/pbmclean.c55
-rw-r--r--editor/pbmmask.c394
-rw-r--r--editor/pbmreduce.c514
-rw-r--r--editor/pgmmedian.c3
-rw-r--r--editor/pnmconvol.c178
-rw-r--r--editor/pnmcrop.c787
-rw-r--r--editor/pnmhisteq.c34
-rw-r--r--editor/pnminvert.c4
-rwxr-xr-xeditor/pnmmargin8
-rw-r--r--editor/pnmmontage.c1
-rw-r--r--editor/pnmnorm.c116
-rw-r--r--editor/pnmpad.c3
-rw-r--r--editor/pnmpaste.c32
-rwxr-xr-xeditor/pnmquant39
-rwxr-xr-xeditor/pnmquantall20
-rw-r--r--editor/pnmremap.c254
-rw-r--r--editor/pnmrotate.c2
-rw-r--r--editor/pnmshear.c2
-rw-r--r--editor/pnmstitch.c1
-rw-r--r--editor/ppmbrighten.c274
-rw-r--r--editor/ppmchange.c94
-rw-r--r--editor/ppmcolormask.c188
-rw-r--r--editor/ppmdraw.c69
-rwxr-xr-xeditor/ppmfade16
-rw-r--r--editor/ppmlabel.c2
-rwxr-xr-xeditor/ppmquant13
-rwxr-xr-xeditor/ppmshadow134
-rw-r--r--editor/ppmshadow.doc4
-rw-r--r--editor/specialty/Makefile8
-rw-r--r--editor/specialty/pammixinterlace.c62
-rw-r--r--editor/specialty/pampaintspill.c2
-rw-r--r--editor/specialty/pgmabel.c5
-rw-r--r--editor/specialty/pnmindex.c1
-rw-r--r--editor/specialty/pnmmercator.c2
-rw-r--r--editor/specialty/ppmglobe.c2
-rw-r--r--editor/specialty/ppmntsc.c4
58 files changed, 5917 insertions, 2309 deletions
diff --git a/editor/Makefile b/editor/Makefile
index 39329f00..5b12e4ca 100644
--- a/editor/Makefile
+++ b/editor/Makefile
@@ -16,10 +16,10 @@ SUBDIRS = pamflip specialty
 # This package is so big, it's useful even when some parts won't 
 # build.
 
-PORTBINARIES = pamaddnoise pambackground pamcomp pamcut \
+PORTBINARIES = pamaddnoise pamaltsat pambackground pambrighten pamcomp pamcut \
 	       pamdice pamditherbw pamedge \
 	       pamenlarge \
-	       pamfunc pammasksharpen \
+	       pamfunc pamhue pamlevels pammasksharpen pammixmulti \
 	       pamperspective pamrecolor pamrubber \
 	       pamscale pamsistoaglyph pamstretch pamthreshold pamundice \
 	       pamwipeout \
@@ -51,12 +51,14 @@ OBJECTS = $(BINARIES:%=%.o)
 
 MERGE_OBJECTS = $(MERGEBINARIES:%=%.o2)
 
+HAVE_MERGE_COMPAT=YES
+
 .PHONY: all
 all: $(BINARIES) $(SUBDIRS:%=%/all)
 
 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
@@ -98,3 +100,15 @@ install.bin.local: $(PKGDIR)/bin
 	cd $(PKGDIR)/bin ; \
 	rm -f pnmcomp$(EXE) ; \
 	$(SYMLINK) pamcomp$(EXE) pnmcomp$(EXE)
+
+mergecomptrylist:
+	cat /dev/null >$@
+	echo "TRY(\"pnminterp\",  main_pamstretch);" >>$@
+	echo "TRY(\"pgmnorm\",    main_pnmnorm);"    >>$@
+	echo "TRY(\"ppmnorm\",    main_pnmnorm);"    >>$@
+	echo "TRY(\"pgmedge\",    main_pamedge);"    >>$@
+	echo "TRY(\"pnmenlarge\", main_pamenlarge);" >>$@
+	echo "TRY(\"pnmcut\",     main_pamcut);"     >>$@
+	echo "TRY(\"pnmscale\",   main_pamscale);"   >>$@
+	echo "TRY(\"pnmcomp\",    main_pamcomp);"    >>$@
+
diff --git a/editor/pamaddnoise.c b/editor/pamaddnoise.c
index a88a5b93..ccfde0b6 100644
--- a/editor/pamaddnoise.c
+++ b/editor/pamaddnoise.c
@@ -26,7 +26,7 @@
 ** Prentice Hall, 1993  ISBN 0-13-145814-0
 */
 
-#define _XOPEN_SOURCE   /* get M_PI in math.h */
+#define _XOPEN_SOURCE 500  /* get M_PI in math.h */
 
 #include <math.h>
 
diff --git a/editor/pamaltsat.c b/editor/pamaltsat.c
new file mode 100644
index 00000000..6d9b91e0
--- /dev/null
+++ b/editor/pamaltsat.c
@@ -0,0 +1,535 @@
+#include <stdbool.h>
+#include <assert.h>
+#include <string.h>
+
+#include <pam.h>
+#include <pm_gamma.h>
+#include <nstring.h>
+
+#include "shhopt.h"
+#include "mallocvar.h"
+
+typedef unsigned int  uint;
+typedef unsigned char uchar;
+
+typedef enum {MLog,  MSpectrum } Method; /* method identifiers */
+
+typedef struct {
+    Method       method;
+    const char * name;
+} MethodTableEntry;
+
+MethodTableEntry methodTable[] = {
+    {MLog,      "log"},
+    {MSpectrum, "spectrum"}
+};
+
+/* Command-line arguments parsed: */
+typedef struct {
+    const char * inputFileName;
+        /* name of the input file. "-" for stdin */
+    float        strength;
+    uint         linear;
+    Method       method;
+} CmdlineInfo;
+
+
+
+
+static Method
+methodFmNm(const char * const methodNm) {
+/*----------------------------------------------------------------------------
+   The method of saturation whose name is 'methodNm'
+-----------------------------------------------------------------------------*/
+    uint  i;
+    bool found;
+    Method method;
+
+    for (i = 0, found = false; i < ARRAY_SIZE(methodTable) && !found; ++i) {
+        if (streq(methodNm, methodTable[i].name)) {
+            found = true;
+            method = methodTable[i].method;
+        }
+    }
+
+    if (!found) {
+        /* Issue error message and abort */
+        char * methodList;
+        uint   methodListLen;
+        uint   i;
+
+        /* Allocate a buffer to store the list of known saturation methods: */
+        for (i = 0, methodListLen = 0; i < ARRAY_SIZE(methodTable); ++i)
+            methodListLen += strlen(methodTable[i].name) + 2;
+
+        MALLOCARRAY(methodList, methodListLen);
+
+        if (!methodList)
+            pm_error("Failed to allocate memory for %lu saturation "
+                     "method names", (unsigned long)ARRAY_SIZE(methodTable));
+
+        /* Fill the list of methods: */
+        for (i = 0, methodList[0] = '\0'; i < ARRAY_SIZE(methodTable); ++i) {
+            if (i > 0)
+                strcat(methodList, ", ");
+            strcat(methodList, methodTable[i].name);
+        }
+
+        pm_error("Unknown saturation method: '%s'. Known methods are: %s",
+                 methodNm, methodList);
+
+        free(methodList);
+    }
+    return method;
+}
+
+
+
+static CmdlineInfo
+parsedCommandLine(int argc, const char ** argv) {
+
+    CmdlineInfo cmdline;
+    optStruct3 opt;
+
+    uint option_def_index;
+    uint methodSpec, strengthSpec, linearSpec;
+    const char * method;
+
+    optEntry * option_def;
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "method",   OPT_STRING, &method,            &methodSpec,   0);
+    OPTENT3(0, "strength", OPT_FLOAT,  &cmdline.strength,  &strengthSpec, 0);
+    OPTENT3(0, "linear",   OPT_FLAG,   &cmdline.linear,    &linearSpec,   0);
+
+    opt.opt_table     = option_def;
+    opt.short_allowed = 0;
+    opt.allowNegNum   = 0;
+
+    pm_optParseOptions3( &argc, (char **)argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (methodSpec)
+        cmdline.method = methodFmNm(method);
+    else
+        cmdline.method = MSpectrum;
+
+    if (!strengthSpec)
+        pm_error("You must specify -strength");
+
+    if (!linearSpec)
+        cmdline.linear = 0;
+
+    if (argc-1 < 1)
+        cmdline.inputFileName = "-";
+    else {
+        cmdline.inputFileName = argv[1];
+        if (argc-1 > 1)
+            pm_error("Program takes at most one argument:  file name");
+    }
+
+    free(option_def);
+
+    return cmdline;
+}
+
+
+
+typedef struct {
+    double _[3];
+} TupleD;
+
+typedef struct {
+/*----------------------------------------------------------------------------
+  Information about a color sample in linear format
+-----------------------------------------------------------------------------*/
+    TupleD sample;    /* layer intensities                        */
+    double maxval;    /* the highest layer intensity              */
+    uint   maxl;      /* index of that layer                      */
+    uint   minl;      /* index of the layer with lowest intensity */
+    double intensity; /* total sample intensity                   */
+} LinSampleInfo;
+
+/* ---------------------------- Binary search ------------------------------ */
+/*                    ( a minimal drop-in implementation )                   */
+
+/* Function to search, where <data> is an arbitrary user-supplied parameter */
+typedef double (binsearchFunc)(double       const x,
+                               const void * const data);
+
+/* The binary-search function. Returns such <x> from [<min>, <max>] that
+   monotonically increasing function func(x, data) equals <value> within
+   precision <prec>. <dataP> is an arbitary parameter to <func>. */
+static double
+binsearch(binsearchFunc       func,
+          const void  * const dataP,
+          double        const prec,
+          double        const minArg,
+          double        const maxArg,
+          double        const value
+         ) {
+    double x;
+    double min, max;
+    bool found;
+
+    for (min = minArg, max = maxArg, found = false; !found;) {
+
+        x = (min + max) / 2;
+        {
+            double const f = func(x, dataP);
+
+            if ((fabs(f - value)) < prec)
+                found = true;
+            else {
+                assert(f != value);
+
+                if (f > value) max = x;
+                else           min = x;
+            }
+        }
+    }
+    return x;
+}
+
+/* ------------- Utilities not specific to saturation methods -------------- */
+
+/* Y chromaticities in Rec.709: R       G       B  */
+static double const yCoeffs[3]  = {0.3333, 0.6061, 0.0606};
+
+static void
+applyRatio(TupleD * const tupP,
+           double   const ratio) {
+/*----------------------------------------------------------------------------
+  Multiply the components of tuple *tupP by coefficient 'ratio'.
+-----------------------------------------------------------------------------*/
+    uint c;
+
+    for (c = 0; c < 3; ++c)
+        tupP->_[c] = tupP->_[c] * ratio;
+}
+
+
+
+static void
+getTupInfo(tuplen          const tup,
+           bool            const linear,
+           LinSampleInfo * const siP) {
+/*----------------------------------------------------------------------------
+  Convert PBM tuple <tup> into linear form with double precision siP->sample
+  and obtain also additional information required for further processing.
+  Return the result as *siP.
+-----------------------------------------------------------------------------*/
+    uint i;
+    double minval;
+
+    minval         = 1.1;
+    siP->intensity = 0;
+    siP->maxval    = 0.0;
+    siP->maxl      = 0;
+
+    for (i = 0; i < 3; ++i) {
+        double linval;
+        if (!linear)
+            linval = pm_ungamma709(tup[i]);
+        else
+            linval = tup[i];
+
+        siP->sample._[i] = linval;
+
+        if (linval > siP->maxval) {
+            siP->maxval = linval;
+            siP->maxl   = i;
+        }
+        if (linval < minval) {
+           siP->minl = i;
+           minval    = linval;
+        }
+        siP->intensity += linval * yCoeffs[i];
+    }
+}
+
+/* ------------------------ Logarithmic saturation ------------------------- */
+
+/* Method and algorithm by Anton Shepelev.  */
+
+static void
+tryLogSat(double          const sat,
+          LinSampleInfo * const siP,
+          TupleD *        const tupsatP,
+          double *        const intRatioP,
+          double *        const highestP) {
+/*----------------------------------------------------------------------------
+  Try to increase the saturation of siP->sample by a factor 'sat' and return
+  the result as *tupsatP.
+
+  Also return as *intRatioP the ratio of intensities of 'tupin' and
+  siP->sample.
+
+  Return as *highestP the highest component the saturated color would have if
+  normalized to intensity siP->intensity.
+
+  If the return value exceeds unity saturation cannot be properly increased by
+  the required factor.
+-----------------------------------------------------------------------------*/
+    uint   c;
+    double intSat;
+
+    for (c = 0, intSat = 0.0; c < 3; ++c) {
+        tupsatP->_[c] = pow(siP->sample._[c], sat);
+        intSat       = intSat + tupsatP->_[c] * yCoeffs[c];
+    }
+
+    {
+        double const intRatio = siP->intensity / intSat;
+
+        double const maxComp = tupsatP->_[siP->maxl] * intRatio;
+
+        *intRatioP = intRatio;
+        *highestP = maxComp;
+    }
+}
+
+
+
+/* Structure for the binary search of maximum saturation: */
+typedef struct {
+    LinSampleInfo * siP;
+        /* original color with precalculated information  */
+    TupleD *        tupsatP;
+        /* saturated color                            */
+    double *        intRatioP;
+        /* ratio of orignal and saturated intensities */
+} MaxLogSatInfo;
+
+
+
+static binsearchFunc binsearchMaxLogSat;
+
+static double
+binsearchMaxLogSat(double       const x,
+                   const void * const dataP) {
+/*----------------------------------------------------------------------------
+  Target function for the generic binary search routine, for the finding
+  of the maximum possible saturation of a given color. 'dataP' shall point
+  to a MaxSatInfo structure.
+-----------------------------------------------------------------------------*/
+    const MaxLogSatInfo * const infoP = dataP;
+
+    double highest;
+
+    tryLogSat(x, infoP->siP, infoP->tupsatP, infoP->intRatioP, &highest);
+
+    return highest;
+}
+
+
+
+static void
+getMaxLogSat(LinSampleInfo * const siP,
+             TupleD        * const tupsatP,
+             double        * const intRatioP,
+             double          const upperLimit
+          ) {
+/*  Saturates the color <siP->sample> as much as possible and stores the result
+    in <tupsatP>, which must be multiplied by <*intRatioP> in order to restore
+    the intensity of the original color. The range of saturation search is
+    [1.0..<upperlimit>]. */
+    const double PREC = 0.00001; /* precision of binary search */
+
+    MaxLogSatInfo info;
+
+    info.siP       = siP;
+    info.tupsatP   = tupsatP;
+    info.intRatioP = intRatioP;
+
+/*  Discarding return value (maximum saturation) because upon completion of
+    binsearch() info.tupsatP will contain the saturated color. The target value
+    of maximum channel intensity is decreased by PREC in order to avoid
+    overlow. */
+    binsearch(binsearchMaxLogSat, &info, PREC, 1.0, upperLimit, 1.0 - PREC);
+}
+
+
+
+static void
+saturateLog(LinSampleInfo* const siP,
+            double         const sat,
+            TupleD*        const tupsatP) {
+/*----------------------------------------------------------------------------
+  Saturate linear tuple *siP using the logarithmic saturation method.
+-----------------------------------------------------------------------------*/
+    double intRatio;
+        /* ratio of original and saturated intensities */
+    double maxlValSat;
+        /* maximum component intensity in the saturated sample */
+
+    tryLogSat(sat, siP, tupsatP, &intRatio, &maxlValSat);
+
+    /* if we cannot saturate siP->sample by 'sat', use the maximum possible
+       saturation
+    */
+    if (maxlValSat > 1.0)
+        getMaxLogSat(siP, tupsatP, &intRatio, sat);
+
+    /* restore the original intensity: */
+    applyRatio(tupsatP, intRatio);
+}
+
+
+
+/* ------------------------- Spectrum saturation --------------------------- */
+
+/* Method and algorithm by Anton Shepelev.  */
+
+static void
+saturateSpectrum(LinSampleInfo * const siP,
+                 double          const sat,
+                 TupleD *        const tupsatP) {
+/*----------------------------------------------------------------------------
+  Saturate linear tuple *siP using the Spectrum saturation method.
+-----------------------------------------------------------------------------*/
+    double k;
+    double * sample;
+
+    sample = siP->sample._; /* short-cut to the input sample data */
+
+    if (sample[siP->minl] == sample[siP->maxl])
+        k = 1.0; /* Cannot saturate a neutral sample */
+    else {
+        double const km1 =
+            (1.0 - siP->intensity)/(siP->maxval - siP->intensity);
+            /* Maximum saturation factor that keeps maximum layer intesity
+               within range
+            */
+        double const km2 = siP->intensity/(siP->intensity - sample[siP->minl]);
+            /* Maximum saturation factor  that keeps minimum layer intesity
+               within range
+            */
+
+        /* To satisfy both constraints, choose the strictest: */
+        double const km = km1 > km2 ? km2 : km1;
+
+        /* Ensure the saturation factor does not exceed the maximum
+           possible value:
+        */
+        k = sat < km ? sat : km;
+    }
+
+    {
+        /* Initialize the resulting sample with the input value */
+        uint i;
+        for (i = 0; i < 3; ++i)
+            tupsatP->_[i] = sample[i];
+    }
+
+    applyRatio(tupsatP, k); /* apply the saturation factor */
+
+    {
+         /* restore the original intensity */
+        uint i;
+        for (i = 0; i < 3; ++i)
+            tupsatP->_[i] = tupsatP->_[i] - siP->intensity * (k - 1.0);
+    }
+}
+
+
+
+/* --------------------- General saturation algorithm ---------------------- */
+
+static void
+saturateTup(Method const method,
+            double const sat,
+            bool   const linear,
+            tuplen const tup) {
+/*----------------------------------------------------------------------------
+  Saturate black and white tuple 'tup'
+-----------------------------------------------------------------------------*/
+    LinSampleInfo si;
+
+    getTupInfo(tup, linear, &si);
+
+    if (sat < 1.0 ||  /* saturation can always be decresed */
+        si.maxval < 1.0 ) { /* there is room for increase        */
+
+        TupleD tupsat;
+
+        /* Dispatch saturation methods:
+           (There seems too little benefit in using a table of
+           function pointers, so a manual switch should suffice)
+        */
+        switch (method) {
+            case MLog:      saturateLog     (&si, sat, &tupsat); break;
+            case MSpectrum: saturateSpectrum(&si, sat, &tupsat); break;
+        }
+
+        /* Put the processed tuple back in the tuple row, gamma-adjusting it
+           if required.
+        */
+        {
+            uint i;
+
+            for (i = 0; i < 3; ++i)
+                tup[i] = linear ? tupsat._[i] : pm_gamma709(tupsat._[i]);
+        }
+    }
+}
+
+
+
+static void
+pamaltsat(CmdlineInfo const cmdline,
+          FILE *      const ofP) {
+
+    struct pam inPam, outPam;
+    tuplen *   tuplerown;
+    FILE *     ifP;
+    uint       row;
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    pnm_readpaminit(ifP, &inPam, PAM_STRUCT_SIZE(tuple_type));
+
+    outPam = inPam;
+    outPam.file = ofP;
+
+    tuplerown = pnm_allocpamrown(&inPam);
+
+    pnm_writepaminit(&outPam);
+
+    for (row = 0; row < inPam.height; ++row) {
+        pnm_readpamrown(&inPam, tuplerown);
+
+        if (inPam.depth >= 3) {
+            uint col;
+
+            for (col = 0; col < inPam.width; ++col)
+                saturateTup(cmdline.method, cmdline.strength, cmdline.linear,
+                            tuplerown[col]);
+        }
+
+        pnm_writepamrown(&outPam, tuplerown);
+    }
+
+    pnm_freepamrown(tuplerown);
+    pm_close(ifP);
+}
+
+
+
+int
+main(int argc, const char ** argv) {
+
+    CmdlineInfo cmdline;
+
+    pm_proginit(&argc, argv);
+
+    cmdline = parsedCommandLine(argc, argv);
+
+    pamaltsat(cmdline, stdout);
+
+    return 0;
+}
+
+
+
diff --git a/editor/pambrighten.c b/editor/pambrighten.c
new file mode 100644
index 00000000..51bd0d23
--- /dev/null
+++ b/editor/pambrighten.c
@@ -0,0 +1,271 @@
+/*=============================================================================
+                                  pambrighten
+===============================================================================
+  Change Value and Saturation of Netpbm image.
+=============================================================================*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <math.h>
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "shhopt.h"
+#include "pam.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;  /* '-' if stdin */
+    float        valchange;
+    float        satchange;
+};
+
+
+
+static void
+parseCommandLine(int                        argc,
+                 const char **              argv,
+                 struct CmdlineInfo * const cmdlineP ) {
+/*----------------------------------------------------------------------------
+   Parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to pm_optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int valueSpec;
+    int          valueOpt;
+    unsigned int saturationSpec;
+    int          saturationOpt;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "value",       OPT_INT,    &valueOpt,
+            &valueSpec,           0 );
+    OPTENT3(0, "saturation",  OPT_INT,    &saturationOpt,
+            &saturationSpec,      0 );
+
+    opt.opt_table = option_def;
+    opt.short_allowed = false;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = false;    /* No negative arguments */
+
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (valueSpec) {
+        if (valueOpt < -100)
+            pm_error("Value reduction cannot be more than 100%%.  "
+                     "You specified %d", valueOpt);
+        else
+            cmdlineP->valchange = 1.0 + (float)valueOpt / 100;
+    } else
+        cmdlineP->valchange = 1.0;
+
+    if (saturationSpec) {
+        if (saturationOpt < -100)
+            pm_error("Saturation reduction cannot be more than 100%%.  "
+                     "You specified %d", saturationOpt);
+        else
+            cmdlineP->satchange = 1.0 + (float)saturationOpt / 100;
+    } else
+        cmdlineP->satchange = 1.0;
+
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFileName = argv[1];
+    else
+        pm_error("Program takes at most one argument:  file specification");
+}
+
+
+
+static void
+changeColorPix(tuple              const tupleval,
+               float              const valchange,
+               float              const satchange,
+               const struct pam * const pamP) {
+
+    pixel oldRgb, newRgb;
+    struct hsv oldHsv, newHsv;
+
+    PPM_PUTR(oldRgb, tupleval[PAM_RED_PLANE]);
+    PPM_PUTG(oldRgb, tupleval[PAM_GRN_PLANE]);
+    PPM_PUTB(oldRgb, tupleval[PAM_BLU_PLANE]);
+    oldHsv = ppm_hsv_from_color(oldRgb, pamP->maxval);
+
+    newHsv.h = oldHsv.h;
+
+    newHsv.s = MIN(1.0, MAX(0.0, oldHsv.s * satchange));
+
+    newHsv.v = MIN(1.0, MAX(0.0, oldHsv.v * valchange));
+
+    newRgb = ppm_color_from_hsv(newHsv, pamP->maxval);
+
+    tupleval[PAM_RED_PLANE] = PPM_GETR(newRgb);
+    tupleval[PAM_GRN_PLANE] = PPM_GETG(newRgb);
+    tupleval[PAM_BLU_PLANE] = PPM_GETB(newRgb);
+}
+
+
+
+static void
+changeGrayPix(tuple        const tupleval,
+              float        const valchange,
+              struct pam * const pamP) {
+
+    samplen const oldGray = pnm_normalized_sample(pamP, tupleval[0]);
+    samplen const newGray = MIN(1.0, MAX(0.0, oldGray * valchange));
+
+    tupleval[0] = pnm_unnormalized_sample(pamP, newGray);
+}
+
+
+
+typedef enum {COLORTYPE_COLOR, COLORTYPE_GRAY, COLORTYPE_BW} ColorType;
+
+
+
+static ColorType
+colorTypeOfImage(struct pam * const pamP) {
+/*----------------------------------------------------------------------------
+   The basic type of color represented in the image described by *pamP: full
+   color, grayscale, or black and white
+
+   Note that we're talking about the format of the image, not the reality of
+   the pixels.  A color image is still a color image even if all the colors in
+   it happen to be gray.
+
+   For a PAM image, as is customary in Netpbm, we do not consider the tuple
+   type, but rather infer the color type from the depth and maxval.  This
+   gives us more flexibility for future tuple types.
+-----------------------------------------------------------------------------*/
+    ColorType retval;
+
+    if (pamP->format == PPM_FORMAT ||
+        pamP->format == RPPM_FORMAT ||
+        (pamP->format == PAM_FORMAT && pamP->depth >= 3)) {
+
+        retval = COLORTYPE_COLOR;
+
+    } else if (pamP->format == PGM_FORMAT ||
+               pamP->format == RPGM_FORMAT ||
+               (pamP->format == PAM_FORMAT &&
+                pamP->depth >= 1 &&
+                pamP->maxval > 1)) {
+
+        retval = COLORTYPE_GRAY;
+
+    } else {
+
+        retval = COLORTYPE_BW;
+
+    }
+    return retval;
+}
+
+
+
+static void
+pambrighten(struct CmdlineInfo const cmdline,
+            FILE *             const ifP) {
+
+    struct pam inpam, outpam;
+    tuple * tuplerow;
+    ColorType colorType;
+    unsigned int row;
+
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    colorType = colorTypeOfImage(&inpam);
+
+    outpam = inpam;
+    outpam.file = stdout;
+
+    pnm_writepaminit(&outpam);
+
+    tuplerow = pnm_allocpamrow(&inpam);
+
+    for (row = 0; row < inpam.height; ++row) {
+        unsigned int col;
+
+        pnm_readpamrow(&inpam, tuplerow);
+
+        for (col = 0; col < inpam.width; ++col)  {
+            switch (colorType) {
+            case COLORTYPE_COLOR:
+                changeColorPix(tuplerow[col],
+                               cmdline.valchange, cmdline.satchange,
+                               &inpam);
+                break;
+            case COLORTYPE_GRAY:
+                changeGrayPix(tuplerow[col],
+                              cmdline.valchange,
+                              &inpam);
+                break;
+            case COLORTYPE_BW:
+                /* Nothing to change. */
+                break;
+            }
+        }
+        pnm_writepamrow(&outpam, tuplerow);
+    }
+    pnm_freepamrow(tuplerow);
+}
+
+
+
+int
+main(int argc, const char *argv[]) {
+
+    struct CmdlineInfo cmdline;
+    FILE * ifP;
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    pambrighten(cmdline, ifP);
+
+    pm_close(ifP);
+    pm_close(stdout);
+
+    return 0;
+}
+
+
+
+/*
+   This was derived from ppmbrighten code written by Jef Poskanzer and
+   Brian Moffet. Updated by Willem van Schaik to support PAM.
+
+   Copyright (C) 1989 by Jef Poskanzer.
+   Copyright (C) 1990 by Brian Moffet.
+   Copyright (C) 2019 by Willem van Schaik (willem@schaik.com)
+
+   Permission to use, copy, modify, and distribute this software and its
+   documentation for any purpose and without fee is hereby granted, provided
+   that the above copyright notice appear in all copies and that both that
+   copyright notice and this permission notice appear in supporting
+   documentation.  This software is provided "as is" without express or
+   implied warranty.
+
+   Bryan Henderson contributes his work to the public domain.
+*/
diff --git a/editor/pamcomp.c b/editor/pamcomp.c
index b76eb8c7..6e1e7f7d 100644
--- a/editor/pamcomp.c
+++ b/editor/pamcomp.c
@@ -1,18 +1,18 @@
-/*----------------------------------------------------------------------------
+/*=============================================================================
                               pamcomp
------------------------------------------------------------------------------
+===============================================================================
    This program composes two images together, with optional translucence.
 
    This program is derived from (and replaces) Pnmcomp, whose origin is
    as follows:
 
-       Copyright 1992, David Koblas.                                    
-         Permission to use, copy, modify, and distribute this software  
+       Copyright 1992, David Koblas.
+         Permission to use, copy, modify, and distribute this software
          and its documentation for any purpose and without fee is hereby
          granted, provided that the above copyright notice appear in all
-         copies and that both that copyright notice and this permission 
-         notice appear in supporting documentation.  This software is   
-         provided "as is" without express or implied warranty.          
+         copies and that both that copyright notice and this permission
+         notice appear in supporting documentation.  This software is
+         provided "as is" without express or implied warranty.
 
    No code from the original remains in the present version.  The
    January 2004 version was coded entirely by Bryan Henderson.
@@ -20,9 +20,9 @@
 
    The current version is derived from the January 2004 version, with
    additional work by multiple authors.
------------------------------------------------------------------------------*/
-
-#define _BSD_SOURCE    /* Make sure strcaseceq() is in nstring.h */
+=============================================================================*/
+#define _DEFAULT_SOURCE /* New name for SVID & BSD source defines */
+#define _BSD_SOURCE    /* Make sure strcaseeq() is in nstring.h */
 #include <assert.h>
 #include <string.h>
 #include <math.h>
@@ -40,11 +40,11 @@ enum vertPos {ABOVE, TOP, MIDDLE, BOTTOM, BELOW};
 
 enum sampleScale {INTENSITY_SAMPLE, GAMMA_SAMPLE};
     /* This indicates a scale for a PAM sample value.  INTENSITY_SAMPLE means
-       the value is proportional to light intensity; GAMMA_SAMPLE means the 
+       the value is proportional to light intensity; GAMMA_SAMPLE means the
        value is gamma-adjusted as defined in the PGM/PPM spec.  In both
        scales, the values are continuous and normalized to the range 0..1.
-       
-       This scale has no meaning if the PAM is not a visual image.  
+
+       This scale has no meaning if the PAM is not a visual image.
     */
 
 enum alphaMix {AM_KEEPUNDER, AM_OVERLAY};
@@ -64,7 +64,7 @@ enum alphaMix {AM_KEEPUNDER, AM_OVERLAY};
        its contribution to the composition.
     */
 
-struct cmdlineInfo {
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
@@ -84,12 +84,12 @@ struct cmdlineInfo {
 
 
 static void
-parseCommandLine(int                        argc, 
+parseCommandLine(int                        argc,
                  const char **              argv,
-                 struct cmdlineInfo * const cmdlineP ) {
+                 struct CmdlineInfo * const cmdlineP ) {
 /*----------------------------------------------------------------------------
    Parse program command line described in Unix standard form by argc
-   and argv.  Return the information in the options as *cmdlineP.  
+   and argv.  Return the information in the options as *cmdlineP.
 
    If command line is internally inconsistent (invalid options, etc.),
    issue error message to stderr and abort program.
@@ -111,11 +111,11 @@ parseCommandLine(int                        argc,
     MALLOCARRAY_NOFAIL(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENT3 */
-    OPTENT3(0, "invert",             OPT_FLAG,   NULL,                  
+    OPTENT3(0, "invert",             OPT_FLAG,   NULL,
             &cmdlineP->alphaInvert,       0);
-    OPTENT3(0, "xoff",               OPT_INT,    &cmdlineP->xoff,       
+    OPTENT3(0, "xoff",               OPT_INT,    &cmdlineP->xoff,
             &xoffSpec,                    0);
-    OPTENT3(0, "yoff",               OPT_INT,    &cmdlineP->yoff,       
+    OPTENT3(0, "yoff",               OPT_INT,    &cmdlineP->yoff,
             &yoffSpec,                    0);
     OPTENT3(0, "opacity",            OPT_FLOAT,  &cmdlineP->opacity,
             &opacitySpec,                 0);
@@ -125,9 +125,9 @@ parseCommandLine(int                        argc,
             &alignSpec,                   0);
     OPTENT3(0, "valign",             OPT_STRING, &valign,
             &valignSpec,                  0);
-    OPTENT3(0, "linear",             OPT_FLAG,   NULL,       
+    OPTENT3(0, "linear",             OPT_FLAG,   NULL,
             &cmdlineP->linear,            0);
-    OPTENT3(0, "mixtransparency",    OPT_FLAG,   NULL,       
+    OPTENT3(0, "mixtransparency",    OPT_FLAG,   NULL,
             &cmdlineP->mixtransparency,   0);
 
     opt.opt_table = option_def;
@@ -158,9 +158,9 @@ parseCommandLine(int                        argc,
             cmdlineP->align = BEYONDRIGHT;
         else
             pm_error("Invalid value for align option: '%s'.  Only LEFT, "
-                     "RIGHT, CENTER, BEYONDLEFT, and BEYONDRIGHT are valid.", 
+                     "RIGHT, CENTER, BEYONDLEFT, and BEYONDRIGHT are valid.",
                      align);
-    } else 
+    } else
         cmdlineP->align = LEFT;
 
     if (valignSpec) {
@@ -176,12 +176,12 @@ parseCommandLine(int                        argc,
             cmdlineP->valign = BELOW;
         else
             pm_error("Invalid value for valign option: '%s'.  Only TOP, "
-                     "BOTTOM, MIDDLE, ABOVE, and BELOW are valid.", 
+                     "BOTTOM, MIDDLE, ABOVE, and BELOW are valid.",
                      align);
-    } else 
+    } else
         cmdlineP->valign = TOP;
 
-    if (!opacitySpec) 
+    if (!opacitySpec)
         cmdlineP->opacity = 1.0;
 
     if (argc-1 < 1)
@@ -219,7 +219,7 @@ commonFormat(int const formatA,
 
     int const typeA = PAM_FORMAT_TYPE(formatA);
     int const typeB = PAM_FORMAT_TYPE(formatB);
-    
+
     if (typeA == PAM_TYPE || typeB == PAM_TYPE)
         retval = PAM_FORMAT;
     else if (typeA == PPM_TYPE || typeB == PPM_TYPE)
@@ -242,7 +242,7 @@ typedef enum { TT_BLACKANDWHITE, TT_GRAYSCALE, TT_RGB } BaseTupletype;
 
 
 static BaseTupletype
-commonTupletype(const char * const tupletypeA, 
+commonTupletype(const char * const tupletypeA,
                 const char * const tupletypeB) {
 
     if (strneq(tupletypeA, "RGB", 3) ||
@@ -300,11 +300,11 @@ determineOutputType(const struct pam * const underlayPamP,
     composedPamP->height = underlayPamP->height;
     composedPamP->width  = underlayPamP->width;
 
-    composedPamP->format = commonFormat(underlayPamP->format, 
+    composedPamP->format = commonFormat(underlayPamP->format,
                                         overlayPamP->format);
     composedPamP->plainformat = FALSE;
 
-    composedPamP->maxval = pm_lcm(underlayPamP->maxval, overlayPamP->maxval, 
+    composedPamP->maxval = pm_lcm(underlayPamP->maxval, overlayPamP->maxval,
                                   1, PNM_OVERALLMAXVAL);
 
     composedPamP->visual = true;
@@ -325,7 +325,7 @@ determineOutputType(const struct pam * const underlayPamP,
 
 static void
 warnOutOfFrame(int const originLeft,
-               int const originTop, 
+               int const originTop,
                int const overCols,
                int const overRows,
                int const underCols,
@@ -362,7 +362,7 @@ warnOutOfFrame(int const originLeft,
 
 
 static void
-validateComputableHeight(int const originTop, 
+validateComputableHeight(int const originTop,
                          int const overRows) {
 
     if (originTop < 0) {
@@ -381,11 +381,11 @@ validateComputableHeight(int const originTop,
 
 
 static void
-computeOverlayPosition(int                const underCols, 
+computeOverlayPosition(int                const underCols,
                        int                const underRows,
-                       int                const overCols, 
+                       int                const overCols,
                        int                const overRows,
-                       struct cmdlineInfo const cmdline, 
+                       struct CmdlineInfo const cmdline,
                        int *              const originLeftP,
                        int *              const originTopP) {
 /*----------------------------------------------------------------------------
@@ -421,8 +421,8 @@ computeOverlayPosition(int                const underCols,
 
     validateComputableHeight(*originTopP, overRows);
 
-    warnOutOfFrame(*originLeftP, *originTopP, 
-                   overCols, overRows, underCols, underRows);    
+    warnOutOfFrame(*originLeftP, *originTopP,
+                   overCols, overRows, underCols, underRows);
 }
 
 
@@ -456,7 +456,7 @@ computeOverlayPosition(int                const underCols,
 
    The transparency of each slide is the fraction of light that gets
    through that slide, so the transparency of the composed slide is the
-   product of the underlay and overlay transparencies.
+   product of the underlay and overlay transparencies:
 
        C_T = U_T * O_T
 
@@ -484,7 +484,7 @@ computeOverlayPosition(int                const underCols,
 
 
 static sample
-composeComponents(sample           const compA, 
+composeComponents(sample           const compA,
                   sample           const compB,
                   float            const distrib,
                   float            const bFactor,
@@ -508,7 +508,7 @@ composeComponents(sample           const compA,
   useful.
 
   The inputs and result are based on a maxval of 'maxval'.
-  
+
   Note that while 'distrib' in the straightforward case is always in
   [0,1], it can in fact be negative or greater than 1.  We clip the
   result as required to return a legal sample value.
@@ -520,7 +520,7 @@ composeComponents(sample           const compA,
         retval = compA;
     else {
         if (sampleScale == INTENSITY_SAMPLE) {
-            sample const mix = 
+            sample const mix =
                 ROUNDU(compA * distrib + compB * bFactor *(1.0 - distrib));
             retval = MIN(maxval, MAX(0, mix));
         } else {
@@ -529,10 +529,11 @@ composeComponents(sample           const compA,
             float const compALinear = pm_ungamma709(compANormalized);
             float const compBLinear = pm_ungamma709(compBNormalized);
             float const compBLinearAdj = compBLinear * bFactor;
-            float const mix = 
+            float const mix =
                 compALinear * distrib + compBLinearAdj * (1.0 - distrib)
                 * composedFactor;
-            sample const sampleValue = ROUNDU(pm_gamma709(mix) * maxval);
+            sample const sampleValue =
+                pnm_unnormalize(pm_gamma709(mix), maxval);
             retval = MIN(maxval, MAX(0, sampleValue));
         }
     }
@@ -624,13 +625,13 @@ overlayPixel(tuple            const overlayTuple,
         /* Part of formula for AM_OVERLAY -- see explanation above */
 
     overlayWeight = masterOpacity;  /* initial value */
-    
+
     if (overlayPamP->have_opacity)
         overlayWeight *= (float)
             overlayTuple[overlayPamP->opacity_plane] / overlayPamP->maxval;
-    
+
     if (alphaTuplen) {
-        float const alphaval = 
+        float const alphaval =
             invertAlpha ? (1.0 - alphaTuplen[0]) : alphaTuplen[0];
         overlayWeight *= alphaval;
     }
@@ -645,7 +646,7 @@ overlayPixel(tuple            const overlayTuple,
         float  const uOpacityN = uOpacity / uMaxval;
         float  const oOpacityN = oOpacity / oMaxval;
         float  const composedTrans = (1.0 - uOpacityN) * (1.0 * oOpacityN);
-        
+
         if (composedTrans > .999) {
             underlayWeight = 1.0;
             composedWeight = 1.0;
@@ -659,10 +660,10 @@ overlayPixel(tuple            const overlayTuple,
     }
     {
         unsigned int plane;
-        
+
         for (plane = 0; plane < composedPamP->color_depth; ++plane)
-            composedTuple[plane] = 
-                composeComponents(overlayTuple[plane], underlayTuple[plane], 
+            composedTuple[plane] =
+                composeComponents(overlayTuple[plane], underlayTuple[plane],
                                   overlayWeight, underlayWeight,
                                   composedWeight,
                                   composedPamP->maxval,
@@ -707,7 +708,7 @@ adaptRowFormat(struct pam * const inpamP,
 
 
 static void
-composeRow(int              const originleft, 
+composeRow(int              const originleft,
            struct pam *     const underlayPamP,
            struct pam *     const overlayPamP,
            bool             const invertAlpha,
@@ -732,7 +733,7 @@ composeRow(int              const originleft,
         int const ovlcol = col - originleft;
 
         if (ovlcol >= 0 && ovlcol < overlayPamP->width) {
-            tuplen const alphaTuplen = 
+            tuplen const alphaTuplen =
                 alphaTuplerown ? alphaTuplerown[ovlcol] : NULL;
 
             overlayPixel(overlayTuplerow[ovlcol], overlayPamP,
@@ -799,8 +800,8 @@ determineInputAdaptations(const struct pam * const underlayPamP,
 
 
 static void
-composite(int          const originleft, 
-          int          const origintop, 
+composite(int          const originleft,
+          int          const origintop,
           struct pam * const underlayPamP,
           struct pam * const overlayPamP,
           struct pam * const alphaPamP,
@@ -829,7 +830,7 @@ composite(int          const originleft,
    We assume that the span from the topmost row of the two images to
    the bottommost row is less than INT_MAX.
 -----------------------------------------------------------------------------*/
-    enum sampleScale const sampleScale = 
+    enum sampleScale const sampleScale =
         assumeLinear ? INTENSITY_SAMPLE : GAMMA_SAMPLE;
     enum alphaMix const alphaMix =
         mixTransparency ? AM_OVERLAY : AM_KEEPUNDER;
@@ -859,7 +860,7 @@ composite(int          const originleft,
     assert(INT_MAX - overlayPamP->height > origintop); /* arg constraint */
 
     for (underlayRow = MIN(0, origintop), overlayRow = MIN(0, -origintop);
-         underlayRow < MAX(underlayPamP->height, 
+         underlayRow < MAX(underlayPamP->height,
                            origintop + overlayPamP->height);
          ++underlayRow, ++overlayRow) {
 
@@ -872,19 +873,19 @@ composite(int          const originleft,
         if (underlayRow >= 0 && underlayRow < underlayPamP->height) {
             pnm_readpamrow(underlayPamP, underlayTuplerow);
             adaptRowFormat(underlayPamP, &adaptUnderlayPam, underlayTuplerow);
-            if (underlayRow < origintop || 
+            if (underlayRow < origintop ||
                 underlayRow >= origintop + overlayPamP->height) {
-            
+
                 /* Overlay image does not touch this underlay row. */
 
                 pnm_writepamrow(composedPamP, underlayTuplerow);
             } else {
                 composeRow(originleft, &adaptUnderlayPam, &adaptOverlayPam,
-                           invertAlpha, masterOpacity, 
+                           invertAlpha, masterOpacity,
                            composedPamP, sampleScale, alphaMix,
                            underlayTuplerow, overlayTuplerow, alphaTuplerown,
                            composedTuplerow);
-                
+
                 pnm_writepamrow(composedPamP, composedTuplerow);
             }
         }
@@ -899,19 +900,19 @@ composite(int          const originleft,
 
 
 static void
-initAlphaFile(struct cmdlineInfo const cmdline,
+initAlphaFile(struct CmdlineInfo const cmdline,
               struct pam *       const overlayPamP,
               FILE **            const filePP,
               struct pam *       const pamP) {
 
     FILE * fileP;
-    
+
     if (cmdline.alphaFilespec) {
         fileP = pm_openr(cmdline.alphaFilespec);
         pamP->comment_p = NULL;
         pnm_readpaminit(fileP, pamP, PAM_STRUCT_SIZE(opacity_plane));
 
-        if (overlayPamP->width != pamP->width || 
+        if (overlayPamP->width != pamP->width ||
             overlayPamP->height != pamP->height)
             pm_error("Opacity map and overlay image are not the same size");
     } else
@@ -925,7 +926,7 @@ initAlphaFile(struct cmdlineInfo const cmdline,
 int
 main(int argc, const char *argv[]) {
 
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
     FILE * underlayFileP;
     FILE * overlayFileP;
     FILE * alphaFileP;
@@ -942,7 +943,7 @@ main(int argc, const char *argv[]) {
     overlayFileP = pm_openr(cmdline.overlayFilespec);
 
     overlayPam.comment_p = NULL;
-    pnm_readpaminit(overlayFileP, &overlayPam, 
+    pnm_readpaminit(overlayFileP, &overlayPam,
                     PAM_STRUCT_SIZE(opacity_plane));
 
     if (overlayPam.len < PAM_STRUCT_SIZE(opacity_plane))
@@ -959,7 +960,7 @@ main(int argc, const char *argv[]) {
     underlayFileP = pm_openr(cmdline.underlyingFilespec);
 
     underlayPam.comment_p = NULL;
-    pnm_readpaminit(underlayFileP, &underlayPam, 
+    pnm_readpaminit(underlayFileP, &underlayPam,
                     PAM_STRUCT_SIZE(opacity_plane));
 
     assert(underlayPam.len >= PAM_STRUCT_SIZE(opacity_plane));
@@ -968,8 +969,8 @@ main(int argc, const char *argv[]) {
         pm_error("Overlay image has tuple type '%s', which is not a "
                  "standard visual type.  We don't know how to compose.",
                  overlayPam.tuple_type);
-    
-    computeOverlayPosition(underlayPam.width, underlayPam.height, 
+
+    computeOverlayPosition(underlayPam.width, underlayPam.height,
                            overlayPam.width,  overlayPam.height,
                            cmdline, &originLeft, &originTop);
 
@@ -983,7 +984,7 @@ main(int argc, const char *argv[]) {
 
     pnm_setminallocationdepth(&underlayPam, composedPam.depth);
     pnm_setminallocationdepth(&overlayPam,  composedPam.depth);
-    
+
     composite(originLeft, originTop,
               &underlayPam, &overlayPam, alphaFileP ? &alphaPam : NULL,
               cmdline.alphaInvert, cmdline.opacity,
diff --git a/editor/pamcut.c b/editor/pamcut.c
index 0f0144ef..1c7cb4a7 100644
--- a/editor/pamcut.c
+++ b/editor/pamcut.c
@@ -1,9 +1,9 @@
-/*============================================================================ 
+/*============================================================================
                                 pamcut
 ==============================================================================
   Cut a rectangle out of a Netpbm image
 
-  This is inspired by and intended as a replacement for Pnmcut by 
+  This is inspired by and intended as a replacement for Pnmcut by
   Jef Poskanzer, 1989.
 
   By Bryan Henderson, San Jose CA.  Contributed to the public domain
@@ -24,27 +24,55 @@
        but we hope not.
        */
 
+typedef struct {
+/*----------------------------------------------------------------------------
+   A location in one dimension (row or column) in the image.
+-----------------------------------------------------------------------------*/
+    enum { LOCTYPE_NONE, LOCTYPE_FROMNEAR, LOCTYPE_FROMFAR } locType;
+
+    unsigned int n;
+        /* Row or column count.
+
+           If LOCTYPE_NONE: Meaningless
+
+           If LOCTYPE_FROMFAR: Number of colums from the far edge of the image
+           (right or bottom).  Last column/row is 1.
+
+           If LOCTYPE_FROMNEAR: Number of colums from the near edge of the
+           image (left or top).  First column/row is 0.
+        */
+} Location;
+
+
+
 struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
     const char * inputFileName;  /* File name of input file */
 
-    /* The following describe the rectangle the user wants to cut out. 
+    /* The following describe the rectangle the user wants to cut out.
        the value UNSPEC for any of them indicates that value was not
        specified.  A negative value means relative to the far edge.
-       'width' and 'height' are not negative.  These specifications 
+       'width' and 'height' are not negative.  These specifications
        do not necessarily describe a valid rectangle; they are just
        what the user said.
-       */
-    int left;
-    int right;
-    int top;
-    int bottom;
-    int width;
-    int height;
-    unsigned int pad;
 
+       These do not follow the Netpbm convention of having members of this
+       structure that are identical to the name of an option class being
+       the value of that option.  'left', for example, is not the value of
+       the -left option; it could reflect the value of a -cropleft option
+       instead.
+    */
+    Location leftLoc;
+    Location rghtLoc;
+    Location topLoc;
+    Location botLoc;
+    unsigned int widthSpec;
+    unsigned int width;
+    unsigned int heightSpec;
+    unsigned int height;
+    unsigned int pad;
     unsigned int verbose;
 };
 
@@ -63,26 +91,34 @@ parseCommandLine(int argc, const char ** const argv,
     optStruct3 opt;
     unsigned int option_def_index;
 
+    int left, right, top, bottom;
+    unsigned int cropleftSpec, croprightSpec, croptopSpec, cropbottomSpec;
+    unsigned int cropleft, cropright, croptop, cropbottom;
+    unsigned int leftSpec, rightSpec, topSpec, bottomSpec;
+
+    bool haveLegacyLocationArgs;
+        /* The user specified location with top, left, height, and width
+           arguments like in original Pnmcut instead of with named options.
+        */
+
     MALLOCARRAY_NOFAIL(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENT3 */
-    OPTENT3(0,   "left",       OPT_INT,    &cmdlineP->left,     NULL,      0);
-    OPTENT3(0,   "right",      OPT_INT,    &cmdlineP->right,    NULL,      0);
-    OPTENT3(0,   "top",        OPT_INT,    &cmdlineP->top,      NULL,      0);
-    OPTENT3(0,   "bottom",     OPT_INT,    &cmdlineP->bottom,   NULL,      0);
-    OPTENT3(0,   "width",      OPT_INT,    &cmdlineP->width,    NULL,      0);
-    OPTENT3(0,   "height",     OPT_INT,    &cmdlineP->height,   NULL,      0);
+    OPTENT3(0,   "left",       OPT_INT,    &left,       &leftSpec,          0);
+    OPTENT3(0,   "right",      OPT_INT,    &right,      &rightSpec,         0);
+    OPTENT3(0,   "top",        OPT_INT,    &top,        &topSpec,           0);
+    OPTENT3(0,   "bottom",     OPT_INT,    &bottom,     &bottomSpec,        0);
+    OPTENT3(0,   "cropleft",   OPT_UINT,   &cropleft,   &cropleftSpec,      0);
+    OPTENT3(0,   "cropright",  OPT_UINT,   &cropright,  &croprightSpec,     0);
+    OPTENT3(0,   "croptop",    OPT_UINT,   &croptop,    &croptopSpec,       0);
+    OPTENT3(0,   "cropbottom", OPT_UINT,   &cropbottom, &cropbottomSpec,    0);
+    OPTENT3(0,   "width",      OPT_UINT,   &cmdlineP->width,
+            &cmdlineP->widthSpec,       0);
+    OPTENT3(0,   "height",     OPT_UINT,   &cmdlineP->height,
+            &cmdlineP->heightSpec,      0);
     OPTENT3(0,   "pad",        OPT_FLAG,   NULL, &cmdlineP->pad,           0);
     OPTENT3(0,   "verbose",    OPT_FLAG,   NULL, &cmdlineP->verbose,       0);
 
-    /* Set the defaults */
-    cmdlineP->left = UNSPEC;
-    cmdlineP->right = UNSPEC;
-    cmdlineP->top = UNSPEC;
-    cmdlineP->bottom = UNSPEC;
-    cmdlineP->width = UNSPEC;
-    cmdlineP->height = UNSPEC;
-
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
     opt.allowNegNum = TRUE;  /* We may have parms that are negative numbers */
@@ -90,10 +126,10 @@ parseCommandLine(int argc, const char ** const argv,
     pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
-    if (cmdlineP->width < 0)
-        pm_error("-width may not be negative.");
-    if (cmdlineP->height < 0)
-        pm_error("-height may not be negative.");
+    if (cmdlineP->widthSpec && cmdlineP->width == 0)
+        pm_error("-width may not be zero.");
+    if (cmdlineP->heightSpec && cmdlineP->height == 0)
+        pm_error("-height may not be zero.");
 
     if ((argc-1) != 0 && (argc-1) != 1 && (argc-1) != 4 && (argc-1) != 5)
         pm_error("Wrong number of arguments: %u.  The only argument in "
@@ -104,170 +140,250 @@ parseCommandLine(int argc, const char ** const argv,
     switch (argc-1) {
     case 0:
         cmdlineP->inputFileName = "-";
+        haveLegacyLocationArgs = false;
         break;
     case 1:
         cmdlineP->inputFileName = argv[1];
+        haveLegacyLocationArgs = false;
         break;
     case 4:
-    case 5: {
-        int warg, harg;  /* The "width" and "height" command line arguments */
+        cmdlineP->inputFileName = "-";
+        haveLegacyLocationArgs = true;
+        break;
+    case 5:
+        cmdlineP->inputFileName = argv[5];
+        haveLegacyLocationArgs = true;
+        break;
+    }
 
-        if (sscanf(argv[1], "%d", &cmdlineP->left) != 1)
+    if (haveLegacyLocationArgs) {
+        int leftArg, topArg, widthArg, heightArg;
+
+        if (sscanf(argv[1], "%d", &leftArg) != 1)
             pm_error("Invalid number for left column argument");
-        if (sscanf(argv[2], "%d", &cmdlineP->top) != 1)
+        if (sscanf(argv[2], "%d", &topArg) != 1)
             pm_error("Invalid number for right column argument");
-        if (sscanf(argv[3], "%d", &warg) != 1)
+        if (sscanf(argv[3], "%d", &widthArg) != 1)
             pm_error("Invalid number for width argument");
-        if (sscanf(argv[4], "%d", &harg) != 1)
+        if (sscanf(argv[4], "%d", &heightArg) != 1)
             pm_error("Invalid number for height argument");
 
-        if (warg > 0) {
-            cmdlineP->width = warg;
-            cmdlineP->right = UNSPEC;
+        if (leftArg < 0) {
+            cmdlineP->leftLoc.locType = LOCTYPE_FROMFAR;
+            cmdlineP->leftLoc.n       = -leftArg;
         } else {
-            cmdlineP->width = UNSPEC;
-            cmdlineP->right = warg -1;
+            cmdlineP->leftLoc.locType = LOCTYPE_FROMNEAR;
+            cmdlineP->leftLoc.n       = leftArg;
         }
-        if (harg > 0) {
-            cmdlineP->height = harg;
-            cmdlineP->bottom = UNSPEC;
+        if (topArg < 0) {
+            cmdlineP->topLoc.locType = LOCTYPE_FROMFAR;
+            cmdlineP->topLoc.n       = -topArg;
         } else {
-            cmdlineP->height = UNSPEC;
-            cmdlineP->bottom = harg - 1;
+            cmdlineP->topLoc.locType = LOCTYPE_FROMNEAR;
+            cmdlineP->topLoc.n       = topArg;
         }
-
-        if (argc-1 == 4)
-            cmdlineP->inputFileName = "-";
-        else
-            cmdlineP->inputFileName = argv[5];
-        break;
-    }
+        if (widthArg > 0) {
+            cmdlineP->width = widthArg;
+            cmdlineP->widthSpec = 1;
+            cmdlineP->rghtLoc.locType = LOCTYPE_NONE;
+        } else {
+            cmdlineP->widthSpec = 0;
+            cmdlineP->rghtLoc.locType = LOCTYPE_FROMFAR;
+            cmdlineP->rghtLoc.n = -(widthArg - 1);
+        }
+        if (heightArg > 0) {
+            cmdlineP->height = heightArg;
+            cmdlineP->heightSpec = 1;
+            cmdlineP->botLoc.locType = LOCTYPE_NONE;
+        } else {
+            cmdlineP->heightSpec = 0;
+            cmdlineP->botLoc.locType = LOCTYPE_FROMFAR;
+            cmdlineP->botLoc.n = -(heightArg - 1);
+        }
+    } else {
+        if (leftSpec && cropleftSpec)
+            pm_error("You cannot specify both -left and -cropleft");
+        if (leftSpec) {
+            if (left >= 0) {
+                cmdlineP->leftLoc.locType = LOCTYPE_FROMNEAR;
+                cmdlineP->leftLoc.n       = left;
+            } else {
+                cmdlineP->leftLoc.locType = LOCTYPE_FROMFAR;
+                cmdlineP->leftLoc.n       = -left;
+            }
+        } else if (cropleftSpec) {
+            cmdlineP->leftLoc.locType = LOCTYPE_FROMNEAR;
+            cmdlineP->leftLoc.n       = cropleft;
+        } else
+            cmdlineP->leftLoc.locType = LOCTYPE_NONE;
+
+        if (rightSpec && croprightSpec)
+            pm_error("You cannot specify both -right and -cropright");
+        if (rightSpec) {
+            if (right >= 0) {
+                cmdlineP->rghtLoc.locType = LOCTYPE_FROMNEAR;
+                cmdlineP->rghtLoc.n       = right;
+            } else {
+                cmdlineP->rghtLoc.locType = LOCTYPE_FROMFAR;
+                cmdlineP->rghtLoc.n       = -right;
+            }
+        } else if (croprightSpec) {
+            cmdlineP->rghtLoc.locType = LOCTYPE_FROMFAR;
+            cmdlineP->rghtLoc.n       = 1 + cropright;
+        } else
+            cmdlineP->rghtLoc.locType = LOCTYPE_NONE;
+
+        if (topSpec && croptopSpec)
+            pm_error("You cannot specify both -top and -croptop");
+        if (topSpec) {
+            if (top >= 0) {
+                cmdlineP->topLoc.locType = LOCTYPE_FROMNEAR;
+                cmdlineP->topLoc.n       = top;
+            } else {
+                cmdlineP->topLoc.locType = LOCTYPE_FROMFAR;
+                cmdlineP->topLoc.n       = -top;
+            }
+        } else if (croptopSpec) {
+            cmdlineP->topLoc.locType = LOCTYPE_FROMNEAR;
+            cmdlineP->topLoc.n       = croptop;
+        } else
+            cmdlineP->topLoc.locType = LOCTYPE_NONE;
+
+        if (bottomSpec && cropbottomSpec)
+            pm_error("You cannot specify both -bottom and -cropbottom");
+        if (bottomSpec) {
+            if (bottom >= 0) {
+                cmdlineP->botLoc.locType = LOCTYPE_FROMNEAR;
+                cmdlineP->botLoc.n       = bottom;
+            } else {
+                cmdlineP->botLoc.locType = LOCTYPE_FROMFAR;
+                cmdlineP->botLoc.n       = -bottom;
+            }
+        } else if (cropbottomSpec) {
+            cmdlineP->botLoc.locType = LOCTYPE_FROMFAR;
+            cmdlineP->botLoc.n       = 1 + cropbottom;
+        } else
+            cmdlineP->botLoc.locType = LOCTYPE_NONE;
     }
 }
 
 
+static int
+near(Location     const loc,
+     unsigned int const edge) {
 
-static void
-computeCutBounds(const int cols, const int rows,
-                 const int leftarg, const int rightarg, 
-                 const int toparg, const int bottomarg,
-                 const int widtharg, const int heightarg,
-                 int * const leftcolP, int * const rightcolP,
-                 int * const toprowP, int * const bottomrowP) {
-/*----------------------------------------------------------------------------
-   From the values given on the command line 'leftarg', 'rightarg',
-   'toparg', 'bottomarg', 'widtharg', and 'heightarg', determine what
-   rectangle the user wants cut out.
-
-   Any of these arguments may be UNSPEC to indicate "not specified".
-   Any except 'widtharg' and 'heightarg' may be negative to indicate
-   relative to the far edge.  'widtharg' and 'heightarg' are positive.
+    int retval;
 
-   Return the location of the rectangle as *leftcolP, *rightcolP,
-   *toprowP, and *bottomrowP.  
------------------------------------------------------------------------------*/
-
-    int leftcol, rightcol, toprow, bottomrow;
-        /* The left and right column numbers and top and bottom row numbers
-           specified by the user, except with negative values translated
-           into the actual values.
+    switch (loc.locType) {
+    case LOCTYPE_NONE:
+        assert(false);
+        retval = 0;
+        break;
+    case LOCTYPE_FROMNEAR:
+        retval = loc.n;
+        break;
+    case LOCTYPE_FROMFAR:
+        retval = (int)edge - (int)loc.n;
+    }
 
-           Note that these may very well be negative themselves, such
-           as when the user says "column -10" and there are only 5 columns
-           in the image.
-           */
+    return retval;
+}
 
-    /* Translate negative column and row into real column and row */
-    /* Exploit the fact that UNSPEC is a positive number */
 
-    if (leftarg >= 0)
-        leftcol = leftarg;
-    else
-        leftcol = cols + leftarg;
-    if (rightarg >= 0)
-        rightcol = rightarg;
-    else
-        rightcol = cols + rightarg;
-    if (toparg >= 0)
-        toprow = toparg;
-    else
-        toprow = rows + toparg;
-    if (bottomarg >= 0)
-        bottomrow = bottomarg;
-    else
-        bottomrow = rows + bottomarg;
 
-    /* Sort out left, right, and width specifications */
+static void
+computeCutBounds(unsigned int const cols,
+                 unsigned int const rows,
+                 Location     const leftArg,
+                 Location     const rghtArg,
+                 Location     const topArg,
+                 Location     const botArg,
+                 bool         const widthSpec,
+                 unsigned int const widthArg,
+                 bool         const heightSpec,
+                 unsigned int const heightArg,
+                 int *        const leftColP,
+                 int *        const rghtColP,
+                 int *        const topRowP,
+                 int *        const botRowP) {
+/*----------------------------------------------------------------------------
+   From the values given on the command line 'leftArg', 'rghtArg', 'topArg',
+   'botArg', 'widthArg', and 'heightArg', determine what rectangle the user
+   wants cut out.
 
-    if (leftcol == UNSPEC && rightcol == UNSPEC && widtharg == UNSPEC) {
-        *leftcolP = 0;
-        *rightcolP = cols - 1;
-    }
-    if (leftcol == UNSPEC && rightcol == UNSPEC && widtharg != UNSPEC) {
-        *leftcolP = 0;
-        *rightcolP = 0 + widtharg - 1;
-    }
-    if (leftcol == UNSPEC && rightcol != UNSPEC && widtharg == UNSPEC) {
-        *leftcolP = 0;
-        *rightcolP = rightcol;
-    }
-    if (leftcol == UNSPEC && rightcol != UNSPEC && widtharg != UNSPEC) {
-        *leftcolP = rightcol - widtharg + 1;
-        *rightcolP = rightcol;
-    }
-    if (leftcol != UNSPEC && rightcol == UNSPEC && widtharg == UNSPEC) {
-        *leftcolP = leftcol;
-        *rightcolP = cols - 1;
-    }
-    if (leftcol != UNSPEC && rightcol == UNSPEC && widtharg != UNSPEC) {
-        *leftcolP = leftcol;
-        *rightcolP = leftcol + widtharg - 1;
-    }
-    if (leftcol != UNSPEC && rightcol != UNSPEC && widtharg == UNSPEC) {
-        *leftcolP = leftcol;
-        *rightcolP = rightcol;
-    }
-    if (leftcol != UNSPEC && rightcol != UNSPEC && widtharg != UNSPEC) {
-        pm_error("You may not specify left, right, and width.\n"
-                 "Choose at most two of these.");
+   Return the location of the rectangle as *leftcolP, *rghtcolP, *toprowP, and
+   *botrowP.  Any of these can be outside the image, including by being
+   negative.
+-----------------------------------------------------------------------------*/
+    /* Find left and right bounds */
+
+    if (widthSpec)
+        assert(widthArg > 0);
+
+    if (leftArg.locType == LOCTYPE_NONE) {
+        if (rghtArg.locType == LOCTYPE_NONE) {
+            *leftColP = 0;
+            if (widthSpec)
+                *rghtColP = 0 + (int)widthArg - 1;
+            else
+                *rghtColP = (int)cols - 1;
+        } else {
+            *rghtColP = near(rghtArg, cols);
+            if (widthSpec)
+                *leftColP = near(rghtArg, cols) - (int)widthArg + 1;
+            else
+                *leftColP = 0;
+        }
+    } else {
+        *leftColP = near(leftArg, cols);
+        if (rghtArg.locType == LOCTYPE_NONE) {
+            if (widthSpec)
+                *rghtColP = near(leftArg, cols) + (int)widthArg - 1;
+            else
+                *rghtColP = (int)cols - 1;
+        } else {
+            if (widthSpec) {
+                pm_error("You may not specify left, right, and width.  "
+                         "Choose at most two of these.");
+            } else
+                *rghtColP = near(rghtArg, cols);
+        }
     }
 
+    /* Find top and bottom bounds */
 
-    /* Sort out top, bottom, and height specifications */
+    if (heightSpec)
+        assert(heightArg > 0);
 
-    if (toprow == UNSPEC && bottomrow == UNSPEC && heightarg == UNSPEC) {
-        *toprowP = 0;
-        *bottomrowP = rows - 1;
-    }
-    if (toprow == UNSPEC && bottomrow == UNSPEC && heightarg != UNSPEC) {
-        *toprowP = 0;
-        *bottomrowP = 0 + heightarg - 1;
-    }
-    if (toprow == UNSPEC && bottomrow != UNSPEC && heightarg == UNSPEC) {
-        *toprowP = 0;
-        *bottomrowP = bottomrow;
-    }
-    if (toprow == UNSPEC && bottomrow != UNSPEC && heightarg != UNSPEC) {
-        *toprowP = bottomrow - heightarg + 1;
-        *bottomrowP = bottomrow;
-    }
-    if (toprow != UNSPEC && bottomrow == UNSPEC && heightarg == UNSPEC) {
-        *toprowP = toprow;
-        *bottomrowP = rows - 1;
-    }
-    if (toprow != UNSPEC && bottomrow == UNSPEC && heightarg != UNSPEC) {
-        *toprowP = toprow;
-        *bottomrowP = toprow + heightarg - 1;
-    }
-    if (toprow != UNSPEC && bottomrow != UNSPEC && heightarg == UNSPEC) {
-        *toprowP = toprow;
-        *bottomrowP = bottomrow;
-    }
-    if (toprow != UNSPEC && bottomrow != UNSPEC && heightarg != UNSPEC) {
-        pm_error("You may not specify top, bottom, and height.\n"
-                 "Choose at most two of these.");
+    if (topArg.locType == LOCTYPE_NONE) {
+        if (botArg.locType == LOCTYPE_NONE) {
+            *topRowP = 0;
+            if (heightSpec)
+                *botRowP = 0 + (int)heightArg - 1;
+            else
+                *botRowP = (int)rows - 1;
+        } else {
+            *botRowP = near(botArg, rows);
+            if (heightSpec)
+                *topRowP = near(botArg, rows) - (int)heightArg + 1;
+            else
+                *topRowP = 0;
+        }
+    } else {
+        *topRowP = near(topArg, rows);
+        if (botArg.locType == LOCTYPE_NONE) {
+            if (heightSpec)
+                *botRowP = near(topArg, rows) + (int)heightArg - 1;
+            else
+                *botRowP = (int)rows - 1;
+        } else {
+            if (heightSpec) {
+                pm_error("You may not specify top, bottom, and height.  "
+                         "Choose at most two of these.");
+            } else
+                *botRowP = near(botArg, rows);
+        }
     }
-
 }
 
 
@@ -324,7 +440,7 @@ rejectOutOfBounds(unsigned int const cols,
 
 
 static void
-writeBlackRows(const struct pam * const outpamP, 
+writeBlackRows(const struct pam * const outpamP,
                int                const rows) {
 /*----------------------------------------------------------------------------
    Write out 'rows' rows of black tuples of the image described by *outpamP.
@@ -336,11 +452,11 @@ writeBlackRows(const struct pam * const outpamP,
     tuple blackTuple;
     tuple * blackRow;
     int col;
-    
+
     pnm_createBlackTuple(outpamP, &blackTuple);
 
     MALLOCARRAY_NOFAIL(blackRow, outpamP->width);
-    
+
     for (col = 0; col < outpamP->width; ++col)
         blackRow[col] = blackTuple;
 
@@ -360,14 +476,14 @@ struct rowCutter {
    pnm_readpamrow() and one pnm_writepamrow().  It works like this:
 
    The array inputPointers[] contains an element for each pixel in an input
-   row.  If it's a pixel that gets discarded in the cutting process, 
+   row.  If it's a pixel that gets discarded in the cutting process,
    inputPointers[] points to a special "discard" tuple.  All thrown away
    pixels have the same discard tuple to save CPU cache space.  If it's
    a pixel that gets copied to the output, inputPointers[] points to some
    tuple to which outputPointers[] also points.
 
    The array outputPointers[] contains an element for each pixel in an
-   output row.  If the pixel is one that gets copied from the input, 
+   output row.  If the pixel is one that gets copied from the input,
    outputPointers[] points to some tuple to which inputPointers[] also
    points.  If it's a pixel that gets padded with black, outputPointers[]
    points to a constant black tuple.  All padded pixels have the same
@@ -489,7 +605,7 @@ destroyRowCutter(struct rowCutter * const rowCutterP) {
     pnm_freepamtuple(rowCutterP->discardTuple);
     free(rowCutterP->inputPointers);
     free(rowCutterP->outputPointers);
-    
+
     free(rowCutterP);
 }
 
@@ -519,7 +635,7 @@ extractRowsGen(const struct pam * const inpamP,
             pnm_writepamrow(outpamP, rowCutterP->outputPointers);
         } else  /* row < toprow || row > bottomrow */
             pnm_readpamrow(inpamP, NULL);
-        
+
         /* Note that we may be tempted just to quit after reaching the bottom
            of the extracted image, but that would cause a broken pipe problem
            for the process that's feeding us the image.
@@ -527,7 +643,7 @@ extractRowsGen(const struct pam * const inpamP,
     }
 
     destroyRowCutter(rowCutterP);
-    
+
     /* Write out bottom padding */
     if ((bottomrow - (inpamP->height-1)) > 0)
         writeBlackRows(outpamP, bottomrow - (inpamP->height-1));
@@ -574,7 +690,7 @@ extractRowsPBM(const struct pam * const inpamP,
             /* Prevent overflows in pbm_allocrow_packed() */
             pm_error("Specified right edge is too far "
                      "from the right end of input image");
-        
+
         readOffset  = 0;
         writeOffset = leftcol;
     } else {
@@ -582,7 +698,7 @@ extractRowsPBM(const struct pam * const inpamP,
         if (totalWidth > INT_MAX - 10)
             pm_error("Specified left/right edge is too far "
                      "from the left/right end of input image");
-        
+
         readOffset = -leftcol;
         writeOffset = 0;
     }
@@ -606,7 +722,7 @@ extractRowsPBM(const struct pam * const inpamP,
 
             pbm_writepbmrow_bitoffset(outpamP->file, bitrow, outpamP->width,
                                       0, writeOffset);
-  
+
             if (rightcol >= inpamP->width)
                 /* repair right padding */
                 bitrow[writeOffset/8 + pbm_packed_bytes(outpamP->width) - 1] =
@@ -632,18 +748,20 @@ cutOneImage(FILE *             const ifP,
             FILE *             const ofP) {
 
     int leftcol, rightcol, toprow, bottomrow;
+        /* Could be out of bounds, even negative */
     struct pam inpam;   /* Input PAM image */
     struct pam outpam;  /* Output PAM image */
 
     pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
-    
-    computeCutBounds(inpam.width, inpam.height, 
-                     cmdline.left, cmdline.right, 
-                     cmdline.top, cmdline.bottom, 
-                     cmdline.width, cmdline.height, 
+
+    computeCutBounds(inpam.width, inpam.height,
+                     cmdline.leftLoc, cmdline.rghtLoc,
+                     cmdline.topLoc, cmdline.botLoc,
+                     cmdline.widthSpec, cmdline.width,
+                     cmdline.heightSpec, cmdline.height,
                      &leftcol, &rightcol, &toprow, &bottomrow);
 
-    rejectOutOfBounds(inpam.width, inpam.height, leftcol, rightcol, 
+    rejectOutOfBounds(inpam.width, inpam.height, leftcol, rightcol,
                       toprow, bottomrow, cmdline.pad);
 
     if (cmdline.verbose) {
@@ -691,6 +809,6 @@ main(int argc, const char *argv[]) {
 
     pm_close(ifP);
     pm_close(ofP);
-    
+
     return 0;
 }
diff --git a/editor/pamditherbw.c b/editor/pamditherbw.c
index 36eb7d9e..4b192e6e 100644
--- a/editor/pamditherbw.c
+++ b/editor/pamditherbw.c
@@ -29,6 +29,10 @@ enum halftone {QT_FS,
 
 enum ditherType {DT_REGULAR, DT_CLUSTER};
 
+static sample blackSample = (sample) PAM_BLACK;
+static sample whiteSample = (sample) PAM_BW_WHITE;
+static tuple  const blackTuple = &blackSample;
+static tuple  const whiteTuple = &whiteSample;
 
 struct cmdlineInfo {
     /* All the information the user supplied in the command line,
@@ -95,6 +99,8 @@ parseCommandLine(int argc, char ** argv,
     pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
+    free(option_def);
+
     if (floydOpt + atkinsonOpt + thresholdOpt + hilbertOpt + dither8Opt + 
         cluster3Opt + cluster4Opt + cluster8Opt == 0)
         cmdlineP->halftone = QT_FS;
@@ -186,134 +192,143 @@ makeOutputPam(unsigned int const width,
 
 #define MAXORD 18
 
-static int hil_order,hil_ord;
-static int hil_turn;
-static int hil_dx,hil_dy;
-static int hil_x,hil_y;
-static int hil_stage[MAXORD];
-static int hil_width,hil_height;
+struct Hil {
+    int order;
+    int ord;
+    int turn;
+    int dx;
+    int dy;
+    int x;
+    int y;
+    int stage[MAXORD];
+    int width;
+    int height;
+};
 
 static void 
-initHilbert(int const w, 
-            int const h) {
+initHilbert(int          const w, 
+            int          const h,
+            struct Hil * const hilP) {
 /*----------------------------------------------------------------------------
   Initialize the Hilbert curve tracer 
 -----------------------------------------------------------------------------*/
-    int big,ber;
-    hil_width = w;
-    hil_height = h;
+    int big, ber;
+    hilP->width = w;
+    hilP->height = h;
     big = w > h ? w : h;
-    for (ber = 2, hil_order = 1; ber < big; ber <<= 1, hil_order++);
-    if (hil_order > MAXORD)
+    for (ber = 2, hilP->order = 1; ber < big; ber <<= 1, hilP->order++);
+    if (hilP->order > MAXORD)
         pm_error("Sorry, hilbert order is too large");
-    hil_ord = hil_order;
-    hil_order--;
+    hilP->ord = hilP->order;
+    hilP->order--;
 }
 
 
 
-static int 
-hilbert(int * const px, int * const py) {
+static bool
+hilbert(int *        const px,
+        int *        const py,
+        struct Hil * const hilP) {
 /*----------------------------------------------------------------------------
   Return non-zero if got another point
 -----------------------------------------------------------------------------*/
     int temp;
-    if (hil_ord > hil_order) {
+    if (hilP->ord > hilP->order) {
         /* have to do first point */
 
-        hil_ord--;
-        hil_stage[hil_ord] = 0;
-        hil_turn = -1;
-        hil_dy = 1;
-        hil_dx = hil_x = hil_y = 0;
+        hilP->ord--;
+        hilP->stage[hilP->ord] = 0;
+        hilP->turn = -1;
+        hilP->dy = 1;
+        hilP->dx = hilP->x = hilP->y = 0;
         *px = *py = 0;
-        return 1;
+        return true;
     }
 
     /* Operate the state machine */
     for(;;)  {
-        switch (hil_stage[hil_ord]) {
+        switch (hilP->stage[hilP->ord]) {
         case 0:
-            hil_turn = -hil_turn;
-            temp = hil_dy;
-            hil_dy = -hil_turn * hil_dx;
-            hil_dx = hil_turn * temp;
-            if (hil_ord > 0) {
-                hil_stage[hil_ord] = 1;
-                hil_ord--;
-                hil_stage[hil_ord]=0;
+            hilP->turn = -hilP->turn;
+            temp = hilP->dy;
+            hilP->dy = -hilP->turn * hilP->dx;
+            hilP->dx = hilP->turn * temp;
+            if (hilP->ord > 0) {
+                hilP->stage[hilP->ord] = 1;
+                hilP->ord--;
+                hilP->stage[hilP->ord]=0;
                 continue;
             }
         case 1:
-            hil_x += hil_dx;
-            hil_y += hil_dy;
-            if (hil_x < hil_width && hil_y < hil_height) {
-                hil_stage[hil_ord] = 2;
-                *px = hil_x;
-                *py = hil_y;
-                return 1;
+            hilP->x += hilP->dx;
+            hilP->y += hilP->dy;
+            if (hilP->x < hilP->width && hilP->y < hilP->height) {
+                hilP->stage[hilP->ord] = 2;
+                *px = hilP->x;
+                *py = hilP->y;
+                return true;
             }
         case 2:
-            hil_turn = -hil_turn;
-            temp = hil_dy;
-            hil_dy = -hil_turn * hil_dx;
-            hil_dx = hil_turn * temp;
-            if (hil_ord > 0) { 
+            hilP->turn = -hilP->turn;
+            temp = hilP->dy;
+            hilP->dy = -hilP->turn * hilP->dx;
+            hilP->dx = hilP->turn * temp;
+            if (hilP->ord > 0) { 
                 /* recurse */
 
-                hil_stage[hil_ord] = 3;
-                hil_ord--;
-                hil_stage[hil_ord]=0;
+                hilP->stage[hilP->ord] = 3;
+                hilP->ord--;
+                hilP->stage[hilP->ord]=0;
                 continue;
             }
         case 3:
-            hil_x += hil_dx;
-            hil_y += hil_dy;
-            if (hil_x < hil_width && hil_y < hil_height) {
-                hil_stage[hil_ord] = 4;
-                *px = hil_x;
-                *py = hil_y;
-                return 1;
+            hilP->x += hilP->dx;
+            hilP->y += hilP->dy;
+            if (hilP->x < hilP->width && hilP->y < hilP->height) {
+                hilP->stage[hilP->ord] = 4;
+                *px = hilP->x;
+                *py = hilP->y;
+                return true;
             }
         case 4:
-            if (hil_ord > 0) {
+            if (hilP->ord > 0) {
                 /* recurse */
-                hil_stage[hil_ord] = 5;
-                hil_ord--;
-                hil_stage[hil_ord]=0;
+                hilP->stage[hilP->ord] = 5;
+                hilP->ord--;
+                hilP->stage[hilP->ord]=0;
                 continue;
             }
         case 5:
-            temp = hil_dy;
-            hil_dy = -hil_turn * hil_dx;
-            hil_dx = hil_turn * temp;
-            hil_turn = -hil_turn;
-            hil_x += hil_dx;
-            hil_y += hil_dy;
-            if (hil_x < hil_width && hil_y < hil_height) {
-                hil_stage[hil_ord] = 6;
-                *px = hil_x;
-                *py = hil_y;
-                return 1;
+            temp = hilP->dy;
+            hilP->dy = -hilP->turn * hilP->dx;
+            hilP->dx = hilP->turn * temp;
+            hilP->turn = -hilP->turn;
+            hilP->x += hilP->dx;
+            hilP->y += hilP->dy;
+            if (hilP->x < hilP->width && hilP->y < hilP->height) {
+                hilP->stage[hilP->ord] = 6;
+                *px = hilP->x;
+                *py = hilP->y;
+                return true;
             }
         case 6:
-            if (hil_ord > 0) {
+            if (hilP->ord > 0) {
                 /* recurse */
-                hil_stage[hil_ord] = 7;
-                hil_ord--;
-                hil_stage[hil_ord]=0;
+                hilP->stage[hilP->ord] = 7;
+                hilP->ord--;
+                hilP->stage[hilP->ord]=0;
                 continue;
             }
         case 7:
-            temp = hil_dy;
-            hil_dy = -hil_turn * hil_dx;
-            hil_dx = hil_turn * temp;
-            hil_turn = -hil_turn;
+            temp = hilP->dy;
+            hilP->dy = -hilP->turn * hilP->dx;
+            hilP->dx = hilP->turn * temp;
+            hilP->turn = -hilP->turn;
             /* Return from a recursion */
-            if (hil_ord < hil_order)
-                hil_ord++;
+            if (hilP->ord < hilP->order)
+                hilP->ord++;
             else
-                return 0;
+                return false;
         }
     }
 }
@@ -341,6 +356,8 @@ doHilbert(FILE *       const ifP,
     tuple ** grays;
     tuple ** bits;
 
+    struct Hil hil;
+
     int end;
     int *x,*y;
     int sum;
@@ -355,7 +372,7 @@ doHilbert(FILE *       const ifP,
     MALLOCARRAY(y, clumpSize);
     if (x == NULL  || y == NULL)
         pm_error("out of memory");
-    initHilbert(graypam.width, graypam.height);
+    initHilbert(graypam.width, graypam.height, &hil);
 
     sum = 0;
     end = clumpSize;
@@ -364,7 +381,9 @@ doHilbert(FILE *       const ifP,
         unsigned int i;
         /* compute the next cluster co-ordinates along hilbert path */
         for (i = 0; i < end; i++) {
-            if (hilbert(&x[i],&y[i])==0)
+            bool gotPoint;
+            gotPoint = hilbert(&x[i], &y[i], &hil);
+            if (!gotPoint)
                 end = i;    /* we reached the end */
         }
         /* sum levels */
@@ -381,6 +400,7 @@ doHilbert(FILE *       const ifP,
                 bits[row][col][0] = 0;
         }
     }
+    free(x);    free(y); 
     pnm_writepam(&bitpam, bits);
 
     pnm_freepamarray(bits, &bitpam);
@@ -458,11 +478,11 @@ fsConvertRow(struct converter * const converterP,
             /* We've accumulated enough light (power) to justify a
                white output pixel.
             */
-            bitrow[col][0] = PAM_BW_WHITE;
+            bitrow[col] = whiteTuple;
             /* Remove from sum the power of this white output pixel */
             accum -= stateP->white;
         } else
-            bitrow[col][0] = PAM_BLACK;
+            bitrow[col] = blackTuple;
 
         /* Forward to future output pixels the power from current
            input pixel and the power forwarded from previous input
@@ -589,10 +609,6 @@ atkinsonConvertRow(struct converter * const converterP,
                    tuplen                   grayrow[],
                    tuple                    bitrow[]) {
 
-    /* See http://www.tinrocket.com/projects/programming/graphics/00158/
-       for a description of the Atkinson algorithm
-    */
-
     struct atkinsonState * const stateP = converterP->stateP;
 
     samplen ** const error = stateP->error;
@@ -607,11 +623,11 @@ atkinsonConvertRow(struct converter * const converterP,
             /* We've accumulated enough light (power) to justify a
                white output pixel.
             */
-            bitrow[col][0] = PAM_BW_WHITE;
+            bitrow[col] = whiteTuple;
             /* Remove from accum the power of this white output pixel */
             accum -= stateP->white;
         } else
-            bitrow[col][0] = PAM_BLACK;
+            bitrow[col] = blackTuple;
         
         /* Forward to future output pixels 3/4 of the power from current
            input pixel and the power forwarded from previous input
@@ -699,8 +715,8 @@ threshConvertRow(struct converter * const converterP,
 
     unsigned int col;
     for (col = 0; col < converterP->cols; ++col)
-        bitrow[col][0] =
-            grayrow[col][0] >= stateP->threshval ? PAM_BW_WHITE : PAM_BLACK;
+        bitrow[col] =
+            grayrow[col][0] >= stateP->threshval ? whiteTuple : blackTuple;
 }
 
 
@@ -754,8 +770,8 @@ clusterConvertRow(struct converter * const converterP,
     for (col = 0; col < converterP->cols; ++col) {
         float const threshold = 
             stateP->clusterMatrix[row % diameter][col % diameter];
-        bitrow[col][0] = 
-            grayrow[col][0] > threshold ? PAM_BW_WHITE : PAM_BLACK;
+        bitrow[col] =
+            grayrow[col][0] > threshold ? whiteTuple : blackTuple;
     }
 }
 
@@ -903,7 +919,7 @@ main(int argc, char *argv[]) {
         }
 
         grayrow = pnm_allocpamrown(&graypam);
-        bitrow  = pnm_allocpamrow(&bitpam);
+        MALLOCARRAY_NOFAIL(bitrow, bitpam.width);
 
         for (row = 0; row < graypam.height; ++row) {
             pnm_readpamrown(&graypam, grayrow);
@@ -912,7 +928,7 @@ main(int argc, char *argv[]) {
             
             pnm_writepamrow(&bitpam, bitrow);
         }
-        pnm_freepamrow(bitrow);
+        free(bitrow);
         pnm_freepamrow(grayrow);
 
         if (converter.destroy)
diff --git a/editor/pamenlarge.c b/editor/pamenlarge.c
index 187bfb6e..56a8c6f7 100644
--- a/editor/pamenlarge.c
+++ b/editor/pamenlarge.c
@@ -3,46 +3,132 @@
 ===============================================================================
   By Bryan Henderson 2004.09.26.  Contributed to the public domain by its
   author.
+
+  The design and code for the fast processing of PBMs is by Akira Urushibata
+  in March 2010 and substantially improved in February 2019.
 =============================================================================*/
 
+#include <stdbool.h>
+#include <assert.h>
+
 #include "netpbm/mallocvar.h"
 #include "netpbm/pm_c_util.h"
 #include "netpbm/pam.h"
 #include "netpbm/pbm.h"
+#include "netpbm/shhopt.h"
+#include "netpbm/nstring.h"
+
 
-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;  
-    unsigned int scaleFactor;
+    const char * inputFilespec;
+    unsigned int xScaleFactor;
+    unsigned int yScaleFactor;
 };
 
 
 
 static void
-parseCommandLine(int                  const argc,
-                 const char **        const argv,
-                 struct cmdlineInfo * const cmdlineP) {
+parseCommandLine(int                  argc,
+                 const char        ** argv,
+                 struct CmdlineInfo * cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that the file spec array we return is stored in the storage that
    was passed to us as the argv array.
 -----------------------------------------------------------------------------*/
-    if (argc-1 < 1)
-        pm_error("You must specify at least one argument:  The scale factor");
-    else {
-        cmdlineP->scaleFactor = atoi(argv[1]);
-        
-        if (cmdlineP->scaleFactor < 1)
-            pm_error("Scale factor must be an integer at least 1.  "
-                     "You specified '%s'", argv[1]);
+    optStruct3 opt;  /* set by OPTENT3 */
+    optEntry * option_def;
+    unsigned int option_def_index;
+
+    unsigned int scale;
+    unsigned int xscaleSpec;
+    unsigned int yscaleSpec;
+    unsigned int scaleSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "xscale", OPT_UINT, &cmdlineP->xScaleFactor,  &xscaleSpec, 0);
+    OPTENT3(0, "yscale", OPT_UINT, &cmdlineP->yScaleFactor,  &yscaleSpec, 0);
+    OPTENT3(0, "scale",  OPT_UINT, &scale,                   &scaleSpec, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = false; /* We have some 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 (scaleSpec && scale == 0)
+        pm_error("-scale must be positive.  You specified zero");
+
+    if (xscaleSpec && cmdlineP->xScaleFactor == 0)
+        pm_error("-xscale must be positive.  You specified zero");
+
+    if (yscaleSpec && cmdlineP->yScaleFactor == 0)
+        pm_error("-yscale must be positive.  You specified zero");
+
+    if (scaleSpec && xscaleSpec)
+        pm_error("You cannot specify both -scale and -xscale");
+
+    if (scaleSpec && yscaleSpec)
+        pm_error("You cannot specify both -scale and -yscale");
+
+    if (scaleSpec) {
+        cmdlineP->xScaleFactor = scale;
+        cmdlineP->yScaleFactor = scale;
+    }
+
+    if (xscaleSpec && !yscaleSpec)
+        cmdlineP->yScaleFactor = 1;
+
+    if (yscaleSpec && !xscaleSpec)
+        cmdlineP->xScaleFactor = 1;
+
+    if (scaleSpec || xscaleSpec || yscaleSpec) {
+        /* Scale options specified.  Naked scale argument not allowed */
 
-        if (argc-1 >= 2)
+        if ((argc-1) > 1)
+            pm_error("Too many arguments (%u).  With a scale option, "
+                     "the only argument is the "
+                     "optional file specification", argc-1);
+
+        if (argc-1 > 0)
+            cmdlineP->inputFilespec = argv[1];
+        else
+            cmdlineP->inputFilespec = "-";
+    } else {
+        /* scale must be specified in an argument */
+        if ((argc-1) != 1 && (argc-1) != 2)
+            pm_error("Wrong number of arguments (%d).  Without scale options, "
+                     "you must supply 1 or 2 arguments:  scale and "
+                     "optional file specification", argc-1);
+
+        {
+            const char * error;   /* error message of pm_string_to_uint */
+            unsigned int scale;
+
+            pm_string_to_uint(argv[1], &scale, &error);
+
+            if (error == NULL) {
+                if (scale == 0)
+                    pm_error("Scale argument must be positive.  "
+                             "You specified zero");
+                else
+                    cmdlineP->xScaleFactor = cmdlineP->yScaleFactor = scale;
+            } else
+                pm_error("Invalid scale factor: %s", error);
+
+        }
+        if (argc-1 > 1)
             cmdlineP->inputFilespec = argv[2];
         else
             cmdlineP->inputFilespec = "-";
     }
-}        
+    free(option_def);
+}
 
 
 
@@ -75,253 +161,571 @@ makeOutputRowMap(tuple **     const outTupleRowP,
 static void
 validateComputableDimensions(unsigned int const width,
                              unsigned int const height,
-                             unsigned int const scaleFactor) {
+                             unsigned int const xScaleFactor,
+                             unsigned int const yScaleFactor) {
 /*----------------------------------------------------------------------------
    Make sure that multiplication for output image width and height do not
    overflow.
-   See validateComputetableSize() in libpam.c
-   and pbm_readpbminitrest() in libpbm2.c
+
+   See validateComputetableSize() in libpam.c and pbm_readpbminitrest() in
+   libpbm2.c
 -----------------------------------------------------------------------------*/
     unsigned int const maxWidthHeight = INT_MAX - 2;
     unsigned int const maxScaleFactor = maxWidthHeight / MAX(height, width);
+    unsigned int const greaterScaleFactor = MAX(xScaleFactor, yScaleFactor);
 
-    if (scaleFactor > maxScaleFactor)
-       pm_error("Scale factor '%u' too large.  "
-                "The maximum for this %u x %u input image is %u.",
-                scaleFactor, width, height, maxScaleFactor);
+    if (greaterScaleFactor > maxScaleFactor)
+        pm_error("Scale factor '%u' too large.  "
+                 "The maximum for this %u x %u input image is %u.",
+                 greaterScaleFactor, width, height, maxScaleFactor);
 }
 
 
+static unsigned char const pair[7][4] = {
+    { 0x00 , 0x7F , 0x80 , 0xFF},
+    { 0x00 , 0x3F , 0xC0 , 0xFF},
+    { 0x00 , 0x1F , 0xE0 , 0xFF},
+    { 0x00 , 0x0F , 0xF0 , 0xFF},
+    { 0x00 , 0x07 , 0xF8 , 0xFF},
+    { 0x00 , 0x03 , 0xFC , 0xFF},
+    { 0x00 , 0x01 , 0xFE , 0xFF} };
+
+
 
 static void
-enlargePbmRowHorizontally(struct pam *          const inpamP,
-                          const unsigned char * const inrow,
-                          unsigned int          const inColChars,
-                          unsigned int          const outColChars,
-                          unsigned int          const scaleFactor,
-                          unsigned char *       const outrow) {
-
-    static unsigned char const dbl[16] = { 
-        0x00, 0x03, 0x0C, 0x0F, 0x30, 0x33, 0x3C, 0x3F, 
+enlargePbmRowHorizontallySmall(const unsigned char * const inrow,
+                               unsigned int          const inColChars,
+                               unsigned int          const xScaleFactor,
+                               unsigned char *       const outrow) {
+/*----------------------------------------------------------------------------
+   Fast routines for scale factors 1-13.
+
+   Using a temp value "inrowChar" makes a difference.  We know that inrow
+   and outrow don't overlap, but the compiler does not and emits code
+   which reads inrow[colChar] each time fearing that a write to outrow[x]
+   may have altered the value.  (The first "const" for inrow in the above
+   argument list is not enough for the compiler.)
+-----------------------------------------------------------------------------*/
+
+    static unsigned char const dbl[16] = {
+        0x00, 0x03, 0x0C, 0x0F, 0x30, 0x33, 0x3C, 0x3F,
         0xC0, 0xC3, 0xCC, 0xCF, 0xF0, 0xF3, 0xFC, 0xFF };
 
-    static unsigned char const trp1[8] = { 
+    static unsigned char const trp1[8] = {
         0x00, 0x03, 0x1C, 0x1F, 0xE0, 0xE3, 0xFC, 0xFF };
-        
-    static unsigned char const trp2[16] = { 
+
+    static unsigned char const trp2[16] = {
         0x00, 0x01, 0x0E, 0x0F, 0x70, 0x71, 0x7E, 0x7F,
         0x80, 0x81, 0x8E, 0x8F, 0xF0, 0xF1, 0xFE, 0xFF };
 
-    static unsigned char const trp3[8] = { 
+    static unsigned char const trp3[8] = {
         0x00, 0x07, 0x38, 0x3F, 0xC0, 0xC7, 0xF8, 0xFF };
 
-    static unsigned char const quad[4] = { 0x00, 0x0F, 0xF0, 0xFF };
-
     static unsigned char const quin2[8] = {
         0x00, 0x01, 0x3E, 0x3F, 0xC0, 0xC1, 0xFE, 0xFF };
 
     static unsigned char const quin4[8] = {
         0x00, 0x03, 0x7C, 0x7F, 0x80, 0x83, 0xFC, 0xFF };
 
-    static unsigned int const pair[4] = { 0x0000, 0x00FF, 0xFF00, 0xFFFF };
+    static unsigned char const * quad = pair[3];
 
     unsigned int colChar;
 
-    switch (scaleFactor) {
+    switch (xScaleFactor) {
     case 1:  break; /* outrow set to inrow */
-    case 2:  /* Make outrow using prefabricated parts (same for 3, 5). */ 
-        for (colChar = 0; colChar < inColChars; ++colChar) { 
-            outrow[colChar*2]   = dbl[(inrow[colChar] & 0xF0) >> 4];
-            outrow[colChar*2+1] = dbl[(inrow[colChar] & 0x0F) >> 0];
+
+    case 2:  /* Make outrow using prefabricated parts (same for 3, 5). */
+        for (colChar = 0; colChar < inColChars; ++colChar) {
+            unsigned char const inrowChar = inrow[colChar];
+            outrow[colChar*2]   = dbl[ inrowChar >> 4];
+            outrow[colChar*2+1] = dbl[(inrowChar & 0x0F) >> 0];
             /* Possible outrow overrun by one byte. */
         }
         break;
 
     case 3:
-        for (colChar = 0; colChar < inColChars; ++colChar) { 
-            outrow[colChar*3]   = trp1[(inrow[colChar] & 0xF0) >> 5];
-            outrow[colChar*3+1] = trp2[(inrow[colChar] >> 2) & 0x0F];
-            outrow[colChar*3+2] = trp3[(inrow[colChar] >> 0) & 0x07];
+        for (colChar = 0; colChar < inColChars; ++colChar) {
+            unsigned char const inrowChar = inrow[colChar];
+            outrow[colChar*3]   = trp1[ inrowChar >> 5];
+            outrow[colChar*3+1] = trp2[(inrowChar >> 2) & 0x0F];
+            outrow[colChar*3+2] = trp3[(inrowChar >> 0) & 0x07];
         }
-        break;  
+        break;
 
     case 4:
         for (colChar = 0; colChar < inColChars; ++colChar) {
+            unsigned char const inrowChar = inrow[colChar];
             unsigned int i;
-            for (i = 0; i < 4; ++i) 
-                outrow[colChar*4+i]=
-                    quad[(inrow[colChar] >> (6 - 2 * i)) & 0x03]; 
+            for (i = 0; i < 4; ++i)
+                outrow[colChar*4+i] =
+                    quad[(inrowChar >> (6 - 2 * i)) & 0x03];
         }
         break;
 
     case 5:
-        for (colChar = 0; colChar < inColChars; ++colChar) { 
-            outrow[colChar*5]   = pair [(inrow[colChar] >> 6) & 0x03] >> 5; 
-            outrow[colChar*5+1] = quin2[(inrow[colChar] >> 4) & 0x07] >> 0; 
-            outrow[colChar*5+2] = quad [(inrow[colChar] >> 3) & 0x03] >> 0; 
-            outrow[colChar*5+3] = quin4[(inrow[colChar] >> 1) & 0x07] >> 0;
-            outrow[colChar*5+4] = pair [(inrow[colChar] >> 0) & 0x03] >> 3; 
+        for (colChar = 0; colChar < inColChars; ++colChar) {
+            unsigned char const inrowChar = inrow[colChar];
+            outrow[colChar*5]   = pair [4][(inrowChar >> 6) & 0x03];
+            outrow[colChar*5+1] = quin2[(inrowChar >> 4) & 0x07] >> 0;
+            outrow[colChar*5+2] = quad [(inrowChar >> 3) & 0x03] >> 0;
+            outrow[colChar*5+3] = quin4[(inrowChar >> 1) & 0x07] >> 0;
+            outrow[colChar*5+4] = pair [2][(inrowChar >> 0) & 0x03];
         }
         break;
 
-    case 6:  /* Compound of 2 and 3 */ 
-        for (colChar = 0; colChar < inColChars; ++colChar) { 
-            unsigned char const hi = dbl[(inrow[colChar] & 0xF0) >> 4];
-            unsigned char const lo = dbl[(inrow[colChar] & 0x0F) >> 0];
+    case 6:  /* Compound of 2 and 3 */
+        for (colChar = 0; colChar < inColChars; ++colChar) {
+            unsigned char const inrowChar = inrow[colChar];
+            unsigned char const hi = dbl[(inrowChar & 0xF0) >> 4];
+            unsigned char const lo = dbl[(inrowChar & 0x0F) >> 0];
 
-            outrow[colChar*6]   = trp1[(hi & 0xF0) >> 5];
+            outrow[colChar*6]   = trp1[hi >> 5];
             outrow[colChar*6+1] = trp2[(hi >> 2) & 0x0F];
             outrow[colChar*6+2] = trp3[hi & 0x07];
 
-            outrow[colChar*6+3] = trp1[(lo & 0xF0) >> 5];
+            outrow[colChar*6+3] = trp1[lo >> 5];
             outrow[colChar*6+4] = trp2[(lo >> 2) & 0x0F];
             outrow[colChar*6+5] = trp3[lo & 0x07];
         }
-        break;  
+        break;
 
     case 7:
-        for (colChar = 0; colChar < inColChars; ++colChar) { 
+        /* This approach can be used for other scale values.
+           Good for architectures which provide wide registers
+           such as SSE.
+        */
+        for (colChar = 0; colChar < inColChars; ++colChar) {
+            unsigned char const inrowChar = inrow[colChar];
             uint32_t hi, lo;
 
-            hi = inrow[colChar] >> 4;
+            hi = inrowChar >> 4;
             hi = ((((hi>>1) * 0x00082080) | (0x01 & hi)) & 0x00204081 ) * 0x7F;
             hi >>= 4;
             outrow[colChar*7]   =  (unsigned char) ( hi >> 16);
             outrow[colChar*7+1] =  (unsigned char) ((hi >>  8) & 0xFF);
             outrow[colChar*7+2] =  (unsigned char) ((hi >>  0) & 0xFF);
 
-            lo = inrow[colChar] & 0x001F;
+            lo = inrowChar & 0x001F;
             lo = ((((lo>>1) * 0x02082080) | (0x01 & lo)) & 0x10204081 ) * 0x7F;
             outrow[colChar*7+3] =  (unsigned char) ((lo >> 24) & 0xFF);
-            outrow[colChar*7+4] =  (unsigned char) ((lo >> 16) & 0xFF); 
+            outrow[colChar*7+4] =  (unsigned char) ((lo >> 16) & 0xFF);
             outrow[colChar*7+5] =  (unsigned char) ((lo >>  8) & 0xFF);
             outrow[colChar*7+6] =  (unsigned char) ((lo >>  0) & 0xFF);
         }
         break;
 
     case 8:
-        for (colChar = 0; colChar < inColChars; ++colChar) { 
+        for (colChar = 0; colChar < inColChars; ++colChar) {
+            unsigned char const inrowChar = inrow[colChar];
             unsigned int i;
             for (i = 0; i < 8; ++i) {
                 outrow[colChar*8+i] =
-                    ((inrow[colChar] >> (7-i)) & 0x01) *0xFF;
+                    ((inrowChar >> (7-i)) & 0x01) *0xFF;
             }
         }
         break;
 
     case 9:
-        for (colChar = 0; colChar < inColChars; ++colChar) { 
-            outrow[colChar*9]   =  ((inrow[colChar] >> 7) & 0x01) * 0xFF;
-            outrow[colChar*9+1] =  pair[(inrow[colChar] >> 6) & 0x03] >> 1; 
-            outrow[colChar*9+2] =  pair[(inrow[colChar] >> 5) & 0x03] >> 2; 
-            outrow[colChar*9+3] =  pair[(inrow[colChar] >> 4) & 0x03] >> 3; 
-            outrow[colChar*9+4] =  pair[(inrow[colChar] >> 3) & 0x03] >> 4; 
-            outrow[colChar*9+5] =  pair[(inrow[colChar] >> 2) & 0x03] >> 5; 
-            outrow[colChar*9+6] =  pair[(inrow[colChar] >> 1) & 0x03] >> 6; 
-            outrow[colChar*9+7] =  pair[(inrow[colChar] >> 0) & 0x03] >> 7; 
-            outrow[colChar*9+8] =  (inrow[colChar] & 0x01) * 0xFF;
+        for (colChar = 0; colChar < inColChars; ++colChar) {
+            unsigned char const inrowChar = inrow[colChar];
+            outrow[colChar*9]   =  ((inrowChar >> 7) & 0x01) * 0xFF;
+            outrow[colChar*9+1] =  pair[0][(inrowChar >> 6) & 0x03];
+            outrow[colChar*9+2] =  pair[1][(inrowChar >> 5) & 0x03];
+            outrow[colChar*9+3] =  pair[2][(inrowChar >> 4) & 0x03];
+            outrow[colChar*9+4] =  pair[3][(inrowChar >> 3) & 0x03];
+            outrow[colChar*9+5] =  pair[4][(inrowChar >> 2) & 0x03];
+            outrow[colChar*9+6] =  pair[5][(inrowChar >> 1) & 0x03];
+            outrow[colChar*9+7] =  pair[6][(inrowChar >> 0) & 0x03];
+            outrow[colChar*9+8] =  (inrowChar & 0x01) * 0xFF;
         }
         break;
 
     case 10:
-        for (colChar = 0; colChar < inColChars; ++colChar) { 
-            outrow[colChar*10]   = ((inrow[colChar] >> 7) & 0x01 ) * 0xFF;
-            outrow[colChar*10+1] = pair[(inrow[colChar] >> 6) & 0x03] >> 2; 
-            outrow[colChar*10+2] = pair[(inrow[colChar] >> 5) & 0x03] >> 4; 
-            outrow[colChar*10+3] = pair[(inrow[colChar] >> 4) & 0x03] >> 6;
-            outrow[colChar*10+4] = ((inrow[colChar] >> 4) & 0x01) * 0xFF;
-            outrow[colChar*10+5] = ((inrow[colChar] >> 3) & 0x01) * 0xFF; 
-            outrow[colChar*10+6] = pair[(inrow[colChar] >> 2) & 0x03] >> 2; 
-            outrow[colChar*10+7] = pair[(inrow[colChar] >> 1) & 0x03] >> 4; 
-            outrow[colChar*10+8] = pair[(inrow[colChar] >> 0) & 0x03] >> 6; 
-            outrow[colChar*10+9] = ((inrow[colChar] >> 0) & 0x01) * 0xFF;
+        for (colChar = 0; colChar < inColChars; ++colChar) {
+            unsigned char const inrowChar = inrow[colChar];
+            outrow[colChar*10]   = ((inrowChar >> 7) & 0x01 ) * 0xFF;
+            outrow[colChar*10+1] = pair[1][(inrowChar >> 6) & 0x03];
+            outrow[colChar*10+2] = quad[(inrowChar >> 5) & 0x03];
+            outrow[colChar*10+3] = pair[5][(inrowChar >> 4) & 0x03];
+            outrow[colChar*10+4] = ((inrowChar >> 4) & 0x01) * 0xFF;
+            outrow[colChar*10+5] = ((inrowChar >> 3) & 0x01) * 0xFF;
+            outrow[colChar*10+6] = pair[1][(inrowChar >> 2) & 0x03];
+            outrow[colChar*10+7] = quad[(inrowChar >> 1) & 0x03];
+            outrow[colChar*10+8] = pair[5][(inrowChar >> 0) & 0x03];
+            outrow[colChar*10+9] = ((inrowChar >> 0) & 0x01) * 0xFF;
+        }
+        break;
+
+    case 11:
+        for (colChar = 0; colChar < inColChars; ++colChar) {
+            unsigned char const inrowChar = inrow[colChar];
+            outrow[colChar*11]   = ((inrowChar >> 7) & 0x01 ) * 0xFF;
+            outrow[colChar*11+1] = pair[2][(inrowChar >> 6) & 0x03];
+            outrow[colChar*11+2] = pair[5][(inrowChar >> 5) & 0x03];
+            outrow[colChar*11+3] = ((inrowChar >> 5) & 0x01) * 0xFF;
+            outrow[colChar*11+4] = pair[0][(inrowChar >> 4) & 0x03];
+            outrow[colChar*11+5] = quad[(inrowChar >> 3) & 0x03];
+            outrow[colChar*11+6] = pair[6][(inrowChar >> 2) & 0x03];
+            outrow[colChar*11+7] = ((inrowChar >> 2) & 0x01) * 0xFF;
+            outrow[colChar*11+8] = pair[1][(inrowChar >> 1) & 0x03];
+            outrow[colChar*11+9] = pair[4][(inrowChar >> 0) & 0x03];
+            outrow[colChar*11+10] = ((inrowChar >> 0) & 0x01) * 0xFF;
+        }
+        break;
+
+    case 12:
+        for (colChar = 0; colChar < inColChars; ++colChar) {
+            unsigned char const inrowChar = inrow[colChar];
+            outrow[colChar*12+ 0] = ((inrowChar >> 7) & 0x01) * 0xFF;
+            outrow[colChar*12+ 1] = quad[(inrowChar >> 6) & 0x03];
+            outrow[colChar*12+ 2] = ((inrowChar >> 6) & 0x01) * 0xFF;
+            outrow[colChar*12+ 3] = ((inrowChar >> 5) & 0x01) * 0xFF;
+            outrow[colChar*12+ 4] = quad[(inrowChar >> 4) & 0x03];
+            outrow[colChar*12+ 5] = ((inrowChar >> 4) & 0x01) * 0xFF;
+            outrow[colChar*12+ 6] = ((inrowChar >> 3) & 0x01) * 0xFF;
+            outrow[colChar*12+ 7] = quad[(inrowChar >> 2) & 0x03];
+            outrow[colChar*12+ 8] = ((inrowChar >> 2) & 0x01) * 0xFF;
+            outrow[colChar*12+ 9] = ((inrowChar >> 1) & 0x01) * 0xFF;
+            outrow[colChar*12+10] = quad[(inrowChar >> 0) & 0x03];
+            outrow[colChar*12+11] = ((inrowChar >> 0) & 0x01) * 0xFF;
+        }
+        break;
+
+    case 13:
+      /* Math quiz: 13 is the last entry here.
+         Is this an arbitrary choice?
+         Or is there something which makes 13 necessary?
+
+         If you like working on questions like this you may like
+         number/group theory.  However don't expect a straightforward
+         answer from a college math textbook.  - afu
+      */
+         for (colChar = 0; colChar < inColChars; ++colChar) {
+            unsigned char const inrowChar = inrow[colChar];
+            outrow[colChar*13+ 0] = ((inrowChar >> 7) & 0x01) * 0xFF;
+            outrow[colChar*13+ 1] = pair[4][(inrowChar >> 6) & 0x03];
+            outrow[colChar*13+ 2] = ((inrowChar >> 6) & 0x01) * 0xFF;
+            outrow[colChar*13+ 3] = pair[1][(inrowChar >> 5) & 0x03];
+            outrow[colChar*13+ 4] = pair[6][(inrowChar >> 4) & 0x03];
+            outrow[colChar*13+ 5] = ((inrowChar >> 4) & 0x01) * 0xFF;
+            outrow[colChar*13+ 6] = quad[(inrowChar >> 3) & 0x03];
+            outrow[colChar*13+ 7] = ((inrowChar >> 3) & 0x01) * 0xFF;
+            outrow[colChar*13+ 8] = pair[0][(inrowChar >> 2) & 0x03];
+            outrow[colChar*13+ 9] = pair[5][(inrowChar >> 1) & 0x03];
+            outrow[colChar*13+10] = ((inrowChar >> 1) & 0x01) * 0xFF;
+            outrow[colChar*13+11] = pair[2][(inrowChar >> 0) & 0x03];
+            outrow[colChar*13+12] = ((inrowChar >> 0) & 0x01) * 0xFF;
         }
         break;
- 
 
     default:
-        /*  Unlike the above cases, we iterate through outrow.  To compute the
-            color composition of each outrow byte, we consult a single bit or
-            two consecutive bits in inrow.
+        pm_error("Internal error.  Invalid scale factor for "
+                 "enlargePbmRowHorizontallySmall: %u", xScaleFactor);
+    }
+}
+
+
+/*
+  General method for scale values 14 and above
+
+  First notice that all output characters are either entirely 0, entirely 1
+  or a combination with the change from 0->1 or 1->0 happening only once.
+  (Sequences like 00111000 never appear when scale value is above 8).
+
+  Let us call the chars which are entirely 0 or 1 "solid" and those which
+  may potentially contain both "transitional".   For scale values 6 - 14
+  each input character expands to output characters aligned as follows:
+
+  6 : TTTTTT
+  7 : TTTTTTT
+  8 : SSSSSSSS
+  9 : STTTTTTTS
+  10: STSTSSTSTS
+  11: STTSTTTSTTS
+  12: STSSTSSTSSTS
+  13: STSTTSTSTTSTS
+  14: STSTSTSSTSTSTS
+
+  Above 15 we insert strings of solid chars as necessary:
+
+  22: SsTSsTSsTSsSsTSsTSsTSs
+  30: SssTSssTSssTSssSssTSssTSssTSss
+  38: SsssTSsssTSsssTSsssSsssTSsssTSsssTSsss
+*/
 
-            Color changes never happen twice in a single outrow byte.
 
-            This is a generalization of above routines for scale factors
-            9 and 10.
+struct OffsetInit {
+  unsigned int scale;
+  const char * alignment;
+};
+
+
+static struct OffsetInit const offsetInit[8] = {
+  /* 0: single char copied from output of enlargePbmRowHorizontallySmall()
+     1: stretch of solid chars
+
+     Each entry is symmetrical left-right and has exactly eight '2's
+   */
+
+  {  8, "22222222" },               /* 8n+0 */
+  {  9, "21121212121212112" },      /* 8n+1 */
+  { 10, "211212112211212112" },     /* 8n+2 */
+  { 11, "2112121121211212112" },    /* 8n+3 */
+  {  4, "212212212212" },           /* 8n+4 */
+  { 13, "211211211212112112112" },  /* 8n+5 */
+  {  6, "21212122121212" },         /* 8n+6 */
+  {  7, "212121212121212" }         /* 8n+7 */
+};
+
+  /*   Relationship between 'S' 'T' in previous comment and '1' '2' here
+
+     11: STTSTTTSTTS
+     19: sSTsTsSTsTsTSsTsTSs
+         2112121121211212112           # table entry for 8n+3
+     27: ssSTssTssSTssTssTSssTssTSss
+         2*112*12*112*12*112*12*112*
+     35: sssSTsssTsssSTsssTsssTSsssTsssTSsss
+         2**112**12**112**12**112**12**112**
+     42: ssssSTssssTssssSTssssTssssTSssssTssssTSssss
+         2***112***12***112***12***112***12***112***
+  */
+
+
+struct OffsetTable {
+    unsigned int offsetSolid[8];
+    unsigned int offsetTrans[13];
+    unsigned int scale;
+    unsigned int solidChars;
+};
+
 
-            Logic works for scale factors 4, 6, 7, 8, and above (but not 5).
+
+static void
+setupOffsetTable(unsigned int         const xScaleFactor,
+                 struct OffsetTable * const offsetTableP) {
+
+    unsigned int i, j0, j1, dest;
+    struct OffsetInit const classEntry = offsetInit[xScaleFactor % 8];
+    unsigned int const scale = classEntry.scale;
+    unsigned int const solidChars = xScaleFactor / 8 - (scale > 8 ? 1 : 0);
+
+    for (i = j0 = j1 = dest = 0; classEntry.alignment[i] != '\0'; ++i) {
+      switch (classEntry.alignment[i]) {
+        case '1': offsetTableP->offsetTrans[j0++] = dest++;
+                  break;
+
+        case '2': offsetTableP->offsetSolid[j1++] = dest;
+                  dest += solidChars;
+                  break;
+
+        default:  pm_error("Internal error. Abnormal alignment value");
+        }
+    }
+
+    offsetTableP->scale = scale;
+    offsetTableP->solidChars = solidChars;
+}
+
+
+
+static void
+enlargePbmRowFractional(unsigned char         const inrowChar,
+                        unsigned int          const outColChars,
+                        unsigned int          const xScaleFactor,
+                        unsigned char       * const outrow,
+                        struct OffsetTable  * const tableP) {
+
+/*----------------------------------------------------------------------------
+  Routine called from enlargePbmRowHorizontallyGen() to process the final
+  fractional inrow char.
+
+  outrow : write position for this function (not left edge of entire row)
+----------------------------------------------------------------------------*/
+
+    unsigned int i;
+
+    /* Deploy (most) solid chars */
+
+    for (i = 0; i < 7; ++i) {
+        unsigned int j;
+        unsigned char const bit8 = (inrowChar >> (7 - i) & 0x01) * 0xFF;
+
+        if (tableP->offsetSolid[i] >= outColChars)
+            break;
+        else
+            for (j = 0; j < tableP->solidChars; ++j) {
+                outrow[j + tableP->offsetSolid[i]] = bit8;
+            }
+     }
+    /* If scale factor is a multiple of 8 we are done. */
+
+    if (tableP->scale != 8) {
+        unsigned char outrowTemp[16];
+
+        enlargePbmRowHorizontallySmall(&inrowChar, 1,
+                                       tableP->scale, outrowTemp);
+
+        for (i = 0 ; i < tableP->scale; ++i) {
+            unsigned int const offset = tableP->offsetTrans[i];
+            if (offset >= outColChars)
+                break;
+            else
+                outrow[offset] = outrowTemp[i];
+            }
+
+    }
+
+}
+
+
+
+static void
+enlargePbmRowHorizontallyGen(const unsigned char * const inrow,
+                             unsigned int          const inColChars,
+                             unsigned int          const outColChars,
+                             unsigned int          const xScaleFactor,
+                             unsigned char       * const outrow,
+                             struct OffsetTable  * const tableP) {
+
+/*----------------------------------------------------------------------------
+  We iterate through inrow.
+  Output chars are deployed according to offsetTable.
+
+  All transitional chars and some solid chars are determined by calling
+  one the fast routines in enlargePbmRowHorizontallySmall().
+----------------------------------------------------------------------------*/
+    unsigned int colChar;
+    unsigned int const fullColChars =
+        inColChars - ((inColChars * xScaleFactor == outColChars) ? 0 : 1);
+
+    for (colChar = 0; colChar < fullColChars; ++colChar) {
+        unsigned char const inrowChar = inrow[colChar];
+        char bit8[8];
+        unsigned int i;
+
+        /* Deploy most solid chars
+           Some scale factors yield uneven string lengths: in such
+           cases we don't handle the odd solid chars at this point
         */
 
-        for (colChar = 0; colChar < outColChars; ++colChar) {
-            unsigned int const mult = scaleFactor;
-            unsigned int const mod = colChar % mult;
-            unsigned int const bit = (mod*8)/mult;
-            /* source bit position, leftmost=0 */
-            unsigned int const offset = mult - (mod*8)%mult;
-            /* number of outrow bits derived from the same
-               "source" inrow bit, starting at and to the right
-               of leftmost bit of outrow byte, inclusive
-            */
-
-            if (offset >= 8)  /* Bits in outrow byte are all 1 or 0 */
-                outrow[colChar] =
-                    (inrow[colChar/mult] >> (7-bit) & 0x01) * 0xFF;
-            else           /* Two inrow bits influence this outrow byte */ 
-                outrow[colChar] = (unsigned char)
-                    (pair[inrow[colChar/mult] >> (6-bit) & 0x03] >> offset)
-                    & 0xFF;
+        for (i = 0; i < 8; ++i)
+            bit8[i] = (inrowChar >> (7 - i) & 0x01) * 0xFF;
+
+        for (i = 0; i < tableP->solidChars; ++i) {
+            unsigned int base = colChar * xScaleFactor + i;
+            outrow[base]              = bit8[0];
+            outrow[base + tableP->offsetSolid[1]] = bit8[1];
+            outrow[base + tableP->offsetSolid[2]] = bit8[2];
+            outrow[base + tableP->offsetSolid[3]] = bit8[3];
+            outrow[base + tableP->offsetSolid[4]] = bit8[4];
+            outrow[base + tableP->offsetSolid[5]] = bit8[5];
+            outrow[base + tableP->offsetSolid[6]] = bit8[6];
+            outrow[base + tableP->offsetSolid[7]] = bit8[7];
+        }
+
+        /* If scale factor is a multiple of 8 we are done */
+
+        if (tableP->scale != 8) {
+            /* Deploy transitional chars and any remaining solid chars */
+            unsigned char outrowTemp[16];
+            unsigned int base = colChar * xScaleFactor;
+
+            enlargePbmRowHorizontallySmall(&inrowChar, 1,
+                                           tableP->scale, outrowTemp);
+
+            /* There are at least 4 valid entries in offsetTrans[] */
+
+            outrow[base + tableP->offsetTrans[0]] = outrowTemp[0];
+            outrow[base + tableP->offsetTrans[1]] = outrowTemp[1];
+            outrow[base + tableP->offsetTrans[2]] = outrowTemp[2];
+            outrow[base + tableP->offsetTrans[3]] = outrowTemp[3];
+
+            for (i = 4; i < tableP->scale; ++i)
+                outrow[base + tableP->offsetTrans[i]] = outrowTemp[i];
         }
     }
+
+    /* Process the fractional final inrow byte */
+
+     if (fullColChars < inColChars) {
+        unsigned int  const start = fullColChars * xScaleFactor;
+        unsigned char const inrowLast = inrow[inColChars -1];
+
+        enlargePbmRowFractional(inrowLast, outColChars - start,
+                                xScaleFactor, &outrow[start], tableP);
+        }
+
 }
 
 
 
 static void
 enlargePbm(struct pam * const inpamP,
-           unsigned int const scaleFactor,
+           unsigned int const xScaleFactor,
+           unsigned int const yScaleFactor,
            FILE *       const ofP) {
 
-    unsigned char * inrow;
-    unsigned char * outrow;
+    enum ScaleMethod {METHOD_USEINPUT, METHOD_SMALL, METHOD_GENERAL};
+    enum ScaleMethod const scaleMethod =
+        xScaleFactor == 1  ? METHOD_USEINPUT :
+        xScaleFactor <= 13 ? METHOD_SMALL : METHOD_GENERAL;
 
-    unsigned int row;
-
-    unsigned int const outcols = inpamP->width * scaleFactor;
-    unsigned int const outrows = inpamP->height * scaleFactor;
+    unsigned int const outcols = inpamP->width * xScaleFactor;
+    unsigned int const outrows = inpamP->height * yScaleFactor;
     unsigned int const inColChars  = pbm_packed_bytes(inpamP->width);
     unsigned int const outColChars = pbm_packed_bytes(outcols);
 
+    unsigned char * inrow;
+    unsigned char * outrow;
+    unsigned int row;
+    struct OffsetTable offsetTable;
+
     inrow  = pbm_allocrow_packed(inpamP->width);
-    
-    if (scaleFactor == 1)
+
+    if (scaleMethod == METHOD_USEINPUT)
         outrow = inrow;
-    else  {
-        /* Allow writes beyond outrow data end when scaleFactor is
-           one of the special fast cases: 2, 3, 4, 5, 6, 7, 8, 9, 10.
+    else {
+        /* Allow writes beyond outrow data end when using the table method.
         */
-        unsigned int const rightPadding = 
-            scaleFactor > 10 ? 0 : (scaleFactor - 1) * 8;  
+        unsigned int const rightPadding =
+            scaleMethod == METHOD_GENERAL ? 0 : (xScaleFactor - 1) * 8;
+
         outrow = pbm_allocrow_packed(outcols + rightPadding);
+
+        if (scaleMethod == METHOD_GENERAL)
+            setupOffsetTable(xScaleFactor, &offsetTable);
     }
 
     pbm_writepbminit(ofP, outcols, outrows, 0);
-    
+
     for (row = 0; row < inpamP->height; ++row) {
         unsigned int i;
 
         pbm_readpbmrow_packed(inpamP->file, inrow, inpamP->width,
                               inpamP->format);
 
-        if (outcols % 8 > 0)           /* clean final partial byte */ 
+        if (outcols % 8 > 0)           /* clean final partial byte */
             pbm_cleanrowend_packed(inrow, inpamP->width);
 
-        enlargePbmRowHorizontally(inpamP, inrow, inColChars, outColChars,
-                                  scaleFactor, outrow);
+        switch (scaleMethod) {
+        case METHOD_USEINPUT:
+            /* Nothing to do */
+            break;
+        case METHOD_SMALL:
+            enlargePbmRowHorizontallySmall(inrow, inColChars,
+                                           xScaleFactor, outrow);
+            break;
+        case METHOD_GENERAL:
+            enlargePbmRowHorizontallyGen(inrow, inColChars, outColChars,
+                                         xScaleFactor, outrow,
+                                         &offsetTable);
+            break;
+        }
 
-        for (i = 0; i < scaleFactor; ++i)  
+        for (i = 0; i < yScaleFactor; ++i)
             pbm_writepbmrow_packed(ofP, outrow, outcols, 0);
     }
-    
+
     if (outrow != inrow)
         pbm_freerow(outrow);
 
@@ -332,7 +736,8 @@ enlargePbm(struct pam * const inpamP,
 
 static void
 enlargeGeneral(struct pam * const inpamP,
-               unsigned int const scaleFactor,
+               unsigned int const xScaleFactor,
+               unsigned int const yScaleFactor,
                FILE *       const ofP) {
 /*----------------------------------------------------------------------------
    Enlarge the input image described by *pamP.
@@ -342,15 +747,15 @@ enlargeGeneral(struct pam * const inpamP,
    This works on all kinds of images, but is slower than enlargePbm on
    PBM.
 -----------------------------------------------------------------------------*/
-    struct pam outpam; 
+    struct pam outpam;
     tuple * tuplerow;
     tuple * newtuplerow;
     unsigned int row;
 
-    outpam = *inpamP; 
+    outpam = *inpamP;
     outpam.file   = ofP;
-    outpam.width  = inpamP->width  * scaleFactor;
-    outpam.height = inpamP->height * scaleFactor; 
+    outpam.width  = inpamP->width  * xScaleFactor;
+    outpam.height = inpamP->height * yScaleFactor;
 
     pnm_writepaminit(&outpam);
 
@@ -360,7 +765,7 @@ enlargeGeneral(struct pam * const inpamP,
 
     for (row = 0; row < inpamP->height; ++row) {
         pnm_readpamrow(inpamP, tuplerow);
-        pnm_writepamrowmult(&outpam, newtuplerow, scaleFactor);
+        pnm_writepamrowmult(&outpam, newtuplerow, yScaleFactor);
     }
 
     free(newtuplerow);
@@ -371,10 +776,10 @@ enlargeGeneral(struct pam * const inpamP,
 
 
 int
-main(int           argc, 
-     const char ** argv) {
+main(int           argc,
+     const char ** const argv) {
 
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
     FILE * ifP;
     struct pam inpam;
 
@@ -383,16 +788,21 @@ main(int           argc,
     parseCommandLine(argc, argv, &cmdline);
 
     ifP = pm_openr(cmdline.inputFilespec);
- 
+
     pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
-    
+
+    assert(cmdline.xScaleFactor > 0);
+    assert(cmdline.yScaleFactor > 0);
+
     validateComputableDimensions(inpam.width, inpam.height,
-                                 cmdline.scaleFactor); 
-    
+                                 cmdline.xScaleFactor, cmdline.yScaleFactor);
+
     if (PNM_FORMAT_TYPE(inpam.format) == PBM_TYPE)
-        enlargePbm(&inpam, cmdline.scaleFactor, stdout);
+        enlargePbm(&inpam, cmdline.xScaleFactor, cmdline.yScaleFactor,
+                   stdout);
     else
-        enlargeGeneral(&inpam, cmdline.scaleFactor, stdout);
+        enlargeGeneral(&inpam, cmdline.xScaleFactor, cmdline.yScaleFactor,
+                       stdout);
 
     pm_close(ifP);
     pm_close(stdout);
@@ -400,3 +810,4 @@ main(int           argc,
     return 0;
 }
 
+
diff --git a/editor/pamflip/flip.h b/editor/pamflip/flip.h
index 612a7f84..ace93044 100644
--- a/editor/pamflip/flip.h
+++ b/editor/pamflip/flip.h
@@ -1,7 +1,7 @@
 #ifndef FLIP_H_INCLUDED
 #define FLIP_H_INCLUDED
 
-struct xformCore {
+struct XformCore {
     /* a b
        c d
     */
diff --git a/editor/pamflip/pamflip.c b/editor/pamflip/pamflip.c
index 149ab310..bc752208 100644
--- a/editor/pamflip/pamflip.c
+++ b/editor/pamflip/pamflip.c
@@ -13,9 +13,9 @@
 /*
    transformNonPbmChunk() is the general transformation function.
    It can transform anything, albeit slowly and expensively.
-   
+
    The following are enhancements for specific cases:
-   
+
      transformRowByRowPbm():
        PBM image with left-right or null transformation
      transformRowsBottomTopPbm()
@@ -58,6 +58,7 @@
    the source image, reading the input image through multiple times.
 */
 
+#define _DEFAULT_SOURCE 1  /* New name for SVID & BSD source defines */
 #define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
 #define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
 
@@ -76,12 +77,12 @@
 #include "flip.h"
 #include "pamflip_sse.h"
 
-enum xformType {LEFTRIGHT, TOPBOTTOM, TRANSPOSE};
+enum XformType {LEFTRIGHT, TOPBOTTOM, TRANSPOSE};
 
 static void
 parseXformOpt(const char *     const xformOpt,
               unsigned int  *  const xformCountP,
-              enum xformType * const xformList) {
+              enum XformType * const xformList) {
 /*----------------------------------------------------------------------------
    Translate the -xform option string into an array of transform types.
 
@@ -92,10 +93,10 @@ parseXformOpt(const char *     const xformOpt,
     char * xformOptWork;
     char * cursor;
     bool eol;
-    
+
     xformOptWork = strdup(xformOpt);
     cursor = &xformOptWork[0];
-    
+
     eol = FALSE;    /* initial value */
     xformCount = 0; /* initial value */
     while (!eol && xformCount < 10) {
@@ -124,13 +125,13 @@ parseXformOpt(const char *     const xformOpt,
 
 
 /* See transformPoint() for an explanation of the transform matrix types.  The
-   difference between xformCore and xformMatrix is that 'xformCore' is
+   difference between XformCore and XformMatrix is that 'XformCore' is
    particular to the source image dimensions and can be used to do the
-   transformation, while 'xformCore' is independent of the source image and
+   transformation, while 'XformCore' is independent of the source image and
    just tells what kind of transformation.
 */
 
-struct xformMatrix {
+struct XformMatrix {
     /* a b 0
        c d 0
        e f 1
@@ -146,7 +147,7 @@ struct xformMatrix {
 
 
 static void
-leftright(struct xformCore * const xformP) {
+leftright(struct XformCore * const xformP) {
     xformP->a = - xformP->a;
     xformP->c = - xformP->c;
 }
@@ -154,7 +155,7 @@ leftright(struct xformCore * const xformP) {
 
 
 static void
-topbottom(struct xformCore * const xformP) {
+topbottom(struct XformCore * const xformP) {
     xformP->b = - xformP->b;
     xformP->d = - xformP->d;
 }
@@ -173,7 +174,7 @@ swap(int * const xP, int * const yP) {
 
 
 static void
-transpose(struct xformCore * const xformP) {
+transpose(struct XformCore * const xformP) {
     swap(&xformP->a, &xformP->b);
     swap(&xformP->c, &xformP->d);
 }
@@ -182,18 +183,18 @@ transpose(struct xformCore * const xformP) {
 
 static void
 computeXformCore(unsigned int       const xformCount,
-                 enum xformType     const xformType[],
-                 struct xformCore * const xformP) {
-    
-    struct xformCore const nullTransform = {1, 0, 0, 1};
+                 enum XformType     const XformType[],
+                 struct XformCore * const xformP) {
+
+    struct XformCore const nullTransform = {1, 0, 0, 1};
 
     unsigned int i;
 
     *xformP = nullTransform;   /* initial value */
 
     for (i = 0; i < xformCount; ++i) {
-        switch (xformType[i]) {
-        case LEFTRIGHT: 
+        switch (XformType[i]) {
+        case LEFTRIGHT:
             leftright(xformP);
             break;
         case TOPBOTTOM:
@@ -209,7 +210,7 @@ computeXformCore(unsigned int       const xformCount,
 
 
 static void
-xformDimensions(struct xformCore const xform,
+xformDimensions(struct XformCore const xform,
                 unsigned int     const inCols,
                 unsigned int     const inRows,
                 unsigned int *   const outColsP,
@@ -221,25 +222,25 @@ xformDimensions(struct xformCore const xform,
    E.g. if it's a 90 degree rotation of a 10 x 20 image, the output is
    a 20 x 10 image.
 -----------------------------------------------------------------------------*/
-    *outColsP = abs(xform.a * inCols + xform.c * inRows);
-    *outRowsP = abs(xform.b * inCols + xform.d * inRows);
+    *outColsP = abs(xform.a * (int)inCols + xform.c * (int)inRows);
+    *outRowsP = abs(xform.b * (int)inCols + xform.d * (int)inRows);
 }
 
 
 
 static void
-computeXformMatrix(struct xformMatrix * const xformP, 
+computeXformMatrix(struct XformMatrix * const xformP,
                    unsigned int         const sourceCols,
                    unsigned int         const sourceRows,
-                   struct xformCore     const xformCore) {
+                   struct XformCore     const XformCore) {
 
-    int colMax = xformCore.a * (sourceCols-1) + xformCore.c * (sourceRows-1);
-    int rowMax = xformCore.b * (sourceCols-1) + xformCore.d * (sourceRows-1);
+    int colMax = XformCore.a * (sourceCols-1) + XformCore.c * (sourceRows-1);
+    int rowMax = XformCore.b * (sourceCols-1) + XformCore.d * (sourceRows-1);
 
-    xformP->a = xformCore.a;
-    xformP->b = xformCore.b;
-    xformP->c = xformCore.c;
-    xformP->d = xformCore.d;
+    xformP->a = XformCore.a;
+    xformP->b = XformCore.b;
+    xformP->c = XformCore.c;
+    xformP->d = XformCore.d;
     xformP->e = colMax < 0 ? -colMax : 0;
     xformP->f = rowMax < 0 ? -rowMax : 0;
 }
@@ -251,7 +252,7 @@ struct cmdlineInfo {
        in a form easy for the program to use.
     */
     const char * inputFilespec;  /* Filespec of input file */
-    struct xformCore xform;
+    struct XformCore xform;
         /* Handy mathematical representation of all of transform options */
     size_t availableMemory;
     unsigned int pageSize;
@@ -271,7 +272,7 @@ interpretMemorySize(unsigned int const memsizeSpec,
     if (memsizeSpec) {
         if (memsizeOpt > sizeMax / Meg)
             pm_error("-memsize value too large: %u MiB.  Maximum this program "
-                     "can handle is %u MiB", 
+                     "can handle is %u MiB",
                      memsizeOpt, (unsigned)sizeMax / Meg);
         *availableMemoryP = memsizeOpt * Meg;
     } else
@@ -299,8 +300,8 @@ parseCommandLine(int argc, char ** const argv,
     unsigned int memsizeOpt;
     const char * xformOpt;
     unsigned int xformCount;
-        /* Number of transforms in the 'xformType' array */
-    enum xformType xformList[10];
+        /* Number of transforms in the 'XformType' array */
+    enum XformType xformList[10];
         /* Array of transforms to be applied, in order */
 
     MALLOCARRAY(option_def, 100);
@@ -322,11 +323,11 @@ parseCommandLine(int argc, char ** const argv,
     OPTENT3(0, "cw",        OPT_FLAG,    NULL, &r270,    0);
     OPTENT3(0, "null",      OPT_FLAG,    NULL, &null,    0);
     OPTENT3(0, "verbose",   OPT_FLAG,    NULL, &cmdlineP->verbose,       0);
-    OPTENT3(0, "memsize",   OPT_UINT,    &memsizeOpt, 
+    OPTENT3(0, "memsize",   OPT_UINT,    &memsizeOpt,
             &memsizeSpec,       0);
     OPTENT3(0, "pagesize",  OPT_UINT,    &cmdlineP->pageSize,
             &pagesizeSpec,      0);
-    OPTENT3(0, "xform",     OPT_STRING,  &xformOpt, 
+    OPTENT3(0, "xform",     OPT_STRING,  &xformOpt,
             &xformSpec, 0);
 
     opt.opt_table = option_def;
@@ -363,20 +364,20 @@ parseCommandLine(int argc, char ** const argv,
         } else if (null) {
             xformCount = 0;
         }
-    } else if (xformSpec) 
+    } else if (xformSpec)
         parseXformOpt(xformOpt, &xformCount, xformList);
     else
         pm_error("You must specify an option such as -topbottom to indicate "
                  "what kind of flip you want.");
 
     computeXformCore(xformCount, xformList, &cmdlineP->xform);
-    
+
     interpretMemorySize(memsizeSpec, memsizeOpt, &cmdlineP->availableMemory);
 
     if (!pagesizeSpec)
-        cmdlineP->pageSize = 4*1024;         
+        cmdlineP->pageSize = 4*1024;
 
-    if (argc-1 == 0) 
+    if (argc-1 == 0)
         cmdlineP->inputFilespec = "-";
     else if (argc-1 != 1)
         pm_error("Program takes zero or one argument (filename).  You "
@@ -390,7 +391,7 @@ parseCommandLine(int argc, char ** const argv,
 
 
 static void
-bitOrderReverse(unsigned char * const bitrow, 
+bitOrderReverse(unsigned char * const bitrow,
                 unsigned int    const cols) {
 /*----------------------------------------------------------------------------
   Reverse the bits in a packed pbm row (1 bit per pixel).  I.e. the leftmost
@@ -412,12 +413,12 @@ bitOrderReverse(unsigned char * const bitrow,
             if ((bitrow[j] | bitrow[i]) == 0) {
                 /* Both are 0x00 - skip */
             } else {
-                unsigned char const t = bitreverse[bitrow[j]]; 
+                unsigned char const t = bitreverse[bitrow[j]];
                 bitrow[j] = bitreverse[bitrow[i]];
                 bitrow[i] = t;
             }
     } else {
-        unsigned int const m = cols % 8; 
+        unsigned int const m = cols % 8;
 
         unsigned int i, j;
             /* Cursors into bitrow[].  i moves from left to center;
@@ -430,7 +431,7 @@ bitOrderReverse(unsigned char * const bitrow,
                 /* Skip if both are 0x00 */
                 th = bitreverse[bitrow[i]];
                 bitrow[i] =
-                    bitreverse[0xff & ((bitrow[j-1] << 8 
+                    bitreverse[0xff & ((bitrow[j-1] << 8
                                         | bitrow[j]) >> (8-m))];
                 bitrow[j] = 0xff & ((th << 8 | tl) >> m);
                 tl = th;
@@ -449,7 +450,7 @@ bitOrderReverse(unsigned char * const bitrow,
 
 
 static void
-transformRowByRowPbm(struct pam * const inpamP, 
+transformRowByRowPbm(struct pam * const inpamP,
                      struct pam * const outpamP,
                      bool         const reverse) {
 /*----------------------------------------------------------------------------
@@ -463,7 +464,7 @@ transformRowByRowPbm(struct pam * const inpamP,
     unsigned char * bitrow;
     unsigned int row;
 
-    bitrow = pbm_allocrow_packed(outpamP->width); 
+    bitrow = pbm_allocrow_packed(outpamP->width);
 
     for (row = 0; row < inpamP->height; ++row) {
         pbm_readpbmrow_packed(inpamP->file,  bitrow, inpamP->width,
@@ -480,7 +481,7 @@ transformRowByRowPbm(struct pam * const inpamP,
 
 
 static void
-transformRowByRowNonPbm(struct pam * const inpamP, 
+transformRowByRowNonPbm(struct pam * const inpamP,
                         struct pam * const outpamP,
                         bool         const reverse) {
 /*----------------------------------------------------------------------------
@@ -499,20 +500,20 @@ transformRowByRowNonPbm(struct pam * const inpamP,
            itself.
         */
     tuple * scratchTuplerow;
-    
+
     unsigned int row;
-    
+
     tuplerow = pnm_allocpamrow(inpamP);
-    
+
     if (reverse) {
         /* Set up newtuplerow[] to point to the tuples of tuplerow[] in
            reverse order.
         */
         unsigned int col;
-        
+
         MALLOCARRAY_NOFAIL(scratchTuplerow, inpamP->width);
 
-        for (col = 0; col < inpamP->width; ++col) 
+        for (col = 0; col < inpamP->width; ++col)
             scratchTuplerow[col] = tuplerow[inpamP->width - col - 1];
         newtuplerow = scratchTuplerow;
     } else {
@@ -523,7 +524,7 @@ transformRowByRowNonPbm(struct pam * const inpamP,
         pnm_readpamrow(inpamP, tuplerow);
         pnm_writepamrow(outpamP, newtuplerow);
     }
-    
+
     if (scratchTuplerow)
         free(scratchTuplerow);
     pnm_freepamrow(tuplerow);
@@ -534,7 +535,7 @@ transformRowByRowNonPbm(struct pam * const inpamP,
 static void
 transformRowsBottomTopPbm(struct pam * const inpamP,
                           struct pam * const outpamP,
-                          bool         const reverse) { 
+                          bool         const reverse) {
 /*----------------------------------------------------------------------------
   Flip a PBM raster top for bottom; read the raster from *inpamP;
   write the flipped raster to *outpamP.
@@ -546,15 +547,15 @@ transformRowsBottomTopPbm(struct pam * const inpamP,
 
     unsigned char ** bitrow;
     unsigned int row;
-        
+
     bitrow = pbm_allocarray_packed(outpamP->width, outpamP->height);
-        
+
     for (row = 0; row < rows; ++row)
-        pbm_readpbmrow_packed(inpamP->file, bitrow[row], 
+        pbm_readpbmrow_packed(inpamP->file, bitrow[row],
                               inpamP->width, inpamP->format);
 
     for (row = 0; row < rows; ++row) {
-        if (reverse) 
+        if (reverse)
             bitOrderReverse(bitrow[rows-row-1], inpamP->width);
 
         pbm_writepbmrow_packed(outpamP->file, bitrow[rows - row - 1],
@@ -566,7 +567,7 @@ transformRowsBottomTopPbm(struct pam * const inpamP,
 
 
 static void
-transformRowsBottomTopNonPbm(struct pam * const inpamP, 
+transformRowsBottomTopNonPbm(struct pam * const inpamP,
                              struct pam * const outpamP) {
 /*----------------------------------------------------------------------------
   Do a simple vertical flip.  Read the raster from *inpamP; write the
@@ -592,17 +593,17 @@ transformRowsBottomTopNonPbm(struct pam * const inpamP,
 
         pnm_writepamrow(outpamP, tuplerow);
     }
-    
+
     pnm_freepamarray(tuplerows, outpamP);
 }
 
 
 
 static void __inline__
-transformPoint(int                const col, 
-               int                const row, 
-               struct xformMatrix const xform, 
-               unsigned int *     const newcolP, 
+transformPoint(int                const col,
+               int                const row,
+               struct XformMatrix const xform,
+               unsigned int *     const newcolP,
                unsigned int *     const newrowP ) {
 /*----------------------------------------------------------------------------
    Compute the location in the output of a pixel that is at row 'row',
@@ -612,7 +613,7 @@ transformPoint(int                const col,
    Return the output image location of the pixel as *newcolP and *newrowP.
 -----------------------------------------------------------------------------*/
     /* The transformation is:
-     
+
                  [ a b 0 ]
        [ x y 1 ] [ c d 0 ] = [ x2 y2 1 ]
                  [ e f 1 ]
@@ -652,9 +653,9 @@ writeRaster(struct pam *    const pamP,
 static void
 transformPbmGen(struct pam *     const inpamP,
                 struct pam *     const outpamP,
-                struct xformCore const xformCore) { 
+                struct XformCore const XformCore) {
 /*----------------------------------------------------------------------------
-   This is the same as transformGen, except that it uses less 
+   This is the same as transformGen, except that it uses less
    memory, since the PBM buffer format uses one bit per pixel instead
    of twelve bytes + pointer space
 
@@ -664,28 +665,28 @@ transformPbmGen(struct pam *     const inpamP,
 -----------------------------------------------------------------------------*/
     bit * bitrow;
     bit ** newbits;
-    struct xformMatrix xform;
+    struct XformMatrix xform;
     unsigned int row;
-            
-    computeXformMatrix(&xform, inpamP->width, inpamP->height, xformCore);
-    
+
+    computeXformMatrix(&xform, inpamP->width, inpamP->height, XformCore);
+
     bitrow = pbm_allocrow_packed(inpamP->width);
     newbits = pbm_allocarray_packed( outpamP->width, outpamP->height );
-            
+
     /* Initialize entire array to zeroes.  One bits will be or'ed in later */
     for (row = 0; row < outpamP->height; ++row) {
         unsigned int col;
-        for (col = 0; col < pbm_packed_bytes(outpamP->width); ++col) 
-            newbits[row][col] = 0; 
+        for (col = 0; col < pbm_packed_bytes(outpamP->width); ++col)
+            newbits[row][col] = 0;
     }
-    
+
     for (row = 0; row < inpamP->height; ++row) {
         unsigned int col;
 
         pbm_readpbmrow_packed(inpamP->file, bitrow,
                               inpamP->width, inpamP->format);
         for (col = 0; col < inpamP->width; ) {
-            if (bitrow[col/8] == 0x00) 
+            if (bitrow[col/8] == 0x00)
                 col += 8;  /* Blank.   Skip to next byte. */
             else {      /* Examine each pixel. */
                 unsigned int const colLimit = MIN(col+8, inpamP->width);
@@ -694,7 +695,7 @@ transformPbmGen(struct pam *     const inpamP,
                 for (i = 0; col < colLimit; ++i, ++col) {
                     bool const bitIsOne = (bitrow[col/8] >> (7-i)) & 0x01;
                     if (bitIsOne) {
-                        /* Write in only the one bits. */  
+                        /* Write in only the one bits. */
                         unsigned int newcol, newrow;
                         transformPoint(col, row, xform, &newcol, &newrow);
                         newbits[newrow][newcol/8] |= 0x01 << (7 - newcol % 8);
@@ -709,7 +710,7 @@ transformPbmGen(struct pam *     const inpamP,
 
     for (row = 0; row < outpamP->height; ++row)
         pbm_writepbmrow_packed(outpamP->file, newbits[row], outpamP->width, 0);
-    
+
     pbm_freearray(newbits, outpamP->height);
     pbm_freerow(bitrow);
 }
@@ -719,7 +720,7 @@ transformPbmGen(struct pam *     const inpamP,
 static void
 transformNonPbmWhole(struct pam *     const inpamP,
                      struct pam *     const outpamP,
-                     struct xformCore const xformCore,
+                     struct XformCore const XformCore,
                      bool             const verbose) {
 /*----------------------------------------------------------------------------
   Do the transform using "pam" library functions, as opposed to "pbm"
@@ -737,18 +738,18 @@ transformNonPbmWhole(struct pam *     const inpamP,
 -----------------------------------------------------------------------------*/
     tuple * tuplerow;
     tuple ** newtuples;
-    struct xformMatrix xform;
+    struct XformMatrix xform;
     unsigned int row;
 
-    computeXformMatrix(&xform, inpamP->width, inpamP->height, xformCore);
-    
+    computeXformMatrix(&xform, inpamP->width, inpamP->height, XformCore);
+
     tuplerow = pnm_allocpamrow(inpamP);
     newtuples = pnm_allocpamarray(outpamP);
-    
+
     for (row = 0; row < inpamP->height; ++row) {
         unsigned int col;
         pnm_readpamrow(inpamP, tuplerow);
-        
+
         for (col = 0; col < inpamP->width; ++col) {
             unsigned int newcol, newrow;
 
@@ -761,9 +762,9 @@ transformNonPbmWhole(struct pam *     const inpamP,
                             tuplerow[col]);
         }
     }
-    
+
     writeRaster(outpamP, newtuples);
-    
+
     pnm_freepamarray(newtuples, outpamP);
     pnm_freepamrow(tuplerow);
 }
@@ -797,7 +798,7 @@ initOutCell(struct pam *     const outCellPamP,
             unsigned int     const inCellWidth,
             unsigned int     const inCellHeight,
             struct pam *     const inpamP,
-            struct xformCore const xformCore) {
+            struct XformCore const XformCore) {
 /*----------------------------------------------------------------------------
    Set up an output cell.  Create and open a temporary file to hold its
    raster.  Figure out the dimensions of the cell.  Return a PAM structure
@@ -811,7 +812,7 @@ initOutCell(struct pam *     const outCellPamP,
 
     outCellPamP->file = pm_tmpfile();
 
-    xformDimensions(xformCore, inCellWidth, inCellHeight,
+    xformDimensions(XformCore, inCellWidth, inCellHeight,
                     &outCellFileCt, &outCellRankCt);
 
     outCellPamP->width = outCellFileCt;
@@ -823,8 +824,8 @@ initOutCell(struct pam *     const outCellPamP,
 static outputMap *
 createOutputMap(struct pam *       const inpamP,
                 unsigned int       const maxRows,
-                struct xformMatrix const cellXform,
-                struct xformCore   const xformCore) {
+                struct XformMatrix const cellXform,
+                struct XformCore   const XformCore) {
 /*----------------------------------------------------------------------------
    Create and return the output map.  That's a map of all the output cells
    (from which the output image can be assembled, once those cells are filled
@@ -852,7 +853,7 @@ createOutputMap(struct pam *       const inpamP,
 
     MALLOCVAR_NOFAIL(mapP);
 
-    xformDimensions(xformCore, inCellFileCt, inCellRankCt,
+    xformDimensions(XformCore, inCellFileCt, inCellRankCt,
                     &mapP->fileCt, &mapP->rankCt);
 
     MALLOCARRAY(mapP->pam, mapP->rankCt);
@@ -878,14 +879,14 @@ createOutputMap(struct pam *       const inpamP,
         unsigned int outCellFile, outCellRank;
         transformPoint(inCellFile, inCellRank, cellXform,
                        &outCellFile, &outCellRank);
-    
+
         initOutCell(&mapP->pam[outCellRank][outCellFile],
                     inpamP->width, inCellRowCt,
-                    inpamP, xformCore);
+                    inpamP, XformCore);
     }
     return mapP;
 }
-                
+
 
 
 static void
@@ -934,14 +935,14 @@ closeCellFiles(outputMap * const outputMapP) {
 static void
 transformCell(struct pam *     const inpamP,
               struct pam *     const outpamP,
-              struct xformCore const xformCore) {
+              struct XformCore const XformCore) {
 
-    struct xformMatrix xform;
+    struct XformMatrix xform;
     tuple * inTupleRow;
     tuple ** outTuples;
     unsigned int inRow;
 
-    computeXformMatrix(&xform, inpamP->width, inpamP->height, xformCore);
+    computeXformMatrix(&xform, inpamP->width, inpamP->height, XformCore);
 
     inTupleRow = pnm_allocpamrow(inpamP);
 
@@ -951,7 +952,7 @@ transformCell(struct pam *     const inpamP,
         unsigned int inCol;
 
         pnm_readpamrow(inpamP, inTupleRow);
-        
+
         for (inCol = 0; inCol < inpamP->width; ++inCol) {
             unsigned int outCol, outRow;
 
@@ -995,7 +996,7 @@ stitchCellsToOutput(outputMap *  const outputMapP,
             outCol = 0;
 
             for (outFile = 0; outFile < outputMapP->fileCt; ++outFile) {
-                struct pam * const outCellPamP = 
+                struct pam * const outCellPamP =
                     &outputMapP->pam[outRank][outFile];
 
                 assert(outCellPamP->height == cellRows);
@@ -1019,7 +1020,7 @@ stitchCellsToOutput(outputMap *  const outputMapP,
 static void
 transformNonPbmChunk(struct pam *     const inpamP,
                      struct pam *     const outpamP,
-                     struct xformCore const xformCore,
+                     struct XformCore const XformCore,
                      unsigned int     const maxRows,
                      bool             const verbose) {
 /*----------------------------------------------------------------------------
@@ -1031,11 +1032,11 @@ transformNonPbmChunk(struct pam *     const inpamP,
   header).
 
   We call the strip of 'maxRows' rows that we read a source cell.  We
-  transform that cell according to 'xformCore' to create a
+  transform that cell according to 'XformCore' to create a
   target cell.  We store all the target cells in temporary files.
   We consider the target cells to be arranged in a column matrix the
   same as the source cells within the source image; we transform that
-  matrix according to 'xformCore'.  The resulting cell matrix is the
+  matrix according to 'XformCore'.  The resulting cell matrix is the
   target image.
 -----------------------------------------------------------------------------*/
     /* The cells of the source image ("inCell") are in a 1-column matrix.
@@ -1045,7 +1046,7 @@ transformNonPbmChunk(struct pam *     const inpamP,
     unsigned int const inCellFileCt = 1;
     unsigned int const inCellRankCt = (inpamP->height + maxRows - 1) / maxRows;
 
-    struct xformMatrix cellXform;
+    struct XformMatrix cellXform;
     unsigned int inCellRank;
     outputMap * outputMapP;
 
@@ -1053,9 +1054,9 @@ transformNonPbmChunk(struct pam *     const inpamP,
         pm_message("Transforming in %u chunks, using temp files",
                    inCellRankCt);
 
-    computeXformMatrix(&cellXform, inCellFileCt, inCellRankCt, xformCore);
+    computeXformMatrix(&cellXform, inCellFileCt, inCellRankCt, XformCore);
 
-    outputMapP = createOutputMap(inpamP, maxRows, cellXform, xformCore);
+    outputMapP = createOutputMap(inpamP, maxRows, cellXform, XformCore);
 
     for (inCellRank = 0; inCellRank < inCellRankCt; ++inCellRank) {
         unsigned int const inCellFile = 0;
@@ -1069,15 +1070,15 @@ transformNonPbmChunk(struct pam *     const inpamP,
 
         transformPoint(inCellFile, inCellRank, cellXform,
                        &outCellFile, &outCellRank);
-    
+
         outCellPamP = &outputMapP->pam[outCellRank][outCellFile];
 
         /* Input cell is just the next 'inCellRows' rows of input image */
         inCellPam = *inpamP;
         inCellPam.height = inCellRows;
 
-        transformCell(&inCellPam, outCellPamP, xformCore);
-    }    
+        transformCell(&inCellPam, outCellPamP, XformCore);
+    }
 
     rewindCellFiles(outputMapP);
 
@@ -1121,7 +1122,7 @@ maxRowsThatFit(struct pam * const pamP,
 static void
 transformPbm(struct pam *     const inpamP,
              struct pam *     const outpamP,
-             struct xformCore const xform,
+             struct XformCore const xform,
              bool             const verbose) {
 
     if (xform.b == 0 && xform.c == 0) {
@@ -1154,7 +1155,7 @@ transformPbm(struct pam *     const inpamP,
 static void
 transformNonPbm(struct pam *     const inpamP,
                 struct pam *     const outpamP,
-                struct xformCore const xform,
+                struct XformCore const xform,
                 unsigned int     const availableMemory,
                 bool             const verbose) {
 
@@ -1211,7 +1212,7 @@ main(int argc, char * argv[]) {
         ifP = pm_openr_seekable(cmdline.inputFilespec);
     else
         ifP = pm_openr(cmdline.inputFilespec);
-    
+
     pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
 
     outpam = inpam;  /* initial value */
@@ -1232,6 +1233,6 @@ main(int argc, char * argv[]) {
     }
     pm_close(inpam.file);
     pm_close(outpam.file);
-    
+
     return 0;
 }
diff --git a/editor/pamflip/pamflip_sse.c b/editor/pamflip/pamflip_sse.c
index e0929f65..c4e51751 100644
--- a/editor/pamflip/pamflip_sse.c
+++ b/editor/pamflip/pamflip_sse.c
@@ -37,7 +37,7 @@
 
 /*----------------------------------------------------------------------------
    This is a specialized routine for row-for-column PBM transformations.
-   (-cw, -ccw, -xy).  It requires GCC (>= v. 4.2.0) and SSE2. 
+   (-cw, -ccw, -xy).  It requires GCC (>= v. 4.2.0) and SSE2.
 
    In each cycle, we read sixteen rows from the input.  We process this band
    left to right in blocks 8 pixels wide.  We use the SSE2 instruction
@@ -70,7 +70,7 @@
    It is possible to write a non-SSE version by providing a generic
    version of transpose16Bitrows() or one tuned for a specific
    architecture.  Use 8x8 blocks to avoid endian issues.
- 
+
    Further enhancement should be possible by employing wider bands,
    larger blocks as wider SIMD registers become available.  Clearing
    the white parts after instead of before transposition is also a
@@ -149,7 +149,7 @@ transpose16Bitrows(unsigned int const cols,
 
         register v16qi vReg = {
             block[0][col8],  block[1][col8],
-            block[2][col8],  block[3][col8],  
+            block[2][col8],  block[3][col8],
             block[4][col8],  block[5][col8],
             block[6][col8],  block[7][col8],
             block[8][col8],  block[9][col8],
@@ -163,14 +163,14 @@ transpose16Bitrows(unsigned int const cols,
         if (_mm_movemask_epi8(compare) != 0xffff) {
 
             /* There is some black content in this block; write to outplane */
-            
+
             unsigned int outrow;
             unsigned int i;
 
             outrow = col;  /* initial value */
 
             for (i = 0; i < 7; ++i) {
-                /* GCC (>=4.2) automatically unrolls this loop */  
+                /* GCC (>=4.2) automatically unrolls this loop */
                 outplane[outrow++][outcol16] =
                     _mm_movemask_epi8((__m128i)vReg);
                 vReg = (v16qi)_mm_slli_epi32((__m128i)vReg, 1);
@@ -197,7 +197,7 @@ analyzeBlock(const struct pam * const inpamP,
   "twists" brought about by Intel byte ordering which occur when:
     (1) 16 bytes are loaded to a SSE register
     (2) 16 bits are written to memory.
- 
+
   If 'rows' is not a multiple of 8, a partial input band appears at one edge.
   Set *topOfFullBlockP accordingly.  blockPartial[] is an adjusted "block" for
   this partial band, brought up to a size of 8 rows.  The extra pointers point
@@ -227,7 +227,7 @@ analyzeBlock(const struct pam * const inpamP,
                 : block[i];
         }
         *topOfFullBlockP = inpamP->height % 16;
-    
+
         if (inpamP->height >= 16) {
             *outcol16P = inpamP->height/16 - 1;
         } else
@@ -243,7 +243,7 @@ doPartialBlockTop(const struct pam * const inpamP,
                   const bit *        const blockPartial[16],
                   unsigned int       const topOfFullBlock,
                   uint16_t **        const outplane) {
-    
+
     if (topOfFullBlock > 0) {
         unsigned int colChar, row;
         unsigned int pad = 16 - topOfFullBlock;
@@ -267,7 +267,7 @@ doPartialBlockTop(const struct pam * const inpamP,
                            outplane, inpamP->height /16);
             /* Transpose partial rows on top of input.  Place on right edge of
                output.
-            */ 
+            */
     }
 }
 
@@ -303,7 +303,7 @@ doFullBlocks(const struct pam * const inpamP,
         ++modrow;
         if (modrow == 16) {
             /* 16 row buffer is full.  Transpose. */
-            modrow = 0; 
+            modrow = 0;
 
             transpose16Bitrows(inpamP->width, inpamP->height,
                                block, outplane, outcol16);
@@ -320,7 +320,7 @@ doPartialBlockBottom(const struct pam * const inpamP,
                      int                const xdir,
                      const bit *        const blockPartial[16],
                      uint16_t **        const outplane) {
-    
+
     if (xdir > 0 && inpamP->height % 16 > 0) {
         unsigned int colChar;
 
@@ -331,7 +331,7 @@ doPartialBlockBottom(const struct pam * const inpamP,
                            outplane, inpamP->height/16);
             /* Transpose partial rows on bottom of input.  Place on right edge
                of output.
-            */ 
+            */
     }
 }
 
@@ -341,7 +341,7 @@ static void
 writeOut(const struct pam * const outpamP,
          uint16_t **        const outplane,
          int                const ydir) {
-             
+
     unsigned int row;
 
     for (row = 0; row < outpamP->height; ++row) {
@@ -357,23 +357,23 @@ writeOut(const struct pam * const outpamP,
 
 static void
 clearOutplane(const struct pam * const outpamP,
-              uint16_t **        const outplane) { 
-    
+              uint16_t **        const outplane) {
+
     unsigned int row;
-    
+
     for (row = 0; row < outpamP->height; ++row) {
         unsigned int col16;  /* column divided by 16 */
         for (col16 = 0; col16 < (outpamP->width + 15)/16; ++col16)
             outplane[row][col16] = 0x0000;
     }
-} 
+}
 
 
 
 void
 pamflip_transformRowsToColumnsPbmSse(const struct pam * const inpamP,
                                      const struct pam * const outpamP,
-                                     struct xformCore const xformCore) { 
+                                     struct XformCore   const xformCore) {
 /*----------------------------------------------------------------------------
   This is a specialized routine for row-for-column PBM transformations.
   (-cw, -ccw, -xy).
@@ -397,11 +397,11 @@ pamflip_transformRowsToColumnsPbmSse(const struct pam * const inpamP,
         pm_error("Could not allocate %u x %u array of 16 bit units",
                  blocksPerRow, outpamP->height + 7);
 
-    /* We write to the output array in 16 bit units.  Add margin. */  
+    /* We write to the output array in 16 bit units.  Add margin. */
 
     clearOutplane(outpamP, outplane);
 
-    analyzeBlock(inpamP, inrow, xdir, block, blockPartial, 
+    analyzeBlock(inpamP, inrow, xdir, block, blockPartial,
                  &topOfFullBlock, &outcol16);
 
     doPartialBlockTop(inpamP, inrow, blockPartial, topOfFullBlock, outplane);
@@ -421,9 +421,9 @@ pamflip_transformRowsToColumnsPbmSse(const struct pam * const inpamP,
 void
 pamflip_transformRowsToColumnsPbmSse(const struct pam * const inpamP,
                                      const struct pam * const outpamP,
-                                     struct xformCore   const xformCore) { 
+                                     struct XformCore   const xformCore) {
 
     /* Nobody is supposed to call this */
     assert(false);
 }
-#endif 
+#endif
diff --git a/editor/pamflip/pamflip_sse.h b/editor/pamflip/pamflip_sse.h
index 59e7c026..cd1267b7 100644
--- a/editor/pamflip/pamflip_sse.h
+++ b/editor/pamflip/pamflip_sse.h
@@ -7,6 +7,6 @@ struct pam;
 void
 pamflip_transformRowsToColumnsPbmSse(const struct pam *     const inpamP,
                                      const struct pam *     const outpamP,
-                                     struct xformCore       const xformCore);
+                                     struct XformCore       const xformCore);
 
 #endif
diff --git a/editor/pamhue.c b/editor/pamhue.c
new file mode 100644
index 00000000..7dddedd8
--- /dev/null
+++ b/editor/pamhue.c
@@ -0,0 +1,209 @@
+/*=============================================================================
+                                  pamhue
+===============================================================================
+  Change the hue, the Hue-Saturation-Value model, every pixel in an image
+  by a specified angle.
+=============================================================================*/
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <math.h>
+#include "mallocvar.h"
+#include "shhopt.h"
+#include "pam.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;  /* '-' if stdin */
+    float        huechange;
+};
+
+
+
+static void
+parseCommandLine(int                        argc,
+                 const char **              argv,
+                 struct CmdlineInfo * const cmdlineP ) {
+/*----------------------------------------------------------------------------
+   Parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to pm_optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int huechangeSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,  "huechange",          OPT_FLOAT,
+            &cmdlineP->huechange,           &huechangeSpec,             0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = false;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = false;    /* No negative arguments */
+
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!huechangeSpec)
+        pm_error("You must specify -huechange");
+
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFileName = argv[1];
+    else
+        pm_error("Program takes at most one argument:  file specification");
+}
+
+
+
+static float
+positiveMod(float const arg,
+            float const modulus) {
+/*----------------------------------------------------------------------------
+   'arg' mod 'modulus', but positive (i.e. in the range 0.0 - 'modulus').
+-----------------------------------------------------------------------------*/
+    float const mod = fmodf(arg, modulus);
+
+    return mod >= 0.0 ? mod : 360 + mod;
+}
+
+
+
+static void
+changeHue(tuple   const tupleval,
+          float   const huechange,
+          sample  const maxval) {
+
+    pixel oldRgb, newRgb;
+    struct hsv oldHsv, newHsv;
+
+    PPM_PUTR(oldRgb, tupleval[PAM_RED_PLANE]);
+    PPM_PUTG(oldRgb, tupleval[PAM_GRN_PLANE]);
+    PPM_PUTB(oldRgb, tupleval[PAM_BLU_PLANE]);
+
+    oldHsv = ppm_hsv_from_color(oldRgb, maxval);
+
+    newHsv.h = positiveMod(oldHsv.h + huechange, 360.0);
+    newHsv.s = oldHsv.s;
+    newHsv.v = oldHsv.v;
+
+    newRgb = ppm_color_from_hsv(newHsv, maxval);
+
+    tupleval[PAM_RED_PLANE] = PPM_GETR(newRgb);
+    tupleval[PAM_GRN_PLANE] = PPM_GETG(newRgb);
+    tupleval[PAM_BLU_PLANE] = PPM_GETB(newRgb);
+}
+
+
+
+static void
+convertRow(tuple *            const tuplerow,
+           float              const huechange,
+           const struct pam * const pamP) {
+
+    unsigned int col;
+
+    for (col = 0; col < pamP->width; ++col)  {
+        if ((pamP->format == PPM_FORMAT) || (pamP->format == RPPM_FORMAT) ||
+                 ((pamP->format == PAM_FORMAT) && (pamP->depth >= 3))) {
+            /* It's a color image, so there is a hue to change */
+
+            changeHue(tuplerow[col], huechange, pamP->maxval);
+        } else {
+            /* It's black and white or grayscale, which means fully
+               desaturated, so hue is meaningless.  Nothing to change.
+            */
+        }
+    }
+}
+
+
+
+static void
+pamhue(struct CmdlineInfo const cmdline,
+       FILE *             const ifP,
+       FILE *             const ofP) {
+
+    struct pam inpam, outpam;
+    tuple * tuplerow;
+    unsigned int row;
+
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    outpam = inpam;
+    outpam.file = ofP;
+
+    pnm_writepaminit(&outpam);
+
+    tuplerow = pnm_allocpamrow(&inpam);
+
+    for (row = 0; row < inpam.height; ++row) {
+        pnm_readpamrow(&inpam, tuplerow);
+
+        convertRow(tuplerow, cmdline.huechange, &inpam);
+
+        pnm_writepamrow(&outpam, tuplerow);
+    }
+
+    pnm_freepamrow(tuplerow);
+}
+
+
+
+int
+main(int argc, const char *argv[]) {
+
+    struct CmdlineInfo cmdline;
+    FILE * ifP;
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    pamhue(cmdline, ifP, stdout);
+
+    pm_close(ifP);
+    pm_close(stdout);
+
+    return 0;
+}
+
+
+
+/* This was derived by Bryan Henderson from code by Willem van Schaik
+   (willem@schaik.com), which was derived from Ppmbrighten code written by Jef
+   Poskanzer and Brian Moffet.
+
+   Copyright (C) 1989 by Jef Poskanzer.
+   Copyright (C) 1990 by Brian Moffet.
+   Copyright (C) 2019 by Willem van Schaik (willem@schaik.com)
+
+   Permission to use, copy, modify, and distribute this software and its
+   documentation for any purpose and without fee is hereby granted, provided
+   that the above copyright notice appear in all copies and that both that
+   copyright notice and this permission notice appear in supporting
+   documentation.  This software is provided "as is" without express or
+   implied warranty.
+
+   Bryan contributes his work to the public domain.
+*/
diff --git a/editor/pamlevels.c b/editor/pamlevels.c
new file mode 100644
index 00000000..fbbb2c0b
--- /dev/null
+++ b/editor/pamlevels.c
@@ -0,0 +1,515 @@
+#include <stdbool.h>
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "netpbm/pam.h"
+#include "netpbm/pm_system.h"
+#include "netpbm/pm_gamma.h"
+#include "netpbm/nstring.h"
+#include "netpbm/ppm.h"
+
+#include "shhopt.h"
+#include "mallocvar.h"
+
+/* ----------------------------- Type aliases ------------------------------ */
+
+typedef unsigned char uchar;
+typedef unsigned int  uint;
+typedef struct   pam  pam;
+
+typedef struct {
+/*----------------------------------------------------------------------------
+  An RGB triple, in linear intensity or linear brightness; user's choice.
+-----------------------------------------------------------------------------*/
+    double _[3];
+} Rgb;
+
+typedef struct {
+/*----------------------------------------------------------------------------
+  A quadratic polynomial
+-----------------------------------------------------------------------------*/
+    double coeff[3];
+} Polynomial;
+
+typedef struct {
+/*----------------------------------------------------------------------------
+  A set of source or target sample values, in some plane.
+
+  These are either intensity-linear or brightness-linear; user's choice.
+
+  There could be two or three values; user must know which.
+-----------------------------------------------------------------------------*/
+    double _[3];
+} SampleSet;
+
+/* ------------------------- Parse transformations ------------------------- */
+
+typedef struct {
+/*----------------------------------------------------------------------------
+  A mapping of one source color to one target color, encoded in linear RGB
+-----------------------------------------------------------------------------*/
+    tuplen from;
+    tuplen to;
+} Trans;
+
+typedef struct {
+    const char * from;  /* color specifications  */
+    const char * to;    /*   as they appear on commandline */
+    unsigned int  hasFrom;      /* "from" part is present */
+    unsigned int  hasTo;        /* "to" part is present */
+    char  nameFromS[3]; /* short option name */
+    char  nameToS  [3];
+    char  nameFromL[6]; /* long option name */
+    char  nameToL  [6];
+} TransArg;
+
+typedef struct {
+    TransArg _[3];
+} TransArgSet;
+
+typedef struct {
+    unsigned int n;
+        /* Number of elements in 't', 2 for linear transformation; 3 for
+           quadratic.
+        */
+    Trans t[3];
+} TransSet;
+
+typedef struct {
+    unsigned int linear;
+    unsigned int fitbrightness;
+    TransSet     xlats; /* color mappings (-from1, -to1, etc.) */
+    const char * inputFileName;  /* the input file name, "-" for stdin     */
+} CmdlineInfo;
+
+
+
+static void
+optAddTrans (optEntry *     const option_def,
+             unsigned int * const option_def_indexP,
+             TransArg *     const xP,
+             char           const index) {
+
+    char indexc;
+    uint option_def_index;
+
+    option_def_index = *option_def_indexP;
+
+    indexc = '0' + index;
+
+    STRSCPY(xP->nameFromL, "from "); xP->nameFromL[4] = indexc;
+    STRSCPY(xP->nameToL,   "to "  ); xP->nameToL  [2] = indexc;
+    STRSCPY(xP->nameFromS, "f "   ); xP->nameFromS[1] = indexc;
+    STRSCPY(xP->nameToS,   "t "   ); xP->nameToS  [1] = indexc;
+
+    OPTENT3(0, xP->nameFromL, OPT_STRING, &xP->from, &xP->hasFrom, 0);
+    OPTENT3(0, xP->nameFromS, OPT_STRING, &xP->from, &xP->hasFrom, 0);
+    OPTENT3(0, xP->nameToL,   OPT_STRING, &xP->to,   &xP->hasTo,   0);
+    OPTENT3(0, xP->nameToS,   OPT_STRING, &xP->to,   &xP->hasTo,   0);
+
+    *option_def_indexP = option_def_index;
+}
+
+
+
+static void
+parseColor(const char * const text,
+           tuplen *     const colorP) {
+/*----------------------------------------------------------------------------
+  Parses color secification in <text>, converts it into linear RGB,
+  and stores the result in <colorP>.
+-----------------------------------------------------------------------------*/
+    const char * const lastsc = strrchr(text, ':');
+
+    const char * colorname;
+    double mul;
+    tuplen unmultipliedColor;
+    tuplen color;
+
+    if (lastsc) {
+        /* Specification contains a colon.  It might be the colon that
+           introduces the optional multiplier, or it might just be the colon
+           after the type specifier, e.g. "rgbi:...".
+        */
+
+        if (strstr(text, "rgb") == text && strchr(text, ':') == lastsc) {
+            /* The only colon present is the one on the type specifier.
+               So there is no multiplier.
+            */
+            mul = 1.0;
+            colorname = pm_strdup(text);
+        } else {
+            /* There is a multiplier (possibly invalid, though). */
+            const char * const mulstart = lastsc + 1;
+
+            char * endP;
+            char colorbuf[50];
+
+            errno = 0;
+            mul = strtod(mulstart, &endP);
+            if (errno != 0 || endP == mulstart)
+                pm_error("Invalid sample multiplier: '%s'", mulstart);
+
+            strncpy(colorbuf, text, lastsc - text);
+            colorbuf[lastsc - text] = '\0';
+            colorname = pm_strdup(colorbuf);
+        }
+    } else {
+        mul = 1.0;
+        colorname = pm_strdup(text);
+    }
+
+    unmultipliedColor = pnm_parsecolorn(colorname);
+
+    pm_strfree(colorname);
+
+    MALLOCARRAY_NOFAIL(color, 3);
+
+    {
+        /* Linearize and apply multiplier */
+        unsigned int i;
+        for (i = 0; i < 3; ++i)
+            color[i] = pm_ungamma709(unmultipliedColor[i]) * mul;
+    }
+    free(unmultipliedColor);
+
+    *colorP = color;
+}
+
+
+
+static void
+parseTran (TransArg const transArg,
+           Trans *  const rP) {
+
+    parseColor(transArg.from, &rP->from);
+    parseColor(transArg.to,   &rP->to);
+}
+
+
+
+static void
+calcTrans(TransArgSet   const transArgs,
+          TransSet *    const transP) {
+/*----------------------------------------------------------------------------
+   Interpret transformation option (-from1, etc.) values 'transArg'
+   as transformations, *transP.
+-----------------------------------------------------------------------------*/
+    unsigned int xi;
+
+    for (transP->n = 0, xi = 0; xi < 3; ++xi) {
+        const TransArg * const xformP = &transArgs._[xi];
+
+        if (xformP->hasFrom || xformP->hasTo) {
+            if (!xformP->hasFrom || !xformP->hasTo)
+                pm_error("Mapping %u incompletely specified - "
+                         "you specified -fromN or -toN but not the other",
+                    xi + 1);
+            parseTran(*xformP, &transP->t[transP->n++]);
+        }
+    }
+    if (transP->n < 2)
+        pm_error("You must specify at least two mappings with "
+                 "-from1, -to1, etc.  You specified %u", transP->n);
+}
+
+
+
+static void
+parseCommandLine(int                 argc,
+                 const char **       argv,
+                 CmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to pm_optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    TransArgSet xlations; /* color mapping as read from command line */
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;  /* incremented by OPTENT3 */
+
+    OPTENT3(0, "fitbrightness",          OPT_FLAG, NULL,
+            &cmdlineP->fitbrightness, 0);
+    OPTENT3(0, "linear",                 OPT_FLAG, NULL,
+            &cmdlineP->linear,        0);
+
+    {
+        unsigned int i;
+        for (i = 0; i < 3; ++i)
+            optAddTrans(option_def, &option_def_index,
+                        &xlations._[i], i + 1);
+    }
+
+    opt.opt_table     = option_def;
+    opt.short_allowed = 0;
+    opt.allowNegNum   = 0;
+
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
+
+    if (cmdlineP->linear && cmdlineP->fitbrightness) {
+        pm_error("You cannot use -linear and -fitbrightness together");
+        /* Note: It actually makes sense to use them together; we're just not
+           willing to put the effort into something it's unlikely anyone will
+           want.
+        */
+    }
+
+    calcTrans(xlations, &cmdlineP->xlats);
+
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else {
+        cmdlineP->inputFileName = argv[1];
+
+        if (argc-1 > 1)
+            pm_error("Too many arguments.  "
+                     "The only possible non-option argument "
+                     "is the input file name");
+    }
+
+    free(option_def);
+}
+
+
+
+static void
+errResolve(void) {
+    pm_error( "Cannot resolve the transformations");
+}
+
+
+
+static double
+sqr(double const x) {
+    return x * x;
+}
+
+
+
+static void
+solveOnePlane(SampleSet    const f,
+              SampleSet    const t,
+              unsigned int const n,
+              Polynomial * const solutionP) {
+/*----------------------------------------------------------------------------
+  Find the transformation that maps f[i] to t[i] for 0 <= i < n.
+-----------------------------------------------------------------------------*/
+    double const eps = 0.00001;
+
+    double a, b, c;
+
+    /* I have decided against generic methods of solving systems of linear
+       equations in favour of simple explicit formulas, with no memory
+       allocation and tedious matrix processing.
+    */
+
+    switch (n) {
+    case 3: {
+        double const aDenom =
+            sqr( f._[0] ) * ( f._[1] - f._[2] ) -
+            sqr( f._[2] ) * ( f._[1] - f._[0] ) -
+            sqr( f._[1] ) * ( f._[0] - f._[2] );
+
+        if (fabs(aDenom) < eps)
+            errResolve();
+
+        a = (t._[1] * (f._[2] - f._[0]) - t._[0] * (f._[2] - f._[1]) -
+             t._[2] * (f._[1] - f._[0]))
+            / aDenom;
+    } break;
+    case 2:
+        a = 0.0;
+        break;
+    default:
+        a = 0.0; /* to avoid a warning that <a> "may be uninitialized". */
+        pm_error("INTERNAL ERROR: solve(): impossible value of n: %u", n);
+    }
+
+    {
+        double const bDenom = f._[1] - f._[0];
+
+        if (fabs(bDenom) < eps)
+            errResolve();
+
+        b = (t._[1] - t._[0] + a * (sqr(f._[0]) - sqr(f._[1]))) / bDenom;
+    }
+
+    c = -a * sqr(f._[0]) - b * f._[0] + t._[0];
+
+    solutionP->coeff[0] = a; solutionP->coeff[1] = b; solutionP->coeff[2] = c;
+}
+
+
+
+static void
+chanData(TransSet     const ta,
+         bool         const fittingBrightness,
+         unsigned int const plane,
+         SampleSet *  const fromP,
+         SampleSet *  const toP) {
+/*----------------------------------------------------------------------------
+  Collate transformations from 'ta' for plane 'plane'.
+-----------------------------------------------------------------------------*/
+    unsigned int i;
+
+    for (i = 0; i < ta.n; ++i) {
+        if (fittingBrightness) { /* working with gamma-compressed values */
+            fromP->_[i] = pm_gamma709(ta.t[i].from[plane]);
+            toP->  _[i] = pm_gamma709(ta.t[i].to  [plane]);
+        } else { /* working in linear RGB */
+            fromP->_[i] = ta.t[i].from[plane];
+            toP->  _[i] = ta.t[i].to  [plane];
+        }
+    }
+}
+
+
+
+typedef struct {
+    Polynomial _[3];  /* One per plane */
+} Solution;
+
+
+
+static void
+solveFmCmdlineOpts(CmdlineInfo  const cmdline,
+                   unsigned int const depth,
+                   Solution *   const solutionP) {
+/*----------------------------------------------------------------------------
+   Compute the function that will transform the tuples, based on what the user
+   requested ('cmdline').
+
+   The function takes intensity-linear tuples for the normal levels function,
+   or brightness-linear for the brightness approximation levels function.
+
+   The transformed image has 'depth' planes.
+-----------------------------------------------------------------------------*/
+    unsigned int plane;
+    SampleSet from, to;
+    /* This initialization to bypass the "may be uninitialized" warning: */
+    to  ._[0] = 0; to.  _[1] = 0; to  ._[2] = 0;
+    from._[0] = 1; from._[1] = 0; from._[2] = 0;
+
+    for (plane = 0; plane < depth; ++plane) {
+
+        chanData(cmdline.xlats, cmdline.fitbrightness, plane, &from, &to);
+        solveOnePlane(from, to, cmdline.xlats.n, &solutionP->_[plane]);
+    }
+}
+
+
+
+static samplen
+xformedSample(samplen    const value,
+              Polynomial const polynomial) {
+/*----------------------------------------------------------------------------
+  'sample' transformed by 'polynomial'.
+-----------------------------------------------------------------------------*/
+    double const res =
+        (polynomial.coeff[0] * value + polynomial.coeff[1]) * value +
+        polynomial.coeff[2];
+
+    return MAX(0.0f, MIN(1.0f, res));
+}
+
+
+
+static void
+pamlevels(CmdlineInfo const cmdline) {
+
+    unsigned int row;
+    pam      inPam, outPam;
+    Solution solution;
+    tuplen * tuplerown;
+    FILE   * ifP;
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    pnm_readpaminit(ifP, &inPam, PAM_STRUCT_SIZE(tuple_type));
+
+    outPam = inPam;
+    outPam.file = stdout;
+
+    solveFmCmdlineOpts(cmdline, inPam.depth, &solution);
+
+    tuplerown = pnm_allocpamrown(&inPam);
+
+    pnm_writepaminit(&outPam);
+
+    for (row = 0; row < inPam.height; ++row) {
+        unsigned int col;
+
+        pnm_readpamrown(&inPam, tuplerown);
+
+        if (!cmdline.linear && !cmdline.fitbrightness)
+            pnm_ungammarown(&inPam, tuplerown);
+
+        for (col = 0; col < inPam.width; ++col) {
+            unsigned int plane;
+
+            for (plane = 0; plane < inPam.depth; ++plane) {
+                tuplerown[col][plane] =
+                    xformedSample(tuplerown[col][plane], solution._[plane]);
+            }
+        }
+        if (!cmdline.linear && !cmdline.fitbrightness)
+            pnm_gammarown(&inPam, tuplerown);
+
+        pnm_writepamrown(&outPam, tuplerown);
+    }
+    pnm_freepamrown(tuplerown);
+    pm_close(ifP);
+}
+
+
+
+static void
+freeCmdLineInfo(CmdlineInfo cmdline) {
+/*----------------------------------------------------------------------------
+  Free any memory that has been dynamically allcoated in <cmdline>.
+-----------------------------------------------------------------------------*/
+    TransSet * const xxP = &cmdline.xlats;
+
+    uint x;
+
+    for (x = 0; x < xxP->n; ++x) {
+        free(xxP->t[x].from);
+        free(xxP->t[x].to);
+    }
+}
+
+
+
+int main(int    argc, const char * argv[]) {
+
+    CmdlineInfo cmdline;
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    pamlevels(cmdline);
+
+    freeCmdLineInfo(cmdline);
+
+    return 0;
+}
+
+
+
diff --git a/editor/pammixmulti.c b/editor/pammixmulti.c
new file mode 100644
index 00000000..d303c3de
--- /dev/null
+++ b/editor/pammixmulti.c
@@ -0,0 +1,543 @@
+/*************************************************
+ * Blend multiple Netpbm files into a single one *
+ *                                               *
+ * By Scott Pakin <scott+pbm@pakin.org>          *
+ *************************************************/
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <float.h>
+#include <math.h>
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "nstring.h"
+
+typedef enum {
+    BLEND_AVERAGE,   /* Take the average color of all pixels */
+    BLEND_RANDOM,    /* Take each pixel color from a randomly selected image */
+    BLEND_MASK   /* Take each pixel color from the image indicated by a mask */
+} BlendType;
+
+static unsigned int const randSamples = 1024;
+    /* Random samples to draw per file */
+
+struct ProgramState {
+    unsigned int     inFileCt;      /* Number of input files */
+    struct pam *     inPam;         /* List of input-file PAM structures */
+    tuple **         inTupleRows;   /* Current row from each input file */
+    struct pam       outPam;        /* Output-file PAM structure */
+    tuple *          outTupleRow;   /* Row to write to the output file */
+    const char *     maskFileName;  /* Name of the image-mask file */
+    struct pam       maskPam;       /* PAM structure for the image mask */
+    tuple *          maskTupleRow;  /* Row to read from the mask file */
+    double           sigma;
+        /* Standard deviation when selecting images via a mask */
+    unsigned long ** imageWeights;
+        /* Per-image weights as a function of grayscale level */
+};
+
+
+
+struct CmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    BlendType        blend;
+    const char *     maskfile;
+    float            stdev;
+    unsigned int     randomseed;
+    unsigned int     randomseedSpec;
+    unsigned int     inFileNameCt;  /* Number of input files */
+    const char **    inFileName;    /* Name of each input file */
+};
+
+
+
+static void
+freeCmdline(struct CmdlineInfo * const cmdlineP) {
+
+    free(cmdlineP->inFileName);
+}
+
+
+
+static void
+parseCommandLine(int argc, const char ** argv,
+                 struct CmdlineInfo * const cmdlineP) {
+
+    optStruct3 opt;
+    unsigned int option_def_index = 0;
+    optEntry * option_def;
+    unsigned int blendSpec, maskfileSpec, stdevSpec;
+    const char * blendOpt;
+
+    /* Define the allowed command-line options. */
+    MALLOCARRAY(option_def, 100);
+    OPTENT3(0, "blend",     OPT_STRING,     &blendOpt,
+            &blendSpec,     0);
+    OPTENT3(0, "maskfile",  OPT_STRING,     &cmdlineP->maskfile,
+            &maskfileSpec,  0);
+    OPTENT3(0, "stdev",      OPT_FLOAT,     &cmdlineP->stdev,
+            &stdevSpec,     0);
+    OPTENT3(0, "randomseed", OPT_UINT,      &cmdlineP->randomseed,
+            &cmdlineP->randomseedSpec,     0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = 0;
+    opt.allowNegNum = 0;
+
+    pm_optParseOptions3(&argc, (char **) argv, opt, sizeof(opt), 0);
+    if (blendSpec) {
+        if (streq(blendOpt, "average"))
+            cmdlineP->blend = BLEND_AVERAGE;
+        else if (streq(blendOpt, "random"))
+            cmdlineP->blend = BLEND_RANDOM;
+        else if (streq(blendOpt, "mask"))
+            cmdlineP->blend = BLEND_MASK;
+        else
+            pm_error("Unrecognized -blend value '%s'.  "
+                     "We recognize 'average', 'random', and 'mask'", blendOpt);
+    } else
+        cmdlineP->blend = BLEND_AVERAGE;
+
+    if (cmdlineP->blend == BLEND_MASK) {
+        if (!maskfileSpec)
+            pm_error("Because you specified -blend=mask, "
+                     "you must also specify -maskfile");
+    } else {
+        if (maskfileSpec)
+            pm_message("Ignoring -maskfile because -blend=mask "
+                       "is not specified");
+        if (stdevSpec)
+            pm_message("Ignoring -stdev because -blend=mask "
+                       "is not specified");
+    }
+    if (!stdevSpec)
+        cmdlineP->stdev = 0.25;
+
+    if (argc-1 < 1)
+        pm_error("You must specify the names of the files to blend together "
+                 "as arguments");
+
+    cmdlineP->inFileNameCt = argc-1;
+    MALLOCARRAY(cmdlineP->inFileName, argc-1);
+    if (!cmdlineP->inFileName)
+        pm_error("Unable to allocate space for %u file names", argc-1);
+    {
+        unsigned int i;
+        for (i = 0; i < argc-1; ++i)
+            cmdlineP->inFileName[i] = argv[1+i];
+    }
+    free(option_def);
+}
+
+static void
+openInputFiles(unsigned int          const inFileCt,
+               const char **         const inFileName,
+               struct ProgramState * const stateP) {
+/*----------------------------------------------------------------------------
+  Open all of the input files.
+
+  Abort if the input files don't all have the same size and format.
+-----------------------------------------------------------------------------*/
+    struct pam * inPam;
+    unsigned int i;
+
+    MALLOCARRAY(inPam, inFileCt);
+    if (!inPam)
+        pm_error("Failed to allocated memory for PAM structures for %u "
+                 "input files", inFileCt);
+    MALLOCARRAY(stateP->inTupleRows, inFileCt);
+    if (!stateP->inTupleRows)
+        pm_error("Failed to allocated memory for PAM structures for %u "
+                 "input rasters", inFileCt);
+
+    for (i = 0; i < inFileCt; ++i) {
+        FILE * const ifP = pm_openr(inFileName[i]);
+        pnm_readpaminit(ifP, &inPam[i], PAM_STRUCT_SIZE(tuple_type));
+        if (inPam[i].width != inPam[0].width ||
+            inPam[i].height != inPam[0].height)
+            pm_error("Input image %u has different dimensions from "
+                     "earlier input images", i);
+        if (inPam[i].depth != inPam[0].depth)
+            pm_error("Input image %u has different depth from "
+                     "earlier input images", i);
+        if (inPam[i].maxval != inPam[0].maxval)
+            pm_error("Input image %u has different maxval from "
+                     "earlier input images", i);
+        if (!streq(inPam[i].tuple_type, inPam[0].tuple_type))
+            pm_error("Input image %u has different tuple type from "
+                     "earlier input images", i);
+    }
+
+    for (i = 0; i < inFileCt; ++i)
+        stateP->inTupleRows[i] = pnm_allocpamrow(&inPam[i]);
+
+    stateP->inPam    = inPam;
+    stateP->inFileCt = inFileCt;
+}
+
+
+static void
+initMask(const char *          const maskFileName,
+         struct ProgramState * const stateP) {
+
+    struct pam * const maskPamP = &stateP->maskPam;
+
+    FILE * const mfP = pm_openr(maskFileName);
+
+    pnm_readpaminit(mfP, maskPamP, PAM_STRUCT_SIZE(tuple_type));
+
+    if (maskPamP->width != stateP->inPam[0].width ||
+        maskPamP->height != stateP->inPam[0].height) {
+
+        pm_error("The mask image does not have have the same dimensions "
+                 "as the input images");
+    }
+    if (maskPamP->depth > 1)
+        pm_message("Ignoring all but the first channel of the mask image");
+
+    stateP->maskTupleRow = pnm_allocpamrow(maskPamP);
+}
+
+
+
+static void
+termMask(struct ProgramState * const stateP) {
+
+    unsigned int i;
+
+    for (i = 0; i <= stateP->maskPam.maxval; ++i)
+        free(stateP->imageWeights[i]);
+
+    free(stateP->imageWeights);
+
+    pnm_freepamrow(stateP->maskTupleRow);
+
+    pm_close(stateP->maskPam.file);
+}
+
+
+
+static void
+initOutput(FILE *                const ofP,
+           struct ProgramState * const stateP) {
+
+    stateP->outPam      = stateP->inPam[0];
+    stateP->outPam.file = ofP;
+    stateP->outTupleRow = pnm_allocpamrow(&stateP->outPam);
+
+    pnm_writepaminit(&stateP->outPam);
+}
+
+
+
+static void
+blendTuplesRandom(struct ProgramState * const stateP,
+                  unsigned int          const col,
+                  sample *              const outSamps) {
+/*----------------------------------------------------------------------------
+  Blend one tuple of the input images into a new tuple by selecting a tuple
+  from a random input image.
+-----------------------------------------------------------------------------*/
+    unsigned int const depth = stateP->inPam[0].depth;
+    unsigned int const img = (unsigned int) (rand() % stateP->inFileCt);
+
+    unsigned int samp;
+
+    for (samp = 0; samp < depth; ++samp)
+        outSamps[samp] = ((sample *)stateP->inTupleRows[img][col])[samp];
+}
+
+
+
+static void
+blendTuplesAverage(struct ProgramState * const stateP,
+                   unsigned int          const col,
+                   sample *              const outSamps) {
+/*----------------------------------------------------------------------------
+  Blend one tuple of the input images into a new tuple by averaging all input
+  tuples.
+-----------------------------------------------------------------------------*/
+    unsigned int const depth = stateP->inPam[0].depth;
+
+    unsigned int samp;
+
+    for (samp = 0; samp < depth; ++samp) {
+        unsigned int img;
+
+        for (img = 0, outSamps[samp] = 0; img < stateP->inFileCt; ++img)
+            outSamps[samp] += ((sample *)stateP->inTupleRows[img][col])[samp];
+        outSamps[samp] /= stateP->inFileCt;
+    }
+}
+
+
+
+static void
+randomNormal2(double * const r1P,
+              double * const r2P) {
+/*----------------------------------------------------------------------------
+  Return two normally distributed random numbers.
+-----------------------------------------------------------------------------*/
+    double u1, u2;
+
+    do {
+        u1 = (double)rand() / RAND_MAX;
+        u2 = (double)rand() / RAND_MAX;
+    }
+    while (u1 <= DBL_EPSILON);
+
+    *r1P = sqrt(-2.0*log(u1)) * cos(2.0*M_PI*u2);
+    *r2P = sqrt(-2.0*log(u1)) * sin(2.0*M_PI*u2);
+}
+
+
+
+static void
+precomputeImageWeights(struct ProgramState * const stateP,
+                       double                const sigma) {
+/*----------------------------------------------------------------------------
+  Precompute the weight to give to each image as a function of grayscale
+  level.
+-----------------------------------------------------------------------------*/
+    unsigned int const maxGray = (unsigned int) stateP->maskPam.maxval;
+
+    unsigned int i;
+
+    MALLOCARRAY(stateP->imageWeights, maxGray + 1);
+    if (!stateP->imageWeights)
+        pm_error("Unable to allocate memory for image weights for %u "
+                 "gray levels", maxGray);
+
+    for (i = 0; i <= maxGray; ++i) {
+        unsigned int j;
+        MALLOCARRAY(stateP->imageWeights[i], stateP->inFileCt);
+        if (!stateP->imageWeights[i])
+            pm_error("Unable to allocate memory for image weights for %u "
+                     "images for gray level %u", stateP->inFileCt, i);
+        for (j = 0; j < stateP->inFileCt; ++j)
+            stateP->imageWeights[i][j] = 0;
+    }
+
+    /* Populate the image-weight arrays. */
+    for (i = 0; i <= maxGray; ++i) {
+        double const pctGray = i / (double)maxGray;
+
+        unsigned int j;
+
+        for (j = 0; j < stateP->inFileCt * randSamples; ) {
+            double r[2];
+            unsigned int k;
+
+            randomNormal2(&r[0], &r[1]);
+            for (k = 0; k < 2; ++k) {
+                int const img =
+                    r[k] * sigma + pctGray * stateP->inFileCt * 0.999999;
+                    /* Scale [0, 1] to [0, 1) (sort of). */
+                if (img >= 0 && img < (int)stateP->inFileCt) {
+                    ++stateP->imageWeights[i][img];
+                    ++j;
+                }
+            }
+        }
+    }
+}
+
+
+
+static void
+blendTuplesMask(struct ProgramState * const stateP,
+                unsigned int          const col,
+                sample *              const outSamps) {
+/*----------------------------------------------------------------------------
+  Blend one tuple of the input images into a new tuple according to the gray
+  levels specified in a mask file.
+-----------------------------------------------------------------------------*/
+    unsigned int const depth = stateP->inPam[0].depth;
+    sample const grayLevel = ((sample *)stateP->maskTupleRow[col])[0];
+
+    unsigned int img;
+
+    /* Initialize outSamps[] to zeroes */
+    {
+        unsigned int samp;
+
+        for (samp = 0; samp < depth; ++samp)
+            outSamps[samp] = 0;
+    }
+
+    /* Accumulate to outSamps[] */
+    for (img = 0; img < stateP->inFileCt; ++img) {
+        unsigned long weight = stateP->imageWeights[grayLevel][img];
+
+        if (weight != 0) {
+            unsigned int samp;
+
+            for (samp = 0; samp < depth; ++samp)
+                outSamps[samp] +=
+                    ((sample *)stateP->inTupleRows[img][col])[samp] * weight;
+        }
+    }
+    /* Scale all outSamps[] */
+    {
+        unsigned int samp;
+
+        for (samp = 0; samp < depth; ++samp)
+            outSamps[samp] /= randSamples * stateP->inFileCt;
+    }
+}
+
+
+
+static void
+blendImageRow(BlendType             const blend,
+              struct ProgramState * const stateP) {
+/*----------------------------------------------------------------------------
+  Blend one row of input images into a new row.
+-----------------------------------------------------------------------------*/
+    unsigned int const width = stateP->inPam[0].width;
+
+    unsigned int col;
+
+    for (col = 0; col < width; ++col) {
+        sample * const outSamps = stateP->outTupleRow[col];
+
+        switch (blend) {
+        case BLEND_RANDOM:
+            /* Take each pixel from a different, randomly selected image. */
+            blendTuplesRandom(stateP, col, outSamps);
+            break;
+
+        case BLEND_AVERAGE:
+            /* Average each sample across all the images. */
+            blendTuplesAverage(stateP, col, outSamps);
+            break;
+
+        case BLEND_MASK:
+            /* Take each pixel from the image specified by the mask image. */
+            blendTuplesMask(stateP, col, outSamps);
+            break;
+        }
+    }
+}
+
+
+
+static void
+blendImages(BlendType             const blend,
+            struct ProgramState * const stateP) {
+/*----------------------------------------------------------------------------
+  Blend the images row-by-row into a new image.
+-----------------------------------------------------------------------------*/
+    unsigned int const nRows = stateP->inPam[0].height;
+
+    unsigned int row;
+
+    for (row = 0; row < nRows; ++row) {
+        unsigned int img;
+
+        for (img = 0; img < stateP->inFileCt; ++img)
+            pnm_readpamrow(&stateP->inPam[img], stateP->inTupleRows[img]);
+
+        if (blend == BLEND_MASK)
+            pnm_readpamrow(&stateP->maskPam, stateP->maskTupleRow);
+
+        blendImageRow(blend, stateP);
+
+        pnm_writepamrow(&stateP->outPam, stateP->outTupleRow);
+    }
+}
+
+
+
+static void
+termState(struct ProgramState * const stateP) {
+/*----------------------------------------------------------------------------
+  Deallocate all of the resources we allocated.
+-----------------------------------------------------------------------------*/
+    unsigned int i;
+
+    for (i = 0; i < stateP->inFileCt; ++i) {
+        pnm_freepamrow(stateP->inTupleRows[i]);
+        pm_close(stateP->inPam[i].file);
+    }
+
+    free(stateP->outTupleRow);
+    free(stateP->inTupleRows);
+    free(stateP->inPam);
+    pm_close(stateP->outPam.file);
+}
+
+
+
+int
+main(int argc, const char * argv[]) {
+
+    struct CmdlineInfo cmdline;
+    struct ProgramState state;
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    srand(cmdline.randomseedSpec ? cmdline.randomseed : pm_randseed());
+
+    openInputFiles(cmdline.inFileNameCt, cmdline.inFileName, &state);
+
+    if (cmdline.blend == BLEND_MASK)
+        initMask(cmdline.maskfile, &state);
+
+    initOutput(stdout, &state);
+
+    if (cmdline.blend == BLEND_MASK)
+        precomputeImageWeights(&state, cmdline.stdev);
+
+    blendImages(cmdline.blend, &state);
+
+    if (cmdline.blend == BLEND_MASK)
+        termMask(&state);
+
+    termState(&state);
+
+    freeCmdline(&cmdline);
+
+    return 0;
+}
+
+
+/*  COPYRIGHT LICENSE and WARRANTY DISCLAIMER
+
+Copyright (c) 2018 Scott Pakin
+All rights reserved
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted (subject to the limitations in the disclaimer
+below) provided that the following conditions are met:
+
+  Redistributions of source code must retain the above copyright notice,
+  this list of conditions and the following disclaimer.
+
+  Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+  Neither the names of the oopyright owners nor the names of its contributors
+  may be used to endorse or promote products derived from this software
+  without specific prior written permission.
+
+  NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
+  THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+  CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
+  NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
diff --git a/editor/pamperspective.c b/editor/pamperspective.c
index 16715c2e..a206b57f 100644
--- a/editor/pamperspective.c
+++ b/editor/pamperspective.c
@@ -18,6 +18,7 @@
     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
 
+#define _DEFAULT_SOURCE /* New name for SVID & BSD source defines */
 #define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
 #define _BSD_SOURCE   /* Make sure strdup is int string.h */
 
diff --git a/editor/pamrecolor.c b/editor/pamrecolor.c
index 6937fd8d..8c5bce12 100644
--- a/editor/pamrecolor.c
+++ b/editor/pamrecolor.c
@@ -404,7 +404,7 @@ parseCommandLine(int argc, const char ** const argv,
             cmdlineP->color2gray.rfrac +
             cmdlineP->color2gray.gfrac +
             cmdlineP->color2gray.bfrac;
-        if (fabsf(1.0 - maxLuminance) > REAL_EPSILON)
+        if (fabsf(1.0f - maxLuminance) > REAL_EPSILON)
             pm_error("The values given for --rmult, --gmult, and --bmult must "
                      "sum to 1.0, not %.10g", maxLuminance);
     } else if (csSpec)
diff --git a/editor/pamrubber.c b/editor/pamrubber.c
index aaf62788..7169dbcf 100644
--- a/editor/pamrubber.c
+++ b/editor/pamrubber.c
@@ -1405,7 +1405,7 @@ main(int argc, const char ** const argv) {
 
     setGlobalCP(cmdline);
 
-    srand(cmdline.randseedSpec ? cmdline.randseed : time(NULL));
+    srand(cmdline.randseedSpec ? cmdline.randseed : pm_randseed());
 
     ifP = pm_openr(cmdline.fileName);
 
diff --git a/editor/pamscale.c b/editor/pamscale.c
index 2760b298..410cd94a 100644
--- a/editor/pamscale.c
+++ b/editor/pamscale.c
@@ -11,9 +11,9 @@
    people contributed code changes over the years.
 
    Copyright (C) 2003 by Michael Reinelt <reinelt@eunet.at>
-  
+
    Copyright (C) 1989, 1991 by Jef Poskanzer.
-  
+
    Permission to use, copy, modify, and distribute this software and its
    documentation for any purpose and without fee is hereby granted, provided
    that the above copyright notice appear in all copies and that both that
@@ -22,8 +22,9 @@
    implied warranty.
 */
 
-#define _XOPEN_SOURCE   /* get M_PI in math.h */
+#define _XOPEN_SOURCE 500  /* get M_PI in math.h */
 
+#include <stdbool.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <math.h>
@@ -46,7 +47,7 @@
 ** (sinc, bessel) are IIR (infinite impulse respone).
 ** They should be windowed with hanning, hamming, blackman or
 ** kaiser window.
-** For sinc and bessel the blackman window will be used per default.
+** For sinc and bessel the blackman window will be used by default.
 */
 
 #define EPSILON 1e-7
@@ -54,14 +55,14 @@
 
 
 /* x^2 and x^3 helper functions */
-static __inline__ double 
+static __inline__ double
 pow2(double const x) {
     return x * x;
 }
 
 
 
-static __inline__ double 
+static __inline__ double
 pow3(double const x) {
     return x * x * x;
 }
@@ -69,13 +70,13 @@ pow3(double const x) {
 
 
 /* box, pulse, Fourier window, */
-/* box function also know as rectangle function */
+/* box function also known as rectangle function */
 /* 1st order (constant) b-spline */
 
 #define radius_point (0.0)
 #define radius_box (0.5)
 
-static double 
+static double
 filter_box(double const x) {
 
     double const absx = x < 0.0 ? -x : x;
@@ -91,7 +92,7 @@ filter_box(double const x) {
 
 #define radius_triangle (1.0)
 
-static double 
+static double
 filter_triangle(double const x) {
 
     double const absx = x < 0.0 ? -x : x;
@@ -105,7 +106,7 @@ filter_triangle(double const x) {
 
 #define radius_quadratic (1.5)
 
-static double 
+static double
 filter_quadratic(double const x) {
 
     double const absx = x < 0.0 ? -x : x;
@@ -122,7 +123,7 @@ filter_quadratic(double const x) {
 
 #define radius_cubic (2.0)
 
-static double 
+static double
 filter_cubic(double const x) {
 
     double const absx = x < 0.0 ? -x : x;
@@ -139,7 +140,7 @@ filter_cubic(double const x) {
 
 #define radius_catrom (2.0)
 
-static double 
+static double
 filter_catrom(double const x) {
 
     double const absx = x < 0.0 ? -x : x;
@@ -158,7 +159,7 @@ filter_catrom(double const x) {
 
 #define radius_mitchell (2.0)
 
-static double 
+static double
 filter_mitchell(double x)
 {
 
@@ -187,7 +188,7 @@ filter_mitchell(double x)
 
 #define radius_gauss (1.25)
 
-static double 
+static double
 filter_gauss(double const x) {
 
     return exp(-2.0*pow2(x)) * sqrt(2.0/M_PI);
@@ -199,14 +200,14 @@ filter_gauss(double const x) {
 
 #define radius_sinc (4.0)
 
-static double 
+static double
 filter_sinc(double const x) {
     /* Note: Some people say sinc(x) is sin(x)/x.  Others say it's
        sin(PI*x)/(PI*x), a horizontal compression of the former which is
        zero at integer values.  We use the latter, whose Fourier transform
        is a canonical rectangle function (edges at -1/2, +1/2, height 1).
     */
-    return 
+    return
         x == 0.0 ? 1.0 :
         sin(M_PI*x)/(M_PI*x);
 }
@@ -218,10 +219,10 @@ filter_sinc(double const x) {
 
 #define radius_bessel (3.2383)
 
-static double 
+static double
 filter_bessel(double const x) {
 
-    return 
+    return
         x == 0.0 ? M_PI/4.0 :
         j1(M_PI * x) / (2.0 * x);
 }
@@ -232,7 +233,7 @@ filter_bessel(double const x) {
 
 #define radius_hanning (1.0)
 
-static double 
+static double
 filter_hanning(double const x) {
 
     return 0.5 * cos(M_PI * x) + 0.5;
@@ -244,7 +245,7 @@ filter_hanning(double const x) {
 
 #define radius_hamming (1.0)
 
-static double 
+static double
 filter_hamming(double const x) {
     return 0.46 * cos(M_PI * x) + 0.54;
 }
@@ -255,7 +256,7 @@ filter_hamming(double const x) {
 
 #define radius_blackman (1.0)
 
-static double 
+static double
 filter_blackman(double const x) {
     return 0.5 * cos(M_PI * x) + 0.08 * cos(2.0 * M_PI * x) + 0.42;
 }
@@ -268,12 +269,12 @@ filter_blackman(double const x) {
 #define radius_kaiser (1.0)
 
 /* modified zeroth order Bessel function of the first kind. */
-static double 
+static double
 bessel_i0(double const x) {
-  
+
     int i;
     double sum, y, t;
-  
+
     sum = 1.0;
     y = pow2(x)/4.0;
     t = y;
@@ -286,15 +287,15 @@ bessel_i0(double const x) {
 
 
 
-static double 
+static double
 filter_kaiser(double const x) {
     /* typically 4 < a < 9 */
     /* param a trades off main lobe width (sharpness) */
     /* for side lobe amplitude (ringing) */
-  
+
     double const a   = 6.5;
     double const i0a = 1.0/bessel_i0(a);
-  
+
     return i0a * bessel_i0(a * sqrt(1.0-pow2(x)));
 }
 
@@ -305,7 +306,7 @@ filter_kaiser(double const x) {
 
 #define radius_normal (1.0)
 
-static double 
+static double
 filter_normal(double const x) {
     return exp(-pow2(x)/2.0) / sqrt(2.0*M_PI);
 }
@@ -316,7 +317,7 @@ filter_normal(double const x) {
 
 #define radius_hermite  (1.0)
 
-static double 
+static double
 filter_hermite(double const x) {
     /* f(x) = 2|x|^3 - 3|x|^2 + 1, -1 <= x <= 1 */
 
@@ -333,7 +334,7 @@ filter_hermite(double const x) {
 
 #define radius_lanczos (3.0)
 
-static double 
+static double
 filter_lanczos(double const x) {
 
     double const absx = x < 0.0 ? -x : x;
@@ -352,30 +353,30 @@ typedef struct {
         /* This is how far from the Y axis (on either side) the
            function has significant value.  (You can use this to limit
            how much of your domain you bother to compute the function
-           over).  
+           over).
         */
     bool windowed;
 } filter;
 
 
 static filter Filters[] = {
-    { "point",     filter_box,       radius_point,     FALSE },
-    { "box",       filter_box,       radius_box,       FALSE },
-    { "triangle",  filter_triangle,  radius_triangle,  FALSE },
-    { "quadratic", filter_quadratic, radius_quadratic, FALSE },
-    { "cubic",     filter_cubic,     radius_cubic,     FALSE },
-    { "catrom",    filter_catrom,    radius_catrom,    FALSE },
-    { "mitchell",  filter_mitchell,  radius_mitchell,  FALSE },
-    { "gauss",     filter_gauss,     radius_gauss,     FALSE },
-    { "sinc",      filter_sinc,      radius_sinc,      TRUE  },
-    { "bessel",    filter_bessel,    radius_bessel,    TRUE  },
-    { "hanning",   filter_hanning,   radius_hanning,   FALSE },
-    { "hamming",   filter_hamming,   radius_hamming,   FALSE },
-    { "blackman",  filter_blackman,  radius_blackman,  FALSE },
-    { "kaiser",    filter_kaiser,    radius_kaiser,    FALSE },
-    { "normal",    filter_normal,    radius_normal,    FALSE },
-    { "hermite",   filter_hermite,   radius_hermite,   FALSE },
-    { "lanczos",   filter_lanczos,   radius_lanczos,   FALSE },
+    { "point",     filter_box,       radius_point,     false },
+    { "box",       filter_box,       radius_box,       false },
+    { "triangle",  filter_triangle,  radius_triangle,  false },
+    { "quadratic", filter_quadratic, radius_quadratic, false },
+    { "cubic",     filter_cubic,     radius_cubic,     false },
+    { "catrom",    filter_catrom,    radius_catrom,    false },
+    { "mitchell",  filter_mitchell,  radius_mitchell,  false },
+    { "gauss",     filter_gauss,     radius_gauss,     false },
+    { "sinc",      filter_sinc,      radius_sinc,      true  },
+    { "bessel",    filter_bessel,    radius_bessel,    true  },
+    { "hanning",   filter_hanning,   radius_hanning,   false },
+    { "hamming",   filter_hamming,   radius_hamming,   false },
+    { "blackman",  filter_blackman,  radius_blackman,  false },
+    { "kaiser",    filter_kaiser,    radius_kaiser,    false },
+    { "normal",    filter_normal,    radius_normal,    false },
+    { "hermite",   filter_hermite,   radius_hermite,   false },
+    { "lanczos",   filter_lanczos,   radius_lanczos,   false },
    { NULL },
 };
 
@@ -416,29 +417,30 @@ struct CmdlineInfo {
      * in a form easy for the program to use.
      */
     const char * inputFileName;  /* Filespec of input file */
+    unsigned int reportonly;
     unsigned int nomix;
     basicFunction_t filterFunction; /* NULL if not using resample method */
     basicFunction_t windowFunction;
         /* Meaningful only when filterFunction != NULL */
-    double filterRadius;           
+    double filterRadius;
         /* Meaningful only when filterFunction != NULL */
     enum scaleType scaleType;
     /* 'xsize' and 'ysize' are numbers of pixels.  Their meaning depends upon
-       'scaleType'.  for SCALE_BOXFIT and SCALE_BOXFILL, they are the box 
+       'scaleType'.  for SCALE_BOXFIT and SCALE_BOXFILL, they are the box
        dimensions.  For SCALE_SEPARATE, they are the separate dimensions, or
        zero to indicate unspecified.  For SCALE_PIXELMAX, they are
        meaningless.
     */
     unsigned int xsize;
     unsigned int ysize;
-    /* 'xscale' and 'yscale' are meaningful only for scaleType == 
+    /* 'xscale' and 'yscale' are meaningful only for scaleType ==
        SCALE_SEPARATE and only where the corresponding xsize/ysize is
        unspecified.  0.0 means unspecified.
     */
     float xscale;
     float yscale;
     /* 'pixels' is meaningful only for scaleType == SCALE_PIXELMAX */
-    unsigned int pixels; 
+    unsigned int pixels;
     unsigned int linear;
     unsigned int verbose;
 };
@@ -452,12 +454,12 @@ lookupFilterByName(const char * const filtername,
     unsigned int i;
     bool found;
 
-    found = FALSE;  /* initial assumption */
+    found = false;  /* initial assumption */
 
     for (i=0; Filters[i].name; ++i) {
         if (strcmp(filtername, Filters[i].name) == 0) {
             *filterP = Filters[i];
-            found = TRUE;
+            found = true;
         }
     }
     if (!found) {
@@ -466,7 +468,7 @@ lookupFilterByName(const char * const filtername,
         strcpy(known_filters, "");
         for (i = 0; Filters[i].name; ++i) {
             const char * const name = Filters[i].name;
-            if (strlen(known_filters) + strlen(name) + 1 + 1 < 
+            if (strlen(known_filters) + strlen(name) + 1 + 1 <
                 sizeof(known_filters)) {
                 strcat(known_filters, name);
                 strcat(known_filters, " ");
@@ -489,13 +491,13 @@ processFilterOptions(unsigned int const         filterSpec,
     if (filterSpec) {
         filter baseFilter;
         lookupFilterByName(filterOpt, &baseFilter);
-        cmdlineP->filterFunction = baseFilter.function; 
+        cmdlineP->filterFunction = baseFilter.function;
         cmdlineP->filterRadius   = baseFilter.radius;
 
         if (windowSpec) {
             filter windowFilter;
             lookupFilterByName(windowOpt, &windowFilter);
-            
+
             if (cmdlineP->windowFunction == filter_box)
                 cmdlineP->windowFunction = NULL;
             else
@@ -526,28 +528,28 @@ parseSizeParm(const char *   const sizeString,
 
     sizeLong = strtol(sizeString, &endptr, 10);
     if (strlen(sizeString) > 0 && *endptr != '\0')
-        pm_error("%s size argument not an integer: '%s'", 
+        pm_error("%s size argument not an integer: '%s'",
                  description, sizeString);
     else if (sizeLong > INT_MAX - 2)
         pm_error("%s size argument is too large "
-                 "for computations: %ld", 
+                 "for computations: %ld",
                  description, sizeLong);
     else if (sizeLong <= 0)
-        pm_error("%s size argument is not positive: %ld", 
+        pm_error("%s size argument is not positive: %ld",
                  description, sizeLong);
     else
         *sizeP = (unsigned int) sizeLong;
-}        
+}
 
 
 
 static void
-parseXyParms(int                  const argc, 
+parseXyParms(int                  const argc,
              const char **        const argv,
              struct CmdlineInfo * const cmdlineP) {
 
     /* parameters are box width (columns), box height (rows), and
-       optional filespec 
+       optional filespec
     */
     if (argc-1 < 2)
         pm_error("You must supply at least two parameters with "
@@ -571,7 +573,7 @@ parseXyParms(int                  const argc,
 
 
 static void
-parseScaleParms(int                   const argc, 
+parseScaleParms(int                   const argc,
                 const char **         const argv,
                 struct CmdlineInfo  * const cmdlineP) {
 /*----------------------------------------------------------------------------
@@ -583,7 +585,7 @@ parseScaleParms(int                   const argc,
                  "one parameter: the scale factor.");
     else {
         cmdlineP->xscale = cmdlineP->yscale = atof(argv[1]);
-        
+
         if (cmdlineP->xscale <= 0.0)
             pm_error("The scale parameter %s is not a positive number.",
                      argv[1]);
@@ -592,7 +594,7 @@ parseScaleParms(int                   const argc,
                 cmdlineP->inputFileName = "-";
             else {
                 cmdlineP->inputFileName = argv[2];
-                
+
                 if (argc-1 > 2)
                     pm_error("Too many arguments.  There are at most two "
                              "arguments with this set of options: "
@@ -606,7 +608,7 @@ parseScaleParms(int                   const argc,
 
 
 static void
-parseFilespecOnlyParms(int                   const argc, 
+parseFilespecOnlyParms(int                   const argc,
                        const char **         const argv,
                        struct CmdlineInfo  * const cmdlineP) {
 
@@ -618,13 +620,13 @@ parseFilespecOnlyParms(int                   const argc,
 }
 
 
-static void 
-parseCommandLine(int argc, 
-                 const char ** argv, 
+static void
+parseCommandLine(int argc,
+                 const char ** argv,
                  struct CmdlineInfo  * const cmdlineP) {
 /* --------------------------------------------------------------------------
    Parse program command line described in Unix standard form by argc
-   and argv.  Return the information in the options as *cmdlineP.  
+   and argv.  Return the information in the options as *cmdlineP.
 
    If command line is internally inconsistent (invalid options, etc.),
    issue error message to stderr and abort program.
@@ -635,13 +637,14 @@ parseCommandLine(int argc,
     optEntry * option_def;
     optStruct3 opt;
         /* Instructions to pm_optParseOptions3 on how to parse our options. */
-  
+
     unsigned int option_def_index;
     unsigned int xyfit, xyfill;
     int xsize, ysize, pixels;
     int reduce;
     float xscale, yscale;
-    const char *filterOpt, *window;
+    const char * filterOpt;
+    const char * window;
     unsigned int filterSpec, windowSpec;
     unsigned int xscaleSpec, yscaleSpec, xsizeSpec, ysizeSpec;
     unsigned int pixelsSpec, reduceSpec;
@@ -665,15 +668,17 @@ parseCommandLine(int argc,
     OPTENT3(0, "window",    OPT_STRING,  &window,    &windowSpec,          0);
     OPTENT3(0, "nomix",     OPT_FLAG,    NULL,       &cmdlineP->nomix,     0);
     OPTENT3(0, "linear",    OPT_FLAG,    NULL,       &cmdlineP->linear,    0);
-  
+    OPTENT3(0, "reportonly",        OPT_FLAG,    NULL,
+            &cmdlineP->reportonly,    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. */
 
-    if (cmdlineP->nomix && filterSpec) 
+    if (cmdlineP->nomix && filterSpec)
         pm_error("You cannot specify both -nomix and -filter.");
 
     processFilterOptions(filterSpec, filterOpt, windowSpec, window,
@@ -694,19 +699,19 @@ parseCommandLine(int argc,
         pm_error("Cannot specify both -xsize/width and -xscale.");
     if (ysizeSpec && yscaleSpec)
         pm_error("Cannot specify both -ysize/height and -yscale.");
-    
+
     if ((xyfit || xyfill) &&
-        (xsizeSpec || xscaleSpec || ysizeSpec || yscaleSpec || 
+        (xsizeSpec || xscaleSpec || ysizeSpec || yscaleSpec ||
          reduceSpec || pixelsSpec) )
         pm_error("Cannot specify -xyfit/xyfill/xysize with other "
                  "dimension options.");
     if (xyfit && xyfill)
         pm_error("Cannot specify both -xyfit and -xyfill");
-    if (pixelsSpec && 
+    if (pixelsSpec &&
         (xsizeSpec || xscaleSpec || ysizeSpec || yscaleSpec ||
          reduceSpec) )
         pm_error("Cannot specify -pixels with other dimension options.");
-    if (reduceSpec && 
+    if (reduceSpec &&
         (xsizeSpec || xscaleSpec || ysizeSpec || yscaleSpec) )
         pm_error("Cannot specify -reduce with other dimension options.");
 
@@ -722,9 +727,9 @@ parseCommandLine(int argc,
         cmdlineP->scaleType = SCALE_SEPARATE;
         parseFilespecOnlyParms(argc, argv, cmdlineP);
         cmdlineP->xsize = cmdlineP->ysize = 0;
-        cmdlineP->xscale = cmdlineP->yscale = 
+        cmdlineP->xscale = cmdlineP->yscale =
             ((double) 1.0) / ((double) reduce);
-        pm_message("reducing by %d gives scale factor of %f.", 
+        pm_message("reducing by %d gives scale factor of %f.",
                    reduce, cmdlineP->xscale);
     } else if (pixelsSpec) {
         cmdlineP->scaleType = SCALE_PIXELMAX;
@@ -746,12 +751,12 @@ parseCommandLine(int argc,
 
 
 
-static void 
-computeOutputDimensions(struct CmdlineInfo  const cmdline, 
-                        unsigned int        const cols, 
-                        unsigned int        const rows, 
+static void
+computeOutputDimensions(struct CmdlineInfo  const cmdline,
+                        unsigned int        const cols,
+                        unsigned int        const rows,
                         int *               const newcolsP,
-                        int *               const newrowsP) { 
+                        int *               const newrowsP) {
 
     double newcolsD, newrowsD;
         /* Intermediate calculation of the output dimensions, in double
@@ -775,10 +780,10 @@ computeOutputDimensions(struct CmdlineInfo  const cmdline,
     case SCALE_BOXFIT:
     case SCALE_BOXFILL: {
         double const aspect_ratio = (float) cols / (float) rows;
-        double const box_aspect_ratio = 
+        double const box_aspect_ratio =
             (float) cmdline.xsize / (float) cmdline.ysize;
-        
-        if ((box_aspect_ratio > aspect_ratio && 
+
+        if ((box_aspect_ratio > aspect_ratio &&
              cmdline.scaleType == SCALE_BOXFIT) ||
             (box_aspect_ratio < aspect_ratio &&
              cmdline.scaleType == SCALE_BOXFILL)) {
@@ -809,7 +814,7 @@ computeOutputDimensions(struct CmdlineInfo  const cmdline,
             newrowsD = rows;
     }
     }
-    
+
     /* If the rounding yields a zero dimension, we fudge it up to 1.  We do
        this rather than considering it a specification error (and dying)
        because it's friendlier to automated processes that work on arbitrary
@@ -898,9 +903,9 @@ typedef struct {
            at index 0.
         */
     unsigned int allocWeight;
-        /* Number of allocated frames in 'Weight' */ 
+        /* Number of allocated frames in 'Weight' */
     WEIGHT *Weight;
-        /* The terms of the linear combination.  Has 'nWeight' elements. 
+        /* The terms of the linear combination.  Has 'nWeight' elements.
            The coefficients (weights) of each add up to unity.
         */
 } WLIST;
@@ -911,7 +916,7 @@ typedef struct {
         /* The row number in the input image of the row.
            -1 means no row.
         */
-    tuple *tuplerow;  
+    tuple *tuplerow;
         /* The tuples of the row.
            If rowNumber = -1, these are arbitrary, but allocated, tuples.
         */
@@ -957,7 +962,7 @@ appendWeight(WLIST * const WList,
         unsigned int const n = WList->nWeight;
 
         assert(WList->allocWeight >= n+1);
-        
+
         WList->Weight[n].position = index;
         WList->Weight[n].weight   = weight;
         ++WList->nWeight;
@@ -977,7 +982,7 @@ unnormalize(double const normalized,
        wrong thing in actual practice, EG on Darwin PowerPC (my iBook
        running OS X) negative values clamp to maxval.  We get negative
        values because some of the filters (EG catrom) have negative
-       weights.  
+       weights.
     */
 
     return MIN(maxval, (sample)(MAX(0.0, (normalized*maxval + 0.5))));
@@ -1007,10 +1012,10 @@ createWeightList(unsigned int          const targetPos,
                  WLIST *               const weightListP) {
 /*----------------------------------------------------------------------------
    Create a weight list for computing target pixel number 'targetPos' from
-   a set of source pixels.  These pixels are a line of pixels either 
+   a set of source pixels.  These pixels are a line of pixels either
    horizontally or vertically.  The weight list is a list of weights to give
    each source pixel in the set.
-   
+
    The source pixel set is a window of source pixels centered on some
    point.  The weights are defined by the function 'filter' of
    the position within the window, and normalized to add up to 1.0.
@@ -1034,7 +1039,7 @@ createWeightList(unsigned int          const targetPos,
 
    We want to calculate 3 weights, one to be applied to each source pixel
    in computing the target pixel.  Ideally, we would compute the average
-   height of the filter function over each source pixel region.  But 
+   height of the filter function over each source pixel region.  But
    that's too hard.  So we approximate by assuming that the filter function
    is constant within each region, at the value the function has at the
    _center_ of the region.
@@ -1064,12 +1069,12 @@ createWeightList(unsigned int          const targetPos,
        'leftPixel' and 'rightPixel' are the pixel positions of the
        pixels at the edges of that window.  Note that if we're
        doing vertical weights, "left" and "right" mean top and
-       bottom.  
+       bottom.
     */
     double const windowCenter = ((double)targetPos + 0.5) / scale;
     double left = MAX(0.0, windowCenter - filter.radius - EPSILON);
     unsigned int const leftPixel = floor(left);
-    double right = MIN((double)sourceSize - EPSILON, 
+    double right = MIN((double)sourceSize - EPSILON,
                        windowCenter + filter.radius + EPSILON);
     unsigned int const rightPixel = floor(right);
 
@@ -1082,7 +1087,7 @@ createWeightList(unsigned int          const targetPos,
     norm = 0.0;  /* initial value */
 
     for (j = leftPixel; j <= rightPixel; ++j) {
-        /* Calculate the weight that source pixel 'j' will have in the 
+        /* Calculate the weight that source pixel 'j' will have in the
            value of target pixel 'targetPos'.
         */
         double const regionLeft   = MAX(left, (double)j);
@@ -1132,7 +1137,7 @@ createWeightListSet(unsigned int          const sourceSize,
    pixels in a region to effect a resampling.  Multiplying by these
    factors effects all of the following transformations on the
    original pixels:
-   
+
    1) Filter out any frequencies that are artifacts of the
       original sampling.  We assume a perfect sampling was done,
       which means the original continuous dataset had a maximum
@@ -1140,12 +1145,12 @@ createWeightListSet(unsigned int          const sourceSize,
       above that is an artifact of the sampling.  So we filter out
       anything above 1/2 of the original sample rate (sample rate
       == pixel resolution).
-      
+
    2) Filter out any frequencies that are too high to be captured
       by the new sampling -- i.e. frequencies above 1/2 the new
       sample rate.  This is the information we must lose because of low
       sample rate.
-      
+
    3) Sample the result at the new sample rate.
 
    We do all three of these steps in a single convolution of the
@@ -1154,7 +1159,7 @@ createWeightListSet(unsigned int          const sourceSize,
    rectangle function is a pixel domain sinc function, which is
    what we assume 'filterFunction' is.  We get Step 3 by computing
    the convolution only at the new sample points.
-   
+
    I don't know what any of this means when 'filterFunction' is
    not sinc.  Maybe it just means some approximation or additional
    filtering steps are happening.
@@ -1164,7 +1169,7 @@ createWeightListSet(unsigned int          const sourceSize,
     unsigned int targetPos;
 
     MALLOCARRAY_NOFAIL(weightListSet, targetSize);
-    
+
     for (targetPos = 0; targetPos < targetSize; ++targetPos)
         createWeightList(targetPos, sourceSize, scale, filterFunction,
                          &weightListSet[targetPos]);
@@ -1210,7 +1215,7 @@ makeFilterFunction(double          const scale,
 
     retval.basicFunction = basicFunction;
     retval.windowFunction = windowFunction;
-    
+
     retval.horizontalScaler = freqLimit;
 
     /* Our 'windowFunction' argument is a function normalized to the
@@ -1235,11 +1240,11 @@ makeFilterFunction(double          const scale,
 
     return retval;
 }
-                   
 
-                   
+
+
 static void
-destroyWeightListSet(WLIST *      const weightListSet, 
+destroyWeightListSet(WLIST *      const weightListSet,
                      unsigned int const size) {
 
     unsigned int i;
@@ -1264,14 +1269,14 @@ createScanBuf(struct pam * const pamP,
     scanbuf.width = pamP->width;
     scanbuf.height = maxRowWeights;
     MALLOCARRAY_NOFAIL(scanbuf.line, scanbuf.height);
-  
+
     for (lineNumber = 0; lineNumber < scanbuf.height; ++lineNumber) {
         scanbuf.line[lineNumber].rowNumber = -1;
         scanbuf.line[lineNumber].tuplerow = pnm_allocpamrow(pamP);
     }
-  
+
     if (verbose)
-        pm_message("scanline buffer: %d lines of %d pixels", 
+        pm_message("scanline buffer: %d lines of %d pixels",
                    scanbuf.height, scanbuf.width);
 
     *scanbufP = scanbuf;
@@ -1296,10 +1301,10 @@ static void
 resampleDimensionMessage(struct pam * const inpamP,
                          struct pam * const outpamP) {
 
-    pm_message ("resampling from %d*%d to %d*%d (%f, %f)", 
-                inpamP->width, inpamP->height, 
+    pm_message ("resampling from %d*%d to %d*%d (%f, %f)",
+                inpamP->width, inpamP->height,
                 outpamP->width, outpamP->height,
-                (double)outpamP->width/inpamP->width, 
+                (double)outpamP->width/inpamP->width,
                 (double)outpamP->height/inpamP->height);
 }
 
@@ -1325,12 +1330,12 @@ addInPixel(const struct pam * const pamP,
     for (plane = 0; plane < pamP->depth; ++plane) {
         double const normalizedSample = (double)tuple[plane]/pamP->maxval;
         double opacityAdjustment;
-        
+
         if (haveOpacity && plane != opacityPlane)
             opacityAdjustment = (double)tuple[opacityPlane]/pamP->maxval;
         else
             opacityAdjustment = 1;
-        
+
         accum[plane] += opacityAdjustment * normalizedSample * weight;
     }
 }
@@ -1340,7 +1345,7 @@ addInPixel(const struct pam * const pamP,
 static void
 generateOutputTuple(const struct pam * const pamP,
                     double             const accum[],
-                    bool               const haveOpacity, 
+                    bool               const haveOpacity,
                     unsigned int       const opacityPlane,
                     tuple *            const tupleP) {
 /*----------------------------------------------------------------------------
@@ -1358,7 +1363,7 @@ generateOutputTuple(const struct pam * const pamP,
         if (haveOpacity && plane != opacityPlane) {
             if (accum[opacityPlane] < EPSILON) {
                 opacityAdjustedSample = 0.0;
-            } else 
+            } else
                 opacityAdjustedSample = accum[plane] / accum[opacityPlane];
         } else
             opacityAdjustedSample = accum[plane];
@@ -1377,7 +1382,7 @@ outputOneResampledRow(const struct pam * const outpamP,
                       tuple *            const line,
                       double *           const accum) {
 /*----------------------------------------------------------------------------
-   From the data in 'scanbuf' and weights in 'YW' and 'XWeight', 
+   From the data in 'scanbuf' and weights in 'YW' and 'XWeight',
    generate one output row for the image described by *outpamP and
    output it.
 
@@ -1411,24 +1416,24 @@ outputOneResampledRow(const struct pam * const outpamP,
             for (plane = 0; plane < outpamP->depth; ++plane)
                 accum[plane] = 0.0;
         }
-        
+
         for (i = 0; i < YW.nWeight; ++i) {
             int   const yp   = YW.Weight[i].position;
             float const yw   = YW.Weight[i].weight;
             int   const slot = yp % scanbuf.height;
 
             unsigned int j;
-            
+
             for (j = 0; j < XW.nWeight; ++j) {
                 int   const xp    = XW.Weight[j].position;
                 tuple const tuple = scanbuf.line[slot].tuplerow[xp];
-                
-                addInPixel(outpamP, tuple, yw * XW.Weight[j].weight, 
+
+                addInPixel(outpamP, tuple, yw * XW.Weight[j].weight,
                            haveOpacity, opacityPlane,
                            accum);
             }
         }
-        generateOutputTuple(outpamP, accum, haveOpacity, opacityPlane, 
+        generateOutputTuple(outpamP, accum, haveOpacity, opacityPlane,
                             &line[col]);
     }
     pnm_writepamrow(outpamP, line);
@@ -1440,15 +1445,15 @@ static bool
 scanbufContainsTheRows(SCAN  const scanbuf,
                        WLIST const rowWeights) {
 /*----------------------------------------------------------------------------
-   Return TRUE iff scanbuf 'scanbuf' contains every row mentioned in
+   Return true iff scanbuf 'scanbuf' contains every row mentioned in
    'rowWeights'.
 
    It might contain additional rows besides.
 -----------------------------------------------------------------------------*/
     bool missingRow;
     unsigned int i;
-    
-    for (i = 0, missingRow = FALSE;
+
+    for (i = 0, missingRow = false;
          i < rowWeights.nWeight && !missingRow;
         ++i) {
         unsigned int const inputRow = rowWeights.Weight[i].position;
@@ -1461,7 +1466,7 @@ scanbufContainsTheRows(SCAN  const scanbuf,
             /* Nope, this slot has some other row or no row at all.
                So the row we're looking for isn't in the scanbuf.
             */
-            missingRow = TRUE;
+            missingRow = true;
         }
     }
     return !missingRow;
@@ -1481,7 +1486,7 @@ createWeightLists(struct pam *     const inpamP,
 /*----------------------------------------------------------------------------
    This is the function that actually does the resampling.  Note that it
    does it without ever looking at the source or target pixels!  It produces
-   a simple set of numbers that Caller can blindly apply to the source 
+   a simple set of numbers that Caller can blindly apply to the source
    pixels to get target pixels.
 -----------------------------------------------------------------------------*/
     struct filterFunction horizFilter, vertFilter;
@@ -1490,14 +1495,14 @@ createWeightLists(struct pam *     const inpamP,
         (double)outpamP->width/inpamP->width,
         filterFunction, filterRadius, windowFunction);
 
-    createWeightListSet(inpamP->width, outpamP->width, horizFilter, 
+    createWeightListSet(inpamP->width, outpamP->width, horizFilter,
                         horizWeightP);
-    
+
     vertFilter = makeFilterFunction(
         (double)outpamP->height/inpamP->height,
         filterFunction, filterRadius, windowFunction);
 
-    createWeightListSet(inpamP->height, outpamP->height, vertFilter, 
+    createWeightListSet(inpamP->height, outpamP->height, vertFilter,
                         vertWeightP);
 
     *maxRowWeightsP = ceil(2.0*(vertFilter.radius+EPSILON) + 1 + EPSILON);
@@ -1516,12 +1521,12 @@ resample(struct pam *     const inpamP,
 /*---------------------------------------------------------------------------
   Resample the image in the input file, described by *inpamP,
   so as to create the image in the output file, described by *outpamP.
-  
+
   Input and output differ by height, width, and maxval only.
 
   Use the resampling filter function 'filterFunction', applied over
   radius 'filterRadius'.
-  
+
   The input file is positioned past the header, to the beginning of the
   raster.  The output file is too.
 ---------------------------------------------------------------------------*/
@@ -1539,7 +1544,7 @@ resample(struct pam *     const inpamP,
     if (linear)
         pm_error("You cannot use the resampling scaling method on "
                  "linear input.");
-  
+
     createWeightLists(inpamP, outpamP, filterFunction, filterRadius,
                       windowFunction, &horizWeight, &vertWeight,
                       &maxRowWeights);
@@ -1569,21 +1574,21 @@ resample(struct pam *     const inpamP,
         /* Output all the rows we can make out of the current contents of
            the scanbuf.  Might be none.
         */
-        needMoreInput = FALSE;  /* initial assumption */
+        needMoreInput = false;  /* initial assumption */
         while (outputRow < outpamP->height && !needMoreInput) {
             WLIST const rowWeights = vertWeight[outputRow];
                 /* The description of what makes up our current output row;
                    i.e. what fractions of which input rows combine to create
                    this output row.
                 */
-            assert(rowWeights.nWeight <= scanbuf.height); 
+            assert(rowWeights.nWeight <= scanbuf.height);
 
             if (scanbufContainsTheRows(scanbuf, rowWeights)) {
-                outputOneResampledRow(outpamP, scanbuf, rowWeights, 
+                outputOneResampledRow(outpamP, scanbuf, rowWeights,
                                       horizWeight, line, weight);
                 ++outputRow;
             } else
-                needMoreInput = TRUE;
+                needMoreInput = true;
         }
     }
 
@@ -1609,7 +1614,7 @@ resample(struct pam *     const inpamP,
 static void
 zeroNewRow(struct pam * const pamP,
            tuplen *     const tuplenrow) {
-    
+
     unsigned int col;
 
     for (col = 0; col < pamP->width; ++col) {
@@ -1625,7 +1630,7 @@ zeroNewRow(struct pam * const pamP,
 static void
 accumOutputCol(struct pam * const pamP,
                tuplen       const intuplen,
-               float        const fraction, 
+               float        const fraction,
                tuplen       const accumulator) {
 /*----------------------------------------------------------------------------
    Add fraction 'fraction' of the pixel indicated by 'intuplen' to the
@@ -1645,7 +1650,7 @@ accumOutputCol(struct pam * const pamP,
 
 
 static void
-horizontalScale(tuplen *     const inputtuplenrow, 
+horizontalScale(tuplen *     const inputtuplenrow,
                 tuplen *     const newtuplenrow,
                 struct pam * const inpamP,
                 struct pam * const outpamP,
@@ -1683,7 +1688,7 @@ horizontalScale(tuplen *     const inputtuplenrow,
             /* Generate one output pixel in 'newtuplerow'.  It will
                consist of anything accumulated from prior input pixels
                in accumulator[], plus a fraction of the current input
-               pixel.  
+               pixel.
             */
             assert(newcol < outpamP->width);
             accumOutputCol(inpamP, inputtuplenrow[col], fraccoltofill,
@@ -1695,7 +1700,7 @@ horizontalScale(tuplen *     const inputtuplenrow,
             ++newcol;
             fraccoltofill = 1.0;
         }
-        /* There's not enough left in the current input pixel to fill up 
+        /* There's not enough left in the current input pixel to fill up
            a whole output column, so just accumulate the remainder of the
            pixel into the current output column.  Because of rounding, we may
            have a tiny bit of pixel left and have run out of output pixels.
@@ -1714,17 +1719,17 @@ horizontalScale(tuplen *     const inputtuplenrow,
                  newcol, outpamP->width-1);
 
     if (newcol < outpamP->width) {
-        /* We were still working on the last output column when we 
+        /* We were still working on the last output column when we
            ran out of input columns.  This would be because of rounding
            down, and we should be missing only a tiny fraction of that
            last output column.  Just fill in the missing color with the
            color of the rightmost input pixel.
         */
-        accumOutputCol(inpamP, inputtuplenrow[inpamP->width-1], 
+        accumOutputCol(inpamP, inputtuplenrow[inpamP->width-1],
                        fraccoltofill, newtuplenrow[newcol]);
-        
+
         *stretchP = fraccoltofill;
-    } else 
+    } else
         *stretchP = 0.0;
 }
 
@@ -1747,11 +1752,11 @@ zeroAccum(struct pam * const pamP,
 
 static void
 accumOutputRow(struct pam * const pamP,
-               tuplen *     const tuplenrow, 
-               float        const fraction, 
+               tuplen *     const tuplenrow,
+               float        const fraction,
                tuplen *     const accumulator) {
 /*----------------------------------------------------------------------------
-   Take 'fraction' times the samples in row 'tuplenrow' and add it to 
+   Take 'fraction' times the samples in row 'tuplenrow' and add it to
    'accumulator' in the same way as accumOutputCol().
 
    'fraction' is less than 1.0.
@@ -1838,7 +1843,7 @@ writeARow(struct pam *             const pamP,
 
 
 static void
-issueStretchWarning(bool   const verbose, 
+issueStretchWarning(bool   const verbose,
                     double const fracrowtofill) {
 
     /* We need another input row to fill up this
@@ -1847,11 +1852,11 @@ issueStretchWarning(bool   const verbose,
        scaling arithmetic.  So we go ahead with
        the data from the last row we read, which
        amounts to stretching out the last output
-       row.  
+       row.
     */
     if (verbose)
         pm_message("%f of bottom row stretched because of "
-                   "arithmetic imprecision", 
+                   "arithmetic imprecision",
                    fracrowtofill);
 }
 
@@ -1873,21 +1878,21 @@ scaleHorizontallyAndOutputRow(struct pam *             const inpamP,
    'newtuplenrow' is work space Caller provides us.  It is at least
    wide enough to hold one output row.
 -----------------------------------------------------------------------------*/
-    if (outpamP->width == inpamP->width)    
+    if (outpamP->width == inpamP->width)
         /* shortcut X scaling */
         writeARow(outpamP, rowAccumulator, transform);
             /* This destroys 'rowAccumulator' */
     else {
         float stretch;
-            
+
         horizontalScale(rowAccumulator, newtuplenrow, inpamP, outpamP,
                         xscale, &stretch);
-            
+
         if (verbose && row == 0)
             pm_message("%f of right column stretched because of "
-                       "arithmetic imprecision", 
+                       "arithmetic imprecision",
                        stretch);
-            
+
         writeARow(outpamP, newtuplenrow, transform);
             /* This destroys 'newtuplenrow' */
     }
@@ -1919,7 +1924,7 @@ destroyTransforms(const pnm_transformMap * const inputTransform,
 
     if (inputTransform)
         free((void*)inputTransform);
-    
+
     if (outputTransform)
         free((void*)outputTransform);
 }
@@ -1929,7 +1934,7 @@ destroyTransforms(const pnm_transformMap * const inputTransform,
 static void
 scaleWithMixing(struct pam * const inpamP,
                 struct pam * const outpamP,
-                float        const xscale, 
+                float        const xscale,
                 float        const yscale,
                 bool         const assumeLinear,
                 bool         const verbose) {
@@ -1950,8 +1955,8 @@ scaleWithMixing(struct pam * const inpamP,
   approximate but fast results).
 
 -----------------------------------------------------------------------------*/
-    /* Here's how we think of the color mixing scaling operation:  
-       
+    /* Here's how we think of the color mixing scaling operation:
+
        First, I'll describe scaling in one dimension.  Assume we have
        a one row image.  A raster row is ordinarily a sequence of
        discrete pixels which have no width and no distance between
@@ -1973,7 +1978,7 @@ scaleWithMixing(struct pam * const inpamP,
        look the same.
 
        This works for all scale factors, both scaling up and scaling down.
-       
+
        For images with an opacity plane, imagine Input Pixel 0's
        foreground is fully opaque red (1,0,0,1), and Input Pixel 1 is
        fully transparent (foreground irrelevant) (0,0,0,0).  We make
@@ -1994,14 +1999,14 @@ scaleWithMixing(struct pam * const inpamP,
        stretch the image vertically first (same process as above, but
        in place of a single-color pixels, we have a vector of colors).
        Then we take each row this vertical stretching generates and
-       stretch it horizontally.  
+       stretch it horizontally.
     */
 
     tuplen * tuplenrow;     /* An input row */
     tuplen * newtuplenrow;  /* Working space */
     float rowsleft;
     /* The number of rows of output that need to be formed from the
-       current input row (the one in tuplerow[]), less the number that 
+       current input row (the one in tuplerow[]), less the number that
        have already been formed (either in accumulator[]
        or output to the file).  This can be fractional because of the
        way we define rows as having height.
@@ -2021,8 +2026,8 @@ scaleWithMixing(struct pam * const inpamP,
     int row;
     const pnm_transformMap * inputTransform;
     const pnm_transformMap * outputTransform;
-    
-    tuplenrow = pnm_allocpamrown(inpamP); 
+
+    tuplenrow = pnm_allocpamrown(inpamP);
     rowAccumulator = pnm_allocpamrown(inpamP);
 
     rowsread = 0;
@@ -2087,7 +2092,7 @@ scaleWithMixing(struct pam * const inpamP,
 static void
 scaleWithoutMixing(const struct pam * const inpamP,
                    const struct pam * const outpamP,
-                   float              const xscale, 
+                   float              const xscale,
                    float              const yscale) {
 /*----------------------------------------------------------------------------
   Scale the image described by *inpamP by xscale horizontally and
@@ -2096,9 +2101,9 @@ scaleWithoutMixing(const struct pam * const inpamP,
 
   The input file is positioned past the header, to the beginning of the
   raster.  The output file is too.
-  
+
   Don't mix colors from different input pixels together in the output
-  pixels.  Each output pixel is an exact copy of some corresponding 
+  pixels.  Each output pixel is an exact copy of some corresponding
   input pixel.
 -----------------------------------------------------------------------------*/
     tuple * tuplerow;  /* An input row */
@@ -2111,14 +2116,14 @@ scaleWithoutMixing(const struct pam * const inpamP,
     assert(outpamP->maxval == inpamP->maxval);
     assert(outpamP->depth  == inpamP->depth);
 
-    tuplerow = pnm_allocpamrow(inpamP); 
+    tuplerow = pnm_allocpamrow(inpamP);
     rowInInput = 0;
 
     newtuplerow = pnm_allocpamrow(outpamP);
 
     for (row = 0; row < outpamP->height; ++row) {
         unsigned int col;
-        
+
         unsigned int const inputRow = (int) (row / yscale);
             /* The number of the input row that we will use for this output
                row.
@@ -2126,10 +2131,10 @@ scaleWithoutMixing(const struct pam * const inpamP,
 
         for (; rowInInput <= inputRow; ++rowInInput)
             pnm_readpamrow(inpamP, tuplerow);
-        
+
         for (col = 0; col < outpamP->width; ++col) {
             unsigned int const inputCol = (int) (col / xscale);
-            
+
             pnm_assigntuple(inpamP, newtuplerow[col], tuplerow[inputCol]);
         }
 
@@ -2147,12 +2152,58 @@ scaleWithoutMixing(const struct pam * const inpamP,
 }
 
 
+static void
+skipImage(struct pam * const pamP) {
+
+        tuple * tuplerow;
+        unsigned int row;
+
+        tuplerow = pnm_allocpamrow(pamP);
+
+        for (row = 0; row < pamP->height; ++row)
+            pnm_readpamrow(pamP, tuplerow);
+
+        pnm_freepamrow(tuplerow);
+}
+
+
+
+static void
+scale(FILE *             const ifP,
+      struct pam *       const inpamP,
+      struct pam *       const outpamP,
+      float              const xscale,
+      float              const yscale,
+      struct CmdlineInfo const cmdline) {
+
+    pnm_writepaminit(outpamP);
+
+    if (cmdline.nomix) {
+        if (cmdline.verbose)
+            pm_message("Using nomix method");
+        scaleWithoutMixing(inpamP, outpamP, xscale, yscale);
+    } else if (!cmdline.filterFunction) {
+        if (cmdline.verbose)
+            pm_message("Using simple pixel mixing rescaling method");
+        scaleWithMixing(inpamP, outpamP, xscale, yscale,
+                        cmdline.linear, cmdline.verbose);
+    } else {
+        if (cmdline.verbose)
+            pm_message("Using general filter method");
+        resample(inpamP, outpamP,
+                 cmdline.filterFunction, cmdline.filterRadius,
+                 cmdline.windowFunction, cmdline.verbose,
+                 cmdline.linear);
+    }
+}
+
+
 
 static void
 pamscale(FILE *             const ifP,
          FILE *             const ofP,
          struct CmdlineInfo const cmdline) {
-    
+
     struct pam inpam, outpam;
     float xscale, yscale;
 
@@ -2170,44 +2221,31 @@ pamscale(FILE *             const ifP,
         outpam.maxval = inpam.maxval;
     }
 
-    computeOutputDimensions(cmdline, inpam.width, inpam.height, 
+    computeOutputDimensions(cmdline, inpam.width, inpam.height,
                             &outpam.width, &outpam.height);
 
     xscale = (float) outpam.width / inpam.width;
     yscale = (float) outpam.height / inpam.height;
 
     if (cmdline.verbose) {
-        pm_message("Scaling by %f horizontally to %d columns.", 
+        pm_message("Scaling by %f horizontally to %d columns.",
                    xscale, outpam.width);
-        pm_message("Scaling by %f vertically to %d rows.", 
+        pm_message("Scaling by %f vertically to %d rows.",
                    yscale, outpam.height);
     }
 
     if (xscale * inpam.width < outpam.width - 1 ||
-        yscale * inpam.height < outpam.height - 1) 
+        yscale * inpam.height < outpam.height - 1)
         pm_error("Arithmetic precision of this program is inadequate to "
                  "do the specified scaling.  Use a smaller input image "
                  "or a slightly different scale factor.");
 
-    pnm_writepaminit(&outpam);
-
-    if (cmdline.nomix) {
-        if (cmdline.verbose)
-            pm_message("Using nomix method");
-        scaleWithoutMixing(&inpam, &outpam, xscale, yscale);
-    } else if (!cmdline.filterFunction) {
-        if (cmdline.verbose)
-            pm_message("Using simple pixel mixing rescaling method");
-        scaleWithMixing(&inpam, &outpam, xscale, yscale, 
-                        cmdline.linear, cmdline.verbose);
-    } else {
-        if (cmdline.verbose)
-            pm_message("Using general filter method");
-        resample(&inpam, &outpam,
-                 cmdline.filterFunction, cmdline.filterRadius,
-                 cmdline.windowFunction, cmdline.verbose,
-                 cmdline.linear);
-    }
+    if (cmdline.reportonly) {
+        printf("%d %d %f %f %d %d\n", inpam.width, inpam.height,
+               xscale, yscale, outpam.width, outpam.height);
+        skipImage(&inpam);
+    } else
+        scale(ifP, &inpam, &outpam, xscale, yscale, cmdline);
 }
 
 
@@ -2225,7 +2263,7 @@ main(int argc, const char **argv ) {
 
     ifP = pm_openr(cmdline.inputFileName);
 
-    eof = FALSE;
+    eof = false;
     while (!eof) {
         pamscale(ifP, stdout, cmdline);
         pnm_nextimage(ifP, &eof);
@@ -2233,6 +2271,6 @@ main(int argc, const char **argv ) {
 
     pm_close(ifP);
     pm_close(stdout);
-    
+
     return 0;
 }
diff --git a/editor/pamstretch-gen b/editor/pamstretch-gen
index ba0e8188..fec4469c 100755
--- a/editor/pamstretch-gen
+++ b/editor/pamstretch-gen
@@ -1,80 +1,129 @@
 #!/bin/sh
+###############################################################################
+#                              pamstretch-gen
+###############################################################################
+# A generalized version of pamstretch that can do non-integer scale factors.
 #
-# pamstretch-gen - a shell script which acts a little like a general
-# form of pamstretch, by scaling up with pamstretch then scaling
-# down with pamscale.
+# It works by scaling up with pamstretch then scaling down with pamscale.
 #
 # it also copes with N<1, but then it just uses pamscale. :-)
 #
 # Formerly named 'pnminterp-gen' and 'pnmstretch-gen'.
 #
-# Copyright (C) 1998,2000 Russell Marks.
-# 
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or (at
-# your option) any later version.
-# 
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-# 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
-# 
+###############################################################################
 
+# Scan command line arguments
 
-if [ "$1" = "" ]; then
-  echo 'usage: pamstretch-gen N [pnmfile]'
+while true ; do
+    case "$1" in
+        -version|--version )
+        pamstretch --version; exit $?;
+        ;;
+        -p|-pl|-pla|-plai|-plain|--p|--pl|--pla|--plai|--plain )
+        plainopt="-plain"
+        shift
+        ;;
+        -q|-qu|-qui|-quie|-quiet|--q|--qu|--qui|--quie|--quiet )
+        quietopt="-plain"
+        shift
+        ;;
+        -q|-qu|-qui|-quie|-quiet|--q|--qu|--qui|--quie|--quiet )
+        quietopt="-quiet"
+        shift
+        ;;
+        -verb|-verbo|-verbos|-verbose|--verb|--verbo|--verbos|--verbose )
+        verboseopt="-verbose"
+        shift
+        ;;
+        -* )
+        echo 'usage: pamstretch-gen N [pnmfile]' 1>&2
+        exit 1
+        ;;
+        * )
+        break
+        ;;
+    esac
+done
+ 
+tempfile=$(mktemp "${TMPDIR:-/tmp}/netpbm.XXXXXXXX")
+if [ $? -ne 0 -o ! -e $tempfile ]; then
+  echo "Could not create temporary file. Exiting." 1>&2
   exit 1
 fi
+trap 'rm -rf $tempfile' 0 1 3 15
 
-tempdir="${TMPDIR-/tmp}/pamstretch-gen.$$"
-mkdir -m 0700 $tempdir || \
-  { echo "Could not create temporary file. Exiting."; exit 1;}
-trap 'rm -rf $tempdir' 0 1 3 15
+case "$#" in
+    0)
+    echo "pamstretch-gen: too few arguments" 1>&2
+    exit 1
+    ;;   
+    1 )
+    if ! cat > $tempfile; then
+    echo "pamstretch-gen: error reading input" 1>&2
+    exit 1
+    fi
+    ;;
+    2 )
+    if ! cat $2 > $tempfile; then
+    echo "pamstretch-gen: error reading file "$2 1>&2
+    exit 1
+    fi
+    ;;
+    * )
+    echo "pamstretch-gen: misaligned arguments or too many arguments" 1>&2
+    exit 1
+    ;;
+esac
 
-tempfile=$tempdir/pnmig
+# Calculate pamstretch scale factor (="iscale") and output width and
+# height.  Usually "int(scale) + 1" is sufficient for iscale but
+# in some exceptional cases adjustment is necessary because of
+# "-dropedge".
 
-if ! cat $2 >$tempfile 2>/dev/null; then
-  echo 'pamstretch-gen: error reading file' 1>&2
+report=$(pamscale -reportonly $1 $tempfile)
+if [ $? -ne 0 ]; then
+  echo "pamstretch-gen: pamscale -reportonly $1 (file) failed" 1>&2
   exit 1
 fi
 
-if ! pnmfile $tempfile 1>/dev/null 2>/dev/null; then
-  echo 'Not valid pnm input'
-  exit 1
-fi
+iscale_width_height=$(echo $report |\
+  awk 'NF!=6 || $1<=0 || $2<=0 || $3<=0 || $5<=0 || $6<=0  { exit 1 }
+           { if ($3 > 1.0)  { iscale = int($3) + 1;
+                              if (iscale * ($1-1) < $5 ||
+                                  iscale * ($2-1) < $6 )
+                                     ++iscale;            }
+             else { iscale = 1 }  # $3 <= 1.0
+       }
+       { print iscale, "-width="$5, "-height="$6}' )
 
-# we use the width as indication of how much to scale; width and
-# height are being scaled equally, so this should be ok.
-width=`pnmfile $tempfile 2>/dev/null|cut -d " " -f 3`
+# Note that $1, $2, ..., $6 here are fields of the input line fed to awk,
+# not shell positional parameters.
 
-if [ "$width" = "" ]; then
-  echo 'pamstretch-gen: not a PNM file' 1>&2
-  exit 1
-fi
+iscale=${iscale_width_height% -width=* -height=*}
+width_height=${iscale_width_height#* }
+if [ -n "$verboseopt" ]; then
+    echo "pamstretch-gen: rounded scale factor=$iscale $width_height" 1>&2
+fi 
 
-# should really use dc for maths, but awk is less painful :-)
-target_width=`awk 'BEGIN{printf("%d",'0.5+"$width"*"$1"')}'`
+pamstretch -dropedge $quietopt $iscale $tempfile |\
+  pamscale $verboseopt $quietopt $plainopt $width_height
 
-# work out how far we have to scale it up with pamstretch so that the
-# new width is >= the target width.
-int_scale=`awk '
-BEGIN {
-int_scale=1;int_width='"$width"'
-while(int_width<'"$target_width"')
-  {
-  int_scale++
-  int_width+='"$width"'
-  }
-print int_scale
-}'`
 
-if [ "$int_scale" -eq 1 ]; then
-  pamscale "$1" $tempfile
-else
-  pamstretch "$int_scale" $tempfile | pnmscale -xsi "$target_width"
-fi
+# Copyright (C) 1998,2000 Russell Marks.
+# Modifications for "pamscale -reportonly" "pamstretch -dropedge" by
+# Akira Urushibata (Jan. 2019)
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or (at
+# your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
diff --git a/editor/pamstretch.c b/editor/pamstretch.c
index 04883c35..5d24e437 100644
--- a/editor/pamstretch.c
+++ b/editor/pamstretch.c
@@ -1,5 +1,5 @@
 /* pamstretch - scale up portable anymap by interpolating between pixels.
- * 
+ *
  * This program is based on 'pnminterp' by Russell Marks, rename
  * pnmstretch for inclusion in Netpbm, then rewritten and renamed to
  * pamstretch by Bryan Henderson in December 2001.
@@ -10,12 +10,12 @@
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or (at
  * your option) any later version.
- * 
+ *
  * This program is distributed in the hope that it will be useful, but
  * WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * General Public License for more details.
- * 
+ *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
  * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
@@ -27,33 +27,35 @@
 #include <limits.h>
 
 #include "pm_c_util.h"
-#include "pam.h"
+#include "mallocvar.h"
+#include "nstring.h"
 #include "shhopt.h"
+#include "pam.h"
 
-enum an_edge_mode {
+enum EdgeMode {
     EDGE_DROP,
         /* drop one (source) pixel at right/bottom edges. */
     EDGE_INTERP_TO_BLACK,
         /* interpolate right/bottom edge pixels to black. */
     EDGE_NON_INTERP
-        /* don't interpolate right/bottom edge pixels 
+        /* don't interpolate right/bottom edge pixels
            (default, and what zgv does). */
 };
 
 
-struct cmdline_info {
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
-    const char *input_filespec;  /* Filespecs of input files */
-    enum an_edge_mode edge_mode;
+    const char * inputFileName;  /* Filespecs of input files */
+    enum EdgeMode edgeMode;
     unsigned int xscale;
     unsigned int yscale;
 };
 
 
 
-tuple blackTuple;  
+tuple blackTuple;
    /* A "black" tuple.  Unless our input image is PBM, PGM, or PPM, we
       don't really know what "black" means, so this is just something
       arbitrary in that case.
@@ -61,116 +63,125 @@ tuple blackTuple;
 
 
 static void
-parse_command_line(int argc, char ** argv,
-                   struct cmdline_info *cmdline_p) {
+parseCommandLine(int argc, const char ** argv,
+                 struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
-   Note that the file spec array we return is stored in the storage that
+   Note that the file name array we return is stored in the storage that
    was passed to us as the argv array.
 -----------------------------------------------------------------------------*/
     optStruct3 opt;  /* set by OPTENT3 */
-    optEntry *option_def = malloc(100*sizeof(optEntry));
+    optEntry * option_def;
     unsigned int option_def_index;
 
     unsigned int blackedge;
     unsigned int dropedge;
-    unsigned int xscale_spec;
-    unsigned int yscale_spec;
+    unsigned int xscaleSpec;
+    unsigned int yscaleSpec;
+
+    MALLOCARRAY(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENTRY */
     OPTENT3('b', "blackedge",    OPT_FLAG, NULL, &blackedge,            0);
     OPTENT3('d', "dropedge",     OPT_FLAG, NULL, &dropedge,             0);
-    OPTENT3(0,   "xscale",       OPT_UINT, 
-            &cmdline_p->xscale, &xscale_spec, 0);
-    OPTENT3(0,   "yscale",       OPT_UINT, 
-            &cmdline_p->yscale, &yscale_spec, 0);
+    OPTENT3(0,   "xscale",       OPT_UINT,
+            &cmdlineP->xscale, &xscaleSpec, 0);
+    OPTENT3(0,   "yscale",       OPT_UINT,
+            &cmdlineP->yscale, &yscaleSpec, 0);
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE; /* We have some short (old-fashioned) options */
     opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
 
-    pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
-        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
-    if (blackedge && dropedge) 
+    if (blackedge && dropedge)
         pm_error("Can't specify both -blackedge and -dropedge options.");
     else if (blackedge)
-        cmdline_p->edge_mode = EDGE_INTERP_TO_BLACK;
+        cmdlineP->edgeMode = EDGE_INTERP_TO_BLACK;
     else if (dropedge)
-        cmdline_p->edge_mode = EDGE_DROP;
+        cmdlineP->edgeMode = EDGE_DROP;
     else
-        cmdline_p->edge_mode = EDGE_NON_INTERP;
+        cmdlineP->edgeMode = EDGE_NON_INTERP;
 
-    if (xscale_spec && cmdline_p->xscale == 0)
+    if (xscaleSpec && cmdlineP->xscale == 0)
         pm_error("You specified zero for the X scale factor.");
-    if (yscale_spec && cmdline_p->yscale == 0)
+    if (yscaleSpec && cmdlineP->yscale == 0)
         pm_error("You specified zero for the Y scale factor.");
 
-    if (xscale_spec && !yscale_spec)
-        cmdline_p->yscale = 1;
-    if (yscale_spec && !xscale_spec)
-        cmdline_p->xscale = 1;
+    if (xscaleSpec && !yscaleSpec)
+        cmdlineP->yscale = 1;
+    if (yscaleSpec && !xscaleSpec)
+        cmdlineP->xscale = 1;
 
-    if (!(xscale_spec || yscale_spec)) {
+    if (!(xscaleSpec || yscaleSpec)) {
         /* scale must be specified in an argument */
-        if ((argc-1) != 1 && (argc-1) != 2)
+        if (argc-1 != 1 && argc-1 != 2)
             pm_error("Wrong number of arguments (%d).  Without scale options, "
                      "you must supply 1 or 2 arguments:  scale and "
                      "optional file specification", argc-1);
-        
+
         {
-            char *endptr;   /* ptr to 1st invalid character in scale arg */
+            const char * error;   /* error message of pm_string_to_uint */
             unsigned int scale;
-            
-            scale = strtol(argv[1], &endptr, 10);
-            if (*argv[1] == '\0') 
-                pm_error("Scale argument is a null string.  "
-                         "Must be a number.");
-            else if (*endptr != '\0')
-                pm_error("Scale argument contains non-numeric character '%c'.",
-                         *endptr);
-            else if (scale < 2)
-                pm_error("Scale argument must be at least 2.  "
-                         "You specified %d", scale);
-            cmdline_p->xscale = scale;
-            cmdline_p->yscale = scale;
+
+            pm_string_to_uint(argv[1], &scale, &error);
+
+            if (error)
+                pm_error("Invalid scale factor: %s", error);
+            else {
+                if (scale < 1)
+                    pm_error("Scale argument must be at least 1.  "
+                             "You specified %d", scale);
+                cmdlineP->xscale = scale;
+                cmdlineP->yscale = scale;
+            }
         }
-        if (argc-1 > 1) 
-            cmdline_p->input_filespec = argv[2];
+
+        if (argc-1 > 1)
+            cmdlineP->inputFileName = argv[2];
         else
-            cmdline_p->input_filespec = "-";
+            cmdlineP->inputFileName = "-";
     } else {
         /* No scale argument allowed */
-        if ((argc-1) > 1)
+        if (argc-1 > 1)
             pm_error("Too many arguments (%d).  With a scale option, "
                      "the only argument is the "
                      "optional file specification", argc-1);
-        if (argc-1 > 0) 
-            cmdline_p->input_filespec = argv[1];
-        else
-            cmdline_p->input_filespec = "-";
+        else {
+            if (argc-1 > 0)
+                cmdlineP->inputFileName = argv[1];
+            else
+                cmdlineP->inputFileName = "-";
+        }
     }
 }
 
 
 
 static void
-stretch_line(struct pam * const inpamP, 
-             const tuple * const line, const tuple * const line_stretched, 
-             unsigned int const scale, enum an_edge_mode const edge_mode) {
+stretchLine(struct pam *  const inpamP,
+            const tuple * const line,
+            const tuple * const lineStretched,
+            unsigned int  const scale,
+            enum EdgeMode const edgeMode) {
 /*----------------------------------------------------------------------------
    Stretch the line of tuples 'line' into the output buffer 'line_stretched',
    by factor 'scale'.
 -----------------------------------------------------------------------------*/
+    enum EdgeMode const horizontalEdgeMode =
+        (scale == 1) ? EDGE_NON_INTERP : edgeMode;
+
     int scaleincr;
-    int sisize;   
+    int sisize;
         /* normalizing factor to make fractions representable as integers.
            E.g. if sisize = 100, one half is represented as 50.
         */
     unsigned int col;
     unsigned int outcol;
-    
+
     sisize=0;
-    while (sisize<256) 
+    while (sisize<256)
         sisize += scale;
     scaleincr = sisize/scale;  /* (1/scale, normalized) */
 
@@ -185,7 +196,7 @@ stretch_line(struct pam * const inpamP,
             /* We're at the edge.  There is no column to the right with which
                to interpolate.
             */
-            switch(edge_mode) {
+            switch(horizontalEdgeMode) {
             case EDGE_DROP:
                 /* No output column needed for this input column */
                 break;
@@ -194,32 +205,30 @@ stretch_line(struct pam * const inpamP,
                 for (pos = 0; pos < sisize; pos += scaleincr) {
                     unsigned int plane;
                     for (plane = 0; plane < inpamP->depth; ++plane)
-                        line_stretched[outcol][plane] = 
+                        lineStretched[outcol][plane] =
                             (line[col][plane] * (sisize-pos)) / sisize;
                     ++outcol;
                 }
-            }
-            break;
+            } break;
             case EDGE_NON_INTERP: {
                 unsigned int pos;
                 for (pos = 0; pos < sisize; pos += scaleincr) {
                     unsigned int plane;
                     for (plane = 0; plane < inpamP->depth; ++plane)
-                        line_stretched[outcol][plane] = line[col][plane];
+                        lineStretched[outcol][plane] = line[col][plane];
                     ++outcol;
                 }
-            }
-            break;
-            default: 
-                pm_error("INTERNAL ERROR: invalid value for edge_mode");
+            } break;
+            default:
+                pm_error("INTERNAL ERROR: invalid value for edgeMode");
             }
         } else {
             /* Interpolate with the next input column to the right */
             for (pos = 0; pos < sisize; pos += scaleincr) {
                 unsigned int plane;
                 for (plane = 0; plane < inpamP->depth; ++plane)
-                    line_stretched[outcol][plane] = 
-                        (line[col][plane] * (sisize-pos) 
+                    lineStretched[outcol][plane] =
+                        (line[col][plane] * (sisize-pos)
                          +  line[col+1][plane] * pos) / sisize;
                 ++outcol;
             }
@@ -229,31 +238,33 @@ stretch_line(struct pam * const inpamP,
 
 
 
-static void 
-write_interp_rows(struct pam *      const outpamP,
-                  const tuple *     const curline,
-                  const tuple *     const nextline, 
-                  tuple *           const outbuf,
-                  int               const scale) {
+static void
+writeInterpRows(struct pam *      const outpamP,
+                const tuple *     const curline,
+                const tuple *     const nextline,
+                tuple *           const outbuf,
+                int               const scale) {
 /*----------------------------------------------------------------------------
-   Write out 'scale' rows, being 'curline' followed by rows that are 
+   Write out 'scale' rows, being 'curline' followed by rows that are
    interpolated between 'curline' and 'nextline'.
 -----------------------------------------------------------------------------*/
-    unsigned int scaleincr;
-    unsigned int sisize;
+    unsigned int scaleIncr;
+    unsigned int siSize;
     unsigned int pos;
 
-    sisize=0;
-    while(sisize<256) sisize+=scale;
-    scaleincr=sisize/scale;
+    for (siSize = 0; siSize < 256; siSize += scale);
+
+    scaleIncr = siSize / scale;
 
-    for (pos = 0; pos < sisize; pos += scaleincr) {
+    for (pos = 0; pos < siSize; pos += scaleIncr) {
         unsigned int col;
+
         for (col = 0; col < outpamP->width; ++col) {
             unsigned int plane;
-            for (plane = 0; plane < outpamP->depth; ++plane) 
-                outbuf[col][plane] = (curline[col][plane] * (sisize-pos)
-                    + nextline[col][plane] * pos) / sisize;
+
+            for (plane = 0; plane < outpamP->depth; ++plane)
+                outbuf[col][plane] = (curline[col][plane] * (siSize - pos)
+                    + nextline[col][plane] * pos) / siSize;
         }
         pnm_writepamrow(outpamP, outbuf);
     }
@@ -262,10 +273,11 @@ write_interp_rows(struct pam *      const outpamP,
 
 
 static void
-swap_buffers(tuple ** const buffer1P, tuple ** const buffer2P) {
-    /* Advance "next" line to "current" line by switching
-       line buffers 
-    */
+swapBuffers(tuple ** const buffer1P,
+            tuple ** const buffer2P) {
+/*----------------------------------------------------------------------------
+  Advance "next" line to "current" line by switching line buffers.
+-----------------------------------------------------------------------------*/
     tuple *tmp;
 
     tmp = *buffer1P;
@@ -274,110 +286,142 @@ swap_buffers(tuple ** const buffer1P, tuple ** const buffer2P) {
 }
 
 
-static void 
-stretch(struct pam * const inpamP, struct pam * const outpamP,
-        int const xscale, int const yscale,
-        enum an_edge_mode const edge_mode) {
+
+static void
+stretch(struct pam *  const inpamP,
+        struct pam *  const outpamP,
+        unsigned int  const xscale,
+        unsigned int  const yscale,
+        enum EdgeMode const edgeMode) {
+
+    enum EdgeMode const verticalEdgeMode =
+        (yscale == 1) ? EDGE_NON_INTERP : edgeMode;
 
     tuple *linebuf1, *linebuf2;  /* Input buffers for two rows at a time */
     tuple *curline, *nextline;   /* Pointers to one of the two above buffers */
     /* And the stretched versions: */
-    tuple *stretched_linebuf1, *stretched_linebuf2;
-    tuple *curline_stretched, *nextline_stretched;
+    tuple *stretchedLinebuf1, *stretchedLinebuf2;
+    tuple *curlineStretched, *nextlineStretched;
 
     tuple *outbuf;   /* One-row output buffer */
     unsigned int row;
-    unsigned int rowsToStretch;
-    
-    linebuf1 =           pnm_allocpamrow(inpamP);
-    linebuf2 =           pnm_allocpamrow(inpamP);
-    stretched_linebuf1 = pnm_allocpamrow(outpamP);
-    stretched_linebuf2 = pnm_allocpamrow(outpamP);
-    outbuf =             pnm_allocpamrow(outpamP);
+    unsigned int nRowsToStretch;
+
+    linebuf1          = pnm_allocpamrow(inpamP);
+    linebuf2          = pnm_allocpamrow(inpamP);
+    stretchedLinebuf1 = pnm_allocpamrow(outpamP);
+    stretchedLinebuf2 = pnm_allocpamrow(outpamP);
+    outbuf            = pnm_allocpamrow(outpamP);
 
     curline = linebuf1;
-    curline_stretched = stretched_linebuf1;
+    curlineStretched = stretchedLinebuf1;
     nextline = linebuf2;
-    nextline_stretched = stretched_linebuf2;
+    nextlineStretched = stretchedLinebuf2;
 
     pnm_readpamrow(inpamP, curline);
-    stretch_line(inpamP, curline, curline_stretched, xscale, edge_mode);
+    stretchLine(inpamP, curline, curlineStretched, xscale, edgeMode);
 
-    if (edge_mode == EDGE_DROP) 
-        rowsToStretch = inpamP->height - 1;
+    if (verticalEdgeMode == EDGE_DROP)
+        nRowsToStretch = inpamP->height - 1;
     else
-        rowsToStretch = inpamP->height;
-    
-    for (row = 0; row < rowsToStretch; row++) {
-        if (row == inpamP->height-1) {
+        nRowsToStretch = inpamP->height;
+
+    for (row = 0; row < nRowsToStretch; ++row) {
+        if (row == inpamP->height - 1) {
             /* last line is about to be output. there is no further
              * `next line'.  if EDGE_DROP, we stop here, with output
              * of rows-1 rows.  if EDGE_INTERP_TO_BLACK we make next
              * line black.  if EDGE_NON_INTERP (default) we make it a
-             * copy of the current line.  
+             * copy of the current line.
              */
-            switch (edge_mode) {
+            switch (verticalEdgeMode) {
             case EDGE_INTERP_TO_BLACK: {
-                int col;
-                for (col = 0; col < outpamP->width; col++)
-                    nextline_stretched[col] = blackTuple;
-            } 
+                unsigned int col;
+                for (col = 0; col < outpamP->width; ++col)
+                    nextlineStretched[col] = blackTuple;
+            }
             break;
             case EDGE_NON_INTERP: {
                 /* EDGE_NON_INTERP */
-                int col;
-                for (col = 0; col < outpamP->width; col++)
-                    nextline_stretched[col] = curline_stretched[col];
+                unsigned int col;
+                for (col = 0; col < outpamP->width; ++col)
+                    nextlineStretched[col] = curlineStretched[col];
             }
             break;
-            case EDGE_DROP: 
+            case EDGE_DROP:
                 pm_error("INTERNAL ERROR: processing last row, but "
-                         "edge_mode is EDGE_DROP.");
+                         "edgeMode is EDGE_DROP.");
             }
         } else {
             pnm_readpamrow(inpamP, nextline);
-            stretch_line(inpamP, nextline, nextline_stretched, xscale,
-                         edge_mode);
+            stretchLine(inpamP, nextline, nextlineStretched, xscale, edgeMode);
         }
-        
+
         /* interpolate curline towards nextline into outbuf */
-        write_interp_rows(outpamP, curline_stretched, nextline_stretched,
-                          outbuf, yscale);
+        writeInterpRows(outpamP, curlineStretched, nextlineStretched,
+                        outbuf, yscale);
 
-        swap_buffers(&curline, &nextline);
-        swap_buffers(&curline_stretched, &nextline_stretched);
+        swapBuffers(&curline, &nextline);
+        swapBuffers(&curlineStretched, &nextlineStretched);
     }
     pnm_freerow(outbuf);
-    pnm_freerow(stretched_linebuf2);
-    pnm_freerow(stretched_linebuf1);
+    pnm_freerow(stretchedLinebuf2);
+    pnm_freerow(stretchedLinebuf1);
     pnm_freerow(linebuf2);
     pnm_freerow(linebuf1);
 }
 
 
+static void
+computeOutputWidthHeight(int           const inWidth,
+                         int           const inHeight,
+                         unsigned int  const xScale,
+                         unsigned int  const yScale,
+                         enum EdgeMode const edgeMode,
+                         int *         const outWidthP,
+                         int *         const outHeightP) {
+
+    unsigned int const xDropped =
+        (edgeMode == EDGE_DROP && xScale != 1) ? 1 : 0;
+    unsigned int const yDropped =
+        (edgeMode == EDGE_DROP && yScale != 1) ? 1 : 0;
+    double const width  = (inWidth  - xDropped) * xScale;
+    double const height = (inHeight - yDropped) * yScale;
+
+    if (width > INT_MAX - 2)
+        pm_error("output image width (%f) too large for computations",
+                 width);
+    if (height > INT_MAX - 2)
+        pm_error("output image height (%f) too large for computation",
+                 height);
+
+    *outWidthP  = (unsigned int)width;
+    *outHeightP = (unsigned int)height;
+}
+
+
 
-int 
-main(int argc,char *argv[]) {
+int
+main(int argc, const char ** argv) {
 
-    FILE *ifp;
+    FILE * ifP;
 
-    struct cmdline_info cmdline; 
+    struct CmdlineInfo cmdline;
     struct pam inpam, outpam;
-    
-    pnm_init(&argc, argv);
 
-    parse_command_line(argc, argv, &cmdline);
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
 
-    ifp = pm_openr(cmdline.input_filespec);
+    ifP = pm_openr(cmdline.inputFileName);
 
-    pnm_readpaminit(ifp, &inpam, PAM_STRUCT_SIZE(tuple_type));
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
 
     if (inpam.width < 2)
         pm_error("Image is too narrow.  Must be at least 2 columns.");
     if (inpam.height < 2)
         pm_error("Image is too short.  Must be at least 2 lines.");
 
-
     outpam = inpam;  /* initial value */
     outpam.file = stdout;
 
@@ -388,30 +432,18 @@ main(int argc,char *argv[]) {
     } else {
         outpam.format = inpam.format;
     }
-    {
-        unsigned int const dropped = cmdline.edge_mode == EDGE_DROP ? 1 : 0;
-        double const width  = (inpam.width  - dropped) * cmdline.xscale;
-        double const height = (inpam.height - dropped) * cmdline.yscale;
-
-        if (width > INT_MAX - 2)
-            pm_error("output image width (%f) too large for computations",
-                     width);
-        if (height > INT_MAX - 2)
-            pm_error("output image height (%f) too large for computation",
-                     height);
- 
-        outpam.width  = width;
-        outpam.height = height;
-
-        pnm_writepaminit(&outpam);
-    }
+    computeOutputWidthHeight(inpam.width, inpam.height,
+                             cmdline.xscale, cmdline.yscale, cmdline.edgeMode,
+                             &outpam.width, &outpam.height);
+
+    pnm_writepaminit(&outpam);
 
     pnm_createBlackTuple(&outpam, &blackTuple);
 
-    stretch(&inpam, &outpam, 
-            cmdline.xscale, cmdline.yscale, cmdline.edge_mode);
+    stretch(&inpam, &outpam,
+            cmdline.xscale, cmdline.yscale, cmdline.edgeMode);
 
-    pm_close(ifp);
+    pm_close(ifP);
 
     exit(0);
 }
diff --git a/editor/pamundice.c b/editor/pamundice.c
index 9a80e46d..dbe0a8df 100644
--- a/editor/pamundice.c
+++ b/editor/pamundice.c
@@ -271,12 +271,11 @@ computeInputFileName(const char *  const pattern,
                      const char ** const fileNameP) {
 
     struct buffer buffer;
-    unsigned int inCursor, outCursor;
+    unsigned int inCursor;
 
     buffer_init(&buffer);
 
     inCursor = 0;
-    outCursor = 0;
 
     while (pattern[inCursor] != '\0') {
         if (pattern[inCursor] == '%') {
diff --git a/editor/pbmclean.c b/editor/pbmclean.c
index 46e7dee6..08f410c0 100644
--- a/editor/pbmclean.c
+++ b/editor/pbmclean.c
@@ -6,13 +6,14 @@
 =============================================================================*/
 #include <assert.h>
 #include <stdio.h>
+#include <stdbool.h>
 
 #include "pm_c_util.h"
 #include "mallocvar.h"
 #include "shhopt.h"
 #include "pbm.h"
 
-struct cmdlineInfo {
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
@@ -28,7 +29,7 @@ struct cmdlineInfo {
 
 static void
 parseCommandLine(int argc, const char ** argv,
-                 struct cmdlineInfo *cmdlineP) {
+                 struct CmdlineInfo *cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that the file spec array we return is stored in the storage that
    was passed to us as the argv array.
@@ -46,13 +47,13 @@ parseCommandLine(int argc, const char ** argv,
     OPTENT3(0,   "verbose",          OPT_FLAG, NULL, &cmdlineP->verbose, 0);
     OPTENT3(0,   "black",            OPT_FLAG, NULL, &black, 0);
     OPTENT3(0,   "white",            OPT_FLAG, NULL, &white, 0);
-    OPTENT3(0,   "minneighbors",     OPT_UINT, &cmdlineP->minneighbors, 
+    OPTENT3(0,   "minneighbors",     OPT_UINT, &cmdlineP->minneighbors,
             &minneighborsSpec, 0);
     OPTENT3(0,   "extended",         OPT_FLAG, NULL, &cmdlineP->extended, 0);
 
     opt.opt_table = option_def;
-    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
-    opt.allowNegNum = TRUE;  /* We sort of allow negative numbers as parms */
+    opt.short_allowed = false;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = true;  /* We sort of allow negative numbers as parms */
 
     pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
@@ -64,8 +65,8 @@ parseCommandLine(int argc, const char ** argv,
             pm_error("With -extended, you cannot specify both "
                      "-black and -white");
         else if (!black & !white) {
-            cmdlineP->flipBlack = TRUE;
-            cmdlineP->flipWhite = FALSE;
+            cmdlineP->flipBlack = true;
+            cmdlineP->flipWhite = false;
         } else {
             cmdlineP->flipBlack = !!black;
             cmdlineP->flipWhite = !!white;
@@ -77,7 +78,7 @@ parseCommandLine(int argc, const char ** argv,
         } else {
             cmdlineP->flipBlack = !!black;
             cmdlineP->flipWhite = !!white;
-        }    
+        }
     }
     if (!minneighborsSpec) {
         /* Now we do a sleazy tour through the parameters to see if
@@ -86,7 +87,7 @@ parseCommandLine(int argc, const char ** argv,
            unconventional syntax where a -N option was used instead of
            the current -minneighbors option.  The only reason -N didn't
            get processed by pm_pm_optParseOptions3() is that it looked
-           like a negative number parameter instead of an option.  
+           like a negative number parameter instead of an option.
            If we find a -N, we make like it was a -minneighbors=N option.
         */
         int i;
@@ -109,7 +110,7 @@ parseCommandLine(int argc, const char ** argv,
             --argc;
     }
 
-    if (argc-1 < 1) 
+    if (argc-1 < 1)
         cmdlineP->inputFileName = "-";
     else if (argc-1 == 1)
         cmdlineP->inputFileName = argv[1];
@@ -160,7 +161,7 @@ bitpop24(uint32_t const w){
 -----------------------------------------------------------------------------*/
     return (bitpop8((w >> 16) & 0xff) +
             bitpop8((w >>  8) & 0xff) +
-            bitpop8((w >>  0) & 0xff));  
+            bitpop8((w >>  0) & 0xff));
 }
 
 
@@ -204,11 +205,11 @@ and written to outrow at the byte boundary.
 
 
 static unsigned int
-likeNeighbors(uint32_t     const blackSample, 
+likeNeighbors(uint32_t     const blackSample,
               unsigned int const offset) {
 
     bool const thispoint = ( blackSample >> (18-offset) ) & 0x01;
-    uint32_t const sample = (thispoint == PBM_BLACK ) 
+    uint32_t const sample = (thispoint == PBM_BLACK )
                           ?   blackSample
                           : ~ blackSample ;
     uint32_t const selection = 0x701407;
@@ -238,7 +239,7 @@ setSample(const bit *  const prevrow,
         ((nextrow[col8 - 1] & 0x01)  <<  9) |
         ((nextrow[col8]           )  <<  1) |
         ((nextrow[col8 + 1] & 0x80)  >>  7);
-    
+
     return sample;
 }
 
@@ -277,7 +278,7 @@ cleanrow(const bit *    const prevrow,
 /* ----------------------------------------------------------------------
   Work through row, scanning for bits that require flipping, and write
   the results to 'outrow'.
-  
+
   Returns the number of bits flipped within this one row as *nFlippedP.
 -------------------------------------------------------------------------*/
     uint32_t sample;
@@ -350,9 +351,9 @@ setupInputBuffers(FILE *       const ifP,
 
     for (i = 0; i < pbm_packed_bytes(cols+16); ++i)
         edgeRow[i] = 0x00;
-        
+
     for (i = 0; i < 3; ++i) {
-        /* Add blank (all white) bytes beside the edges */ 
+        /* Add blank (all white) bytes beside the edges */
         buffer[i][0] = buffer[i][ pbm_packed_bytes( cols +16 ) - 1] = 0x00;
     }
     nextRow = &buffer[0][1];
@@ -374,7 +375,7 @@ setupInputBuffers(FILE *       const ifP,
 static void
 cleanSimple(FILE *             const ifP,
             FILE *             const ofP,
-            struct cmdlineInfo const cmdline,
+            struct CmdlineInfo const cmdline,
             double *           const nFlippedP) {
 /*----------------------------------------------------------------------------
    Do the traditional clean where you look only at the immediate neighboring
@@ -412,7 +413,7 @@ cleanSimple(FILE *             const ifP,
         if (row < rows -1){
             nextRow = &buffer[(row+1)%3][1];
             /* We take the address directly instead of shuffling the rows
-               with the help of a temporary.  This provision is for proper 
+               with the help of a temporary.  This provision is for proper
                handling of the initial edgerow.
             */
             pbm_readpbmrow_packed(ifP, nextRow, cols, format);
@@ -423,7 +424,7 @@ cleanSimple(FILE *             const ifP,
 
         cleanrow(prevRow, thisRow, nextRow, outRow, cols, cmdline.minneighbors,
                  cmdline.flipWhite, cmdline.flipBlack, &nFlipped);
-        
+
         *nFlippedP += nFlipped;
 
         pbm_writepbmrow_packed(ofP, outRow, cols, 0) ;
@@ -446,7 +447,7 @@ typedef struct {
    A queue of pixel locations.
 -----------------------------------------------------------------------------*/
     unsigned int size;
-    
+
     struct PixQueueElt * headP;
     struct PixQueueElt * tailP;
 } PixQueue;
@@ -495,7 +496,7 @@ pixQueue_push(PixQueue *    const queueP,
     newEltP->nextP = NULL;
     if (queueP->tailP)
         queueP->tailP->nextP = newEltP;
-    
+
     queueP->tailP = newEltP;
 
     if (!queueP->headP)
@@ -538,7 +539,7 @@ pixQueue_term(PixQueue * const queueP) {
 
     struct PixQueueElt * p;
     struct PixQueueElt * nextP;
-    
+
     for (p = queueP->headP; p; p = nextP) {
         nextP = p->nextP;
         free(p);
@@ -720,9 +721,9 @@ cleanPixels(bit **       const pixels,
         for (thisPix.col = 0; thisPix.col < cols; ++thisPix.col) {
             if (pixels[thisPix.row][thisPix.col] == foregroundColor
                 && !visited[thisPix.row][thisPix.col]) {
-                
+
                 double nFlipped;
-                
+
                 processBlob(thisPix, pixels, cols, rows, trivialSize,
                             visited, &nFlipped);
 
@@ -772,7 +773,7 @@ cleanExtended(FILE *             const ifP,
 static void
 pbmclean(FILE *             const ifP,
          FILE *             const ofP,
-         struct cmdlineInfo const cmdline,
+         struct CmdlineInfo const cmdline,
          double *           const nFlippedP) {
 
     if (cmdline.extended) {
@@ -791,7 +792,7 @@ pbmclean(FILE *             const ifP,
 int
 main(int argc, const char *argv[]) {
 
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
     FILE * ifP;
     double nFlipped;
         /* Number of pixels we have flipped so far.  Use type double to
diff --git a/editor/pbmmask.c b/editor/pbmmask.c
index 25c71226..0be10435 100644
--- a/editor/pbmmask.c
+++ b/editor/pbmmask.c
@@ -10,13 +10,57 @@
 ** implied warranty.
 */
 
+#include <stdbool.h>
+#include <assert.h>
+
 #include "pbm.h"
+#include "shhopt.h"
 #include "mallocvar.h"
 
-static bit ** bits;
-static bit ** mask;
-static bit backcolor;
-static int rows, cols;
+struct CmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;  /* File name of input file */
+    unsigned int expand;
+};
+
+
+
+static void
+parseCommandLine(int argc, const char ** argv,
+                 struct CmdlineInfo *  const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optStruct3 opt;  /* set by OPTENT3 */
+    optEntry * option_def;
+    unsigned int option_def_index;
+
+    MALLOCARRAY(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "expand",          OPT_FLAG, NULL, &cmdlineP->expand, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = false;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = true;  /* We sort of allow negative numbers as parms */
+
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    free(option_def);
+
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFileName = argv[1];
+    else
+        pm_error("You specified too many arguments (%u).  The only "
+                 "possible argument is the optional input file specification.",
+                 argc-1);
+}
 
 
 
@@ -28,25 +72,78 @@ static int fstackp = 0;
 
 
 static void
-addflood(int const col,
-         int const row) {
+clearMask(bit ** const mask,
+          unsigned int const cols,
+          unsigned int const rows) {
+
+    /* Clear out the mask. */
+    unsigned int row;
 
-    if ( bits[row][col] == backcolor && mask[row][col] == PBM_BLACK ) {
-        if ( fstackp >= fstacksize ) {
-            if ( fstacksize == 0 ) {
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+
+        for (col = 0; col < cols; ++col)
+            mask[row][col] = PBM_BLACK;
+    }
+}
+
+
+
+static bit
+backcolorFmImage(bit **       const bits,
+                 unsigned int const cols,
+                 unsigned int const rows) {
+
+    /* Figure out the background color, by counting along the edge. */
+
+    unsigned int row;
+    unsigned int col;
+    unsigned int wcount;
+
+    assert(cols > 0); assert(rows > 0);
+
+    wcount = 0;
+    for (row = 0; row < rows; ++row) {
+        if (bits[row][0] == PBM_WHITE)
+            ++wcount;
+        if (bits[row][cols - 1] == PBM_WHITE)
+            ++wcount;
+    }
+    for (col = 1; col < cols - 1; ++col) {
+        if (bits[0][col] == PBM_WHITE)
+            ++wcount;
+        if (bits[rows - 1][col] == PBM_WHITE)
+            ++wcount;
+    }
+
+    return (wcount >= rows + cols - 2) ? PBM_WHITE : PBM_BLACK;
+}
+
+
+
+static void
+addflood(bit **       const bits,
+         bit **       const mask,
+         unsigned int const col,
+         unsigned int const row,
+         bit          const backcolor) {
+
+    if (bits[row][col] == backcolor && mask[row][col] == PBM_BLACK) {
+        if (fstackp >= fstacksize) {
+            if (fstacksize == 0) {
                 fstacksize = 1000;
                 MALLOCARRAY(fcols, fstacksize);
                 MALLOCARRAY(frows, fstacksize);
-                if ( fcols == NULL || frows == NULL )
-                    pm_error( "out of memory" );
+                if (fcols == NULL || frows == NULL)
+                    pm_error("out of memory");
             } else {
                 fstacksize *= 2;
                 fcols = (short*) realloc(
-                    (char*) fcols, fstacksize * sizeof(short) );
+                    (char*) fcols, fstacksize * sizeof(short));
                 frows = (short*) realloc(
-                    (char*) frows, fstacksize * sizeof(short) );
-                if ( fcols == (short*) 0 || frows == (short*) 0 )
-                    pm_error( "out of memory" );
+                    (char*) frows, fstacksize * sizeof(short));
+                if (fcols == (short*) 0 || frows == (short*)0)
+                    pm_error("out of memory");
             }
         }
         fcols[fstackp] = col;
@@ -58,46 +155,81 @@ addflood(int const col,
 
 
 static void
-flood(void) {
+floodEdge(bit **       const bits,
+          unsigned int const cols,
+          unsigned int const rows,
+          bit          const backcolor,
+          bit **       const mask) {
+
+    int col;
+    int row;
+
+    /* Flood the entire edge.  Probably the first call will be enough, but
+       might as well be sure.
+    */
+    assert(cols > 0); assert(rows > 0);
+
+    for (col = cols - 3; col >= 2; col -= 2) {
+        addflood(bits, mask, col, rows - 1, backcolor);
+        addflood(bits, mask, col, 0, backcolor);
+    }
+    for (row = rows - 1; row >= 0; row -= 2) {
+        addflood(bits, mask, cols - 1, row, backcolor);
+        addflood(bits, mask, 0, row, backcolor);
+    }
+}
+
+
+
+static void
+flood(bit **       const bits,
+      unsigned int const cols,
+      unsigned int const rows,
+      bit          const backcolor,
+      bit **       const mask) {
+
+    assert(cols > 0); assert(rows > 0);
 
-    while ( fstackp > 0 ) {
+    floodEdge(bits, cols, rows, backcolor, mask);
+
+    while (fstackp > 0) {
         int col, row;
         --fstackp;
         col = fcols[fstackp];
         row = frows[fstackp];
-        if ( bits[row][col] == backcolor && mask[row][col] == PBM_BLACK ) {
+        if (bits[row][col] == backcolor && mask[row][col] == PBM_BLACK) {
             int c;
             mask[row][col] = PBM_WHITE;
-            if ( row - 1 >= 0 )
-                addflood( col, row - 1 );
-            if ( row + 1 < rows )
-                addflood( col, row + 1 );
-            for ( c = col + 1; c < cols; ++c ) {
-                if ( bits[row][c] == backcolor && mask[row][c] == PBM_BLACK ) {
+            if (row - 1 >= 0)
+                addflood(bits, mask, col, row - 1, backcolor);
+            if (row + 1 < rows)
+                addflood(bits, mask, col, row + 1, backcolor);
+            for (c = col + 1; c < cols; ++c) {
+                if (bits[row][c] == backcolor && mask[row][c] == PBM_BLACK) {
                     mask[row][c] = PBM_WHITE;
-                    if ( row - 1 >= 0 && 
-                         ( bits[row - 1][c - 1] != backcolor || 
-                           mask[row - 1][c - 1] != PBM_BLACK ) )
-                        addflood( c, row - 1 );
-                    if ( row + 1 < rows && 
-                         ( bits[row + 1][c - 1] != backcolor || 
-                           mask[row + 1][c - 1] != PBM_BLACK ) )
-                        addflood( c, row + 1 );
+                    if (row - 1 >= 0 &&
+                        (bits[row - 1][c - 1] != backcolor ||
+                         mask[row - 1][c - 1] != PBM_BLACK))
+                        addflood(bits, mask, c, row - 1, backcolor);
+                    if (row + 1 < rows &&
+                         (bits[row + 1][c - 1] != backcolor ||
+                          mask[row + 1][c - 1] != PBM_BLACK))
+                        addflood(bits, mask, c, row + 1, backcolor);
                 }
                 else
                     break;
             }
-            for ( c = col - 1; c >= 0; --c ) {
-                if ( bits[row][c] == backcolor && mask[row][c] == PBM_BLACK ) {
+            for (c = col - 1; c >= 0; --c) {
+                if (bits[row][c] == backcolor && mask[row][c] == PBM_BLACK) {
                     mask[row][c] = PBM_WHITE;
-                    if ( row - 1 >= 0 && 
-                         ( bits[row - 1][c + 1] != backcolor || 
-                           mask[row - 1][c + 1] != PBM_BLACK ) )
-                        addflood( c, row - 1 );
-                    if ( row + 1 < rows && 
-                         ( bits[row + 1][c + 1] != backcolor || 
-                           mask[row + 1][c + 1] != PBM_BLACK ) )
-                        addflood( c, row + 1 );
+                    if (row - 1 >= 0 &&
+                        (bits[row - 1][c + 1] != backcolor ||
+                         mask[row - 1][c + 1] != PBM_BLACK))
+                        addflood(bits, mask, c, row - 1, backcolor);
+                    if (row + 1 < rows &&
+                         (bits[row + 1][c + 1] != backcolor ||
+                          mask[row + 1][c + 1] != PBM_BLACK))
+                        addflood(bits, mask, c, row + 1, backcolor);
                 } else
                     break;
             }
@@ -107,121 +239,103 @@ flood(void) {
 
 
 
-int
-main(int argc, char * argv[]) {
-
-    FILE* ifp;
-    int argn, expand, wcount;
-    register int row, col;
-    const char* const usage = "[-expand] [pbmfile]";
-
-    pbm_init( &argc, argv );
-
-    argn = 1;
-    expand = 0;
-
-    if ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
-    {
-        if ( pm_keymatch( argv[argn], "-expand", 2 ) )
-            expand = 1;
-        else if ( pm_keymatch( argv[argn], "-noexpand", 2 ) )
-            expand = 0;
-        else
-            pm_usage( usage );
-        ++argn;
-    }
+static bit **
+expandedByOnePixel(bit **       const mask,
+                   unsigned int const cols,
+                   unsigned int const rows) {
 
-    if ( argn == argc )
-        ifp = stdin;
-    else
-    {
-        ifp = pm_openr( argv[argn] );
-        ++argn;
+    /* Expand by one pixel. */
+
+    bit ** const emask = pbm_allocarray(cols, rows);
+
+    unsigned int row;
+
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        for (col = 0; col < cols; ++col)
+            if (mask[row][col] == PBM_BLACK)
+                emask[row][col] = PBM_BLACK;
+            else {
+                unsigned int srow;
+
+                emask[row][col] = PBM_WHITE;
+
+                for (srow = row - 1; srow <= row + 1; ++srow) {
+                    unsigned int scol;
+
+                    for (scol = col - 1; scol <= col + 1; ++scol) {
+                        if (srow >= 0 && srow < rows &&
+                            scol >= 0 && scol < cols &&
+                            mask[srow][scol] == PBM_BLACK) {
+
+                            emask[row][col] = PBM_BLACK;
+                            break;
+                        }
+                    }
+                }
+            }
     }
+    return emask;
+}
+
+
 
-    if ( argn != argc )
-        pm_usage( usage );
+static void
+pbmmask(FILE *             const ifP,
+        FILE *             const ofP,
+        struct CmdlineInfo const cmdline) {
+
+    int cols, rows;
+    bit ** mask;
+    bit ** bits;
+    bit backcolor;
 
-    bits = pbm_readpbm( ifp, &cols, &rows );
+    bits = pbm_readpbm(ifP, &cols, &rows);
 
     if (cols == 0 || rows == 0)
         pm_error("Image contains no pixels, so there is no such thing "
                  "as background and foreground");
 
-    pm_close( ifp );
-    mask = pbm_allocarray( cols, rows );
+    mask = pbm_allocarray(cols, rows);
 
-    /* Clear out the mask. */
-    for ( row = 0; row < rows; ++row )
-        for ( col = 0; col < cols; ++col )
-            mask[row][col] = PBM_BLACK;
+    clearMask(mask, cols, rows);
 
-    /* Figure out the background color, by counting along the edge. */
-    wcount = 0;
-    for ( row = 0; row < rows; ++row ) {
-        if ( bits[row][0] == PBM_WHITE )
-            ++wcount;
-        if ( bits[row][cols - 1] == PBM_WHITE )
-            ++wcount;
-    }
-    for ( col = 1; col < cols - 1; ++col ) {
-        if ( bits[0][col] == PBM_WHITE )
-            ++wcount;
-        if ( bits[rows - 1][col] == PBM_WHITE )
-            ++wcount;
-    }
-    if ( wcount >= rows + cols - 2 )
-        backcolor = PBM_WHITE;
-    else
-        backcolor = PBM_BLACK;
+    backcolor = backcolorFmImage(bits, cols, rows);
 
-    /* Flood the entire edge.  Probably the first call will be enough, but
-       might as well be sure.
-    */
-    for ( col = cols - 3; col >= 2; col -= 2 ) {
-        addflood( col, rows - 1 );
-        addflood( col, 0 );
-    }
-    for ( row = rows - 1; row >= 0; row -= 2 ) {
-        addflood( cols - 1, row );
-        addflood( 0, row );
-    }
-    flood( );
+    flood(bits, cols, rows, backcolor, mask);
 
-    if ( ! expand )
+    if (!cmdline.expand) {
         /* Done. */
-        pbm_writepbm( stdout, mask, cols, rows, 0 );
-    else {
-        /* Expand by one pixel. */
-        int srow, scol;
-        unsigned int row;
-        bit ** emask;
-
-        emask = pbm_allocarray( cols, rows );
-
-        for ( row = 0; row < rows; ++row ) {
-            unsigned int col;
-            for ( col = 0; col < cols; ++col )
-                if ( mask[row][col] == PBM_BLACK )
-                    emask[row][col] = PBM_BLACK;
-                else {
-                    emask[row][col] = PBM_WHITE;
-                    for ( srow = row - 1; srow <= row + 1; ++srow )
-                        for ( scol = col - 1; scol <= col + 1; ++scol )
-                            if ( srow >= 0 && srow < rows &&
-                                 scol >= 0 && scol < cols &&
-                                 mask[srow][scol] == PBM_BLACK ) {
-
-                                emask[row][col] = PBM_BLACK;
-                                break;
-                            }
-                }
-        }
-        pbm_writepbm( stdout, emask, cols, rows, 0 );
+        pbm_writepbm(stdout, mask, cols, rows, 0);
+    } else {
+        bit ** const emask = expandedByOnePixel(mask, cols, rows);
+
+        pbm_writepbm(stdout, emask, cols, rows, 0);
+
+        pbm_freearray(emask, rows);
     }
+}
+
+
+
+int
+main(int argc, const char ** argv) {
 
-    pm_close( stdout );
+    struct CmdlineInfo cmdline;
+    FILE * ifP;
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    pbmmask(ifP, stdout, cmdline);
+
+    pm_close(ifP);
+    pm_close(stdout);
 
     return 0;
 }
 
+
diff --git a/editor/pbmreduce.c b/editor/pbmreduce.c
index ee4a4fbd..3a0968fe 100644
--- a/editor/pbmreduce.c
+++ b/editor/pbmreduce.c
@@ -10,203 +10,345 @@
 ** implied warranty.
 */
 
-#include <limits.h>
+#include "pm_c_util.h"
 #include "pbm.h"
 #include "mallocvar.h"
+#include "shhopt.h"
+#include <assert.h>
 
-int
-main( argc, argv )
-    int argc;
-    char* argv[];
-    {
-    FILE* ifp;
-    register bit** bitslice;
-    register bit* newbitrow;
-    register bit* nbP;
-    int argn, n, rows, cols, format, newrows, newcols;
-    int row, col, limitcol, subrow, subcol, count, direction;
-    const char* const usage = "[-floyd|-fs | -threshold] [-value <val>] N [pbmfile]";
-    int halftone;
-#define QT_FS 1
-#define QT_THRESH 2
 #define SCALE 1024
 #define HALFSCALE 512
-    long threshval, sum;
-    long* thiserr;  /* used for Floyd-Steinberg stuff */
-    long* nexterr;  /* used for Floyd-Steinberg stuff */
-    long* temperr;
-
-
-    pbm_init( &argc, argv );
-
-    argn = 1;
-    halftone = QT_FS;
-    threshval = HALFSCALE;
-
-    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
-	{
-	if ( pm_keymatch( argv[argn], "-fs", 2 ) ||
-	     pm_keymatch( argv[argn], "-floyd", 2 ) )
-	    halftone = QT_FS;
-	else if ( pm_keymatch( argv[argn], "-threshold", 2 ) )
-	    halftone = QT_THRESH;
-	else if ( pm_keymatch( argv[argn], "-value", 2 ) )
-	    {
-	    float f;
-
-	    ++argn;
-	    if ( argn == argc || sscanf( argv[argn], "%f", &f ) != 1 ||
-		 f < 0.0 || f > 1.0 )
-		pm_usage( usage );
-	    threshval = f * SCALE;
-	    }
-	else
-	    pm_usage( usage );
-	++argn;
-	}
-
-    if ( argn == argc )
-	pm_usage( usage );
-    if ( sscanf( argv[argn], "%d", &n ) != 1 )
-	pm_usage( usage );
-    if ( n < 2 )
-	pm_error( "N must be greater than 1" );
-    if (n > INT_MAX / n)
-        pm_error("Scale argument too large.  You specified %d", n);
-    ++argn;
-
-    if ( argn == argc )
-	ifp = stdin;
-    else
-	{
-	ifp = pm_openr( argv[argn] );
-	++argn;
-	}
-
-    if ( argn != argc )
-	pm_usage( usage );
-
-    pbm_readpbminit( ifp, &cols, &rows, &format );
-    bitslice = pbm_allocarray( cols, n );
-
-    newrows = rows / n;
-    newcols = cols / n;
+
+
+enum Halftone {QT_FS, QT_THRESH};
+
+struct CmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *  inputFilespec;
+    enum Halftone halftone;
+    int           value;
+    unsigned int  randomseed;
+    unsigned int  randomseedSpec;
+    int           scale;
+};
+
+
+
+static void
+parseCommandLine(int argc, const char ** argv,
+                 struct CmdlineInfo *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;
+        /* Instructions to pm_optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+    unsigned int floydOpt, thresholdOpt;
+    unsigned int valueSpec;
+    float        value;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "floyd",       OPT_FLAG,  NULL,
+            &floydOpt,                      0);
+    OPTENT3(0, "fs",          OPT_FLAG,  NULL,
+            &floydOpt,                      0);
+    OPTENT3(0, "threshold",   OPT_FLAG,  NULL,
+            &thresholdOpt,                  0);
+    OPTENT3(0, "value",       OPT_FLOAT, &value,
+            &valueSpec,                     0);
+    OPTENT3(0, "randomseed",  OPT_UINT,  &cmdlineP->randomseed,
+            &cmdlineP->randomseedSpec,      0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We may have 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 (floydOpt + thresholdOpt == 0)
+        cmdlineP->halftone = QT_FS;
+    else if (!!floydOpt + !!thresholdOpt > 1)
+        pm_error("Cannot specify both floyd and threshold");
+    else {
+        if (floydOpt)
+            cmdlineP->halftone = QT_FS;
+        else {
+            cmdlineP->halftone = QT_THRESH;
+            if (cmdlineP->randomseedSpec)
+                pm_message("-randomseed value has no effect with -threshold");
+        }
+    }
+
+    if (!valueSpec)
+        cmdlineP->value = HALFSCALE;
+    else {
+        if (value < 0.0)
+            pm_error("-value cannot be negative.  You specified %f", value);
+        if (value > 1.0)
+            pm_error("-value cannot be greater than one.  You specified %f",
+                     value);
+        else
+            cmdlineP->value = value * SCALE;
+    }
+
+    if (argc-1 > 0) {
+        char * endptr;   /* ptr to 1st invalid character in scale arg */
+        unsigned int scale;
+
+        scale = strtol(argv[1], &endptr, 10);
+        if (*argv[1] == '\0') 
+            pm_error("Scale argument is a null string.  Must be a number.");
+        else if (*endptr != '\0')
+            pm_error("Scale argument contains non-numeric character '%c'.",
+                     *endptr);
+        else if (scale < 2)
+            pm_error("Scale argument must be at least 2.  "
+                     "You specified %d", scale);
+        else if (scale > INT_MAX / scale)
+            pm_error("Scale argument too large.  You specified %d", scale);
+        else 
+            cmdlineP->scale = scale;
+
+        if (argc-1 > 1) {
+            cmdlineP->inputFilespec = argv[2];
+
+            if (argc-1 > 2)
+                pm_error("Too many arguments (%d).  There are at most two "
+                         "non-option arguments: "
+                         "scale factor and the file name",
+                         argc-1);
+        } else
+            cmdlineP->inputFilespec = "-";
+    } else
+        pm_error("You must specify the scale factor as an argument");
+
+    free(option_def);
+}
+
+
+
+struct FS {
+  int * thiserr;
+  int * nexterr;
+};
+
+
+static void
+initializeFloydSteinberg(struct FS  * const fsP,
+                         int          const newcols,
+                         unsigned int const seed,
+                         bool         const seedSpec) {
+
+    unsigned int col;
+
+    MALLOCARRAY(fsP->thiserr, newcols + 2);
+    MALLOCARRAY(fsP->nexterr, newcols + 2);
+
+    if (fsP->thiserr == NULL || fsP->nexterr == NULL)
+        pm_error("out of memory");
+
+    srand(seedSpec ? seed : pm_randseed());
+
+    for (col = 0; col < newcols + 2; ++col)
+        fsP->thiserr[col] = (rand() % SCALE - HALFSCALE) / 4;
+        /* (random errors in [-SCALE/8 .. SCALE/8]) */
+}
+
+
+
+/*
+    Scanning method
+    
+    In Floyd-Steinberg dithering mode horizontal direction of scan alternates
+    between rows; this is called "serpentine scanning".
+    
+    Example input (14 x 7), N=3:
+    
+    111222333444xx    Fractional pixels on the right edge and bottom edge (x)
+    111222333444xx    are ignored; their values do not influence output. 
+    111222333444xx
+    888777666555xx
+    888777666555xx
+    888777666555xx
+    xxxxxxxxxxxxxx
+    
+    Output (4 x 2):
+    
+    1234
+    8765
+
+*/
+
+
+
+enum Direction { RIGHT_TO_LEFT, LEFT_TO_RIGHT };
+
+
+static enum Direction
+oppositeDir(enum Direction const arg) {
+
+    switch (arg) {
+    case LEFT_TO_RIGHT: return RIGHT_TO_LEFT;
+    case RIGHT_TO_LEFT: return LEFT_TO_RIGHT;
+    }
+    assert(false);  /* All cases handled above */
+}
+
+
+
+int
+main(int argc, const char * argv[]) {
+
+    FILE * ifP;
+    struct CmdlineInfo cmdline;
+    bit ** bitslice;
+    bit * newbitrow;
+    int rows, cols;
+    int format;
+    unsigned int newrows, newcols;
+    unsigned int row;
+    enum Direction direction;
+    struct FS fs;
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    pbm_readpbminit(ifP, &cols, &rows, &format);
+
+    bitslice = pbm_allocarray(cols, cmdline.scale);
+
+    if (rows < cmdline.scale || cols < cmdline.scale)
+        pm_error("Scale argument (%u) too large for image", cmdline.scale);
+    else {
+        newrows = rows / cmdline.scale;
+        newcols = cols / cmdline.scale;
+    }
     pbm_writepbminit( stdout, newcols, newrows, 0 );
-    newbitrow = pbm_allocrow( newcols );
+    newbitrow = pbm_allocrow_packed( newcols );
 
-    if (halftone == QT_FS) {
-        unsigned int col;
-        /* Initialize Floyd-Steinberg. */
-        MALLOCARRAY(thiserr, newcols + 2);
-        MALLOCARRAY(nexterr, newcols + 2);
-        if (thiserr == NULL || nexterr == NULL)
-            pm_error("out of memory");
-
-        srand(pm_randseed());
-        for (col = 0; col < newcols + 2; ++col)
-            thiserr[col] = (rand() % SCALE - HALFSCALE) / 4;
-	    /* (random errors in [-SCALE/8 .. SCALE/8]) */
-	} else {
+    if (cmdline.halftone == QT_FS)
+        initializeFloydSteinberg(&fs, newcols,
+                                 cmdline.randomseed, cmdline.randomseedSpec);
+    else {
         /* These variables are meaningless in this case, and the values
            should never be used.
         */
-        thiserr = NULL;
-        nexterr = NULL;
+        fs.thiserr = NULL;
+        fs.nexterr = NULL;
     }
-    direction = 1;
-
-    for ( row = 0; row < newrows; ++row )
-	{
-	for ( subrow = 0; subrow < n; ++subrow )
-	    pbm_readpbmrow( ifp, bitslice[subrow], cols, format );
-
-	if ( halftone == QT_FS )
-	    for ( col = 0; col < newcols + 2; ++col )
-		nexterr[col] = 0;
-	if ( direction )
-	    {
-	    col = 0;
-	    limitcol = newcols;
-	    nbP = newbitrow;
-	    }
-	else
-	    {
-	    col = newcols - 1;
-	    limitcol = -1;
-	    nbP = &(newbitrow[col]);
-	    }
-
-	do
-	    {
-	    sum = 0;
-	    count = 0;
-	    for ( subrow = 0; subrow < n; ++subrow )
-		for ( subcol = 0; subcol < n; ++subcol )
-		    if ( row * n + subrow < rows && col * n + subcol < cols )
-			{
-			count += 1;
-			if ( bitslice[subrow][col * n + subcol] == PBM_WHITE )
-			    sum += 1;
-			}
-	    sum = ( sum * SCALE ) / count;
-
-	    if ( halftone == QT_FS )
-		sum += thiserr[col + 1];
-
-	    if ( sum >= threshval )
-		{
-		*nbP = PBM_WHITE;
-		if ( halftone == QT_FS )
-		    sum = sum - threshval - HALFSCALE;
-		}
-	    else
-		*nbP = PBM_BLACK;
-
-	    if ( halftone == QT_FS )
-		{
-		if ( direction )
-		    {
-		    thiserr[col + 2] += ( sum * 7 ) / 16;
-		    nexterr[col    ] += ( sum * 3 ) / 16;
-		    nexterr[col + 1] += ( sum * 5 ) / 16;
-		    nexterr[col + 2] += ( sum     ) / 16;
-		    }
-		else
-		    {
-		    thiserr[col    ] += ( sum * 7 ) / 16;
-		    nexterr[col + 2] += ( sum * 3 ) / 16;
-		    nexterr[col + 1] += ( sum * 5 ) / 16;
-		    nexterr[col    ] += ( sum     ) / 16;
-		    }
-		}
-	    if ( direction )
-		{
-		++col;
-		++nbP;
-		}
-	    else
-		{
-		--col;
-		--nbP;
-		}
-	    }
-	while ( col != limitcol );
-
-	pbm_writepbmrow( stdout, newbitrow, newcols, 0 );
-
-	if ( halftone == QT_FS )
-	    {
-	    temperr = thiserr;
-	    thiserr = nexterr;
-	    nexterr = temperr;
-	    direction = ! direction;
-	    }
-	}
-
-    pm_close( ifp );
-    pm_close( stdout );
-
-    exit( 0 );
+
+    for (row = 0, direction = LEFT_TO_RIGHT; row < newrows; ++row) {
+        unsigned int const colChars = pbm_packed_bytes(newcols);
+
+        unsigned int colChar;
+        unsigned int subrow;
+        unsigned int col;
+        int limitCol;
+        int startCol;
+        int step;
+   
+        for (colChar = 0; colChar < colChars; ++colChar)
+            newbitrow[colChar] = 0x00;  /* Clear to white */
+ 
+        for (subrow = 0; subrow < cmdline.scale; ++subrow)
+            pbm_readpbmrow(ifP, bitslice[subrow], cols, format);
+
+        if (cmdline.halftone == QT_FS) {
+            unsigned int col;
+            for (col = 0; col < newcols + 2; ++col)
+                fs.nexterr[col] = 0;
+        }
+        switch (direction) {
+        case LEFT_TO_RIGHT: {
+            startCol = 0;
+            limitCol = newcols;
+            step = +1;  
+        } break;
+        case RIGHT_TO_LEFT: {
+            startCol = newcols - 1;
+            limitCol = -1;
+            step = -1;
+        } break;
+        }
+
+        for (col = startCol; col != limitCol; col += step) {
+            int const n = cmdline.scale;
+            unsigned int sum;
+            int sumScaled;
+            unsigned int subrow;
+
+            for (subrow = 0, sum = 0; subrow < n; ++subrow) {
+                unsigned int subcol;
+                for (subcol = 0; subcol < n; ++subcol) {
+                    assert(row * n + subrow < rows);
+                    assert(col * n + subcol < cols);
+                    if (bitslice[subrow][col * n + subcol] == PBM_WHITE)
+                        ++sum;
+                }
+            }
+
+            sumScaled = (sum * SCALE) / (SQR(n));
+
+            if (cmdline.halftone == QT_FS)
+                sumScaled += fs.thiserr[col + 1];
+
+            if (sumScaled >= cmdline.value) {
+                if (cmdline.halftone == QT_FS)
+                    sumScaled = sumScaled - cmdline.value - HALFSCALE;
+            } else
+                newbitrow[col/8] |= (PBM_BLACK << (7 - col%8));
+
+            if (cmdline.halftone == QT_FS) {
+                switch (direction) {
+                case LEFT_TO_RIGHT: {
+                    fs.thiserr[col + 2] += ( sumScaled * 7 ) / 16;
+                    fs.nexterr[col    ] += ( sumScaled * 3 ) / 16;
+                    fs.nexterr[col + 1] += ( sumScaled * 5 ) / 16;
+                    fs.nexterr[col + 2] += ( sumScaled     ) / 16;
+                    break;
+                }
+                case RIGHT_TO_LEFT: {
+                    fs.thiserr[col    ] += ( sumScaled * 7 ) / 16;
+                    fs.nexterr[col + 2] += ( sumScaled * 3 ) / 16;
+                    fs.nexterr[col + 1] += ( sumScaled * 5 ) / 16;
+                    fs.nexterr[col    ] += ( sumScaled     ) / 16;
+                    break;
+                }
+                }
+            }
+        }
+
+        pbm_writepbmrow_packed(stdout, newbitrow, newcols, 0);
+
+        if (cmdline.halftone == QT_FS) {
+            int * const temperr = fs.thiserr;
+            fs.thiserr = fs.nexterr;
+            fs.nexterr = temperr;
+            direction  = oppositeDir(direction);
+        }
     }
 
+    free(fs.thiserr);
+    free(fs.nexterr);
+
+    pbm_freerow(newbitrow);
+    pbm_freearray(bitslice, cmdline.scale);
+    pm_close(ifP);
+    pm_close(stdout);
+
+    return 0;
+}
+
 
diff --git a/editor/pgmmedian.c b/editor/pgmmedian.c
index 9d90b6b3..4648af68 100644
--- a/editor/pgmmedian.c
+++ b/editor/pgmmedian.c
@@ -132,13 +132,12 @@ select489(gray * const a,
 
     gray t;
     int i, j, l, r;
-    int ptmp, ttmp;
+    int ptmp;
 
     l = 0;
     r = n - 1;
     while ( r > l ) {
         t = a[parray[k]];
-        ttmp = parray[k];
         i = l;
         j = r;
         ptmp = parray[l];
diff --git a/editor/pnmconvol.c b/editor/pnmconvol.c
index 8d9bb83a..b1d8e025 100644
--- a/editor/pnmconvol.c
+++ b/editor/pnmconvol.c
@@ -169,7 +169,7 @@ getMatrixOptDimensions(const char *   const matrixOptString,
                 if (colCt != *widthP)
                 pm_error("-matrix option value contains rows of different "
                          "widths: %u and %u", *widthP, colCt);
-            }            
+            }
             pm_strfree(rowString);
             cursor = next;
 
@@ -211,7 +211,7 @@ parseMatrixRow(const char * const matrixOptRowString,
                 char * trailingJunk;
                 weight[col] = strtod(colString, &trailingJunk);
 
-                if (*trailingJunk != '\0') 
+                if (*trailingJunk != '\0')
                     pm_error("The Column %u element of the row '%s' in the "
                              "-matrix value is not a valid floating point "
                              "number", col, matrixOptRowString);
@@ -235,7 +235,7 @@ parseMatrixOptWithDimensions(const char * const matrixOptString,
                              unsigned int const width,
                              unsigned int const height,
                              float **     const weight) {
-    
+
     unsigned int row;
     const char * cursor;
 
@@ -262,7 +262,7 @@ parseMatrixOptWithDimensions(const char * const matrixOptString,
             }
         }
     }
-}    
+}
 
 
 
@@ -311,7 +311,7 @@ parseCommandLine(int argc, char ** argv,
                  struct cmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    parse program command line described in Unix standard form by argc
-   and argv.  Return the information in the options as *cmdlineP.  
+   and argv.  Return the information in the options as *cmdlineP.
 
    If command line is internally inconsistent (invalid options, etc.),
    issue error message to stderr and abort program.
@@ -336,9 +336,9 @@ parseCommandLine(int argc, char ** argv,
             &cmdlineP->matrixSpec,     0)
     OPTENT3(0, "matrixfile",   OPT_STRINGLIST, &cmdlineP->matrixfile,
             &matrixfileSpec,           0)
-    OPTENT3(0, "nooffset",     OPT_FLAG,   NULL,                  
+    OPTENT3(0, "nooffset",     OPT_FLAG,   NULL,
             &cmdlineP->nooffset,       0);
-    OPTENT3(0, "normalize",    OPT_FLAG,   NULL,                  
+    OPTENT3(0, "normalize",    OPT_FLAG,   NULL,
             &cmdlineP->normalize,      0);
     OPTENT3(0, "bias",         OPT_UINT,   &cmdlineP->bias,
             &biasSpec,                 0);
@@ -382,7 +382,7 @@ parseCommandLine(int argc, char ** argv,
                      "argument is the input file name");
     } else {
         /* It's an old style invocation we accept for backward compatibility */
-        
+
         if (argc-1 < 1)
             pm_error("You must specify either -matrix or -matrixfile "
                      "at least one argument which names an old-style PGM "
@@ -394,7 +394,7 @@ parseCommandLine(int argc, char ** argv,
                 cmdlineP->inputFileName = argv[2];
             else
                 cmdlineP->inputFileName = "-";
-            
+
             if (argc-1 > 2)
                 pm_error("Too many arguments.  Only acceptable arguments are: "
                          "convolution matrix file name and input file name");
@@ -416,11 +416,11 @@ struct ConvKernel {
     float ** weight[3];
         /* weight[PLANE][ROW][COL] is the weight to give to Plane PLANE
            of the pixel at row ROW, column COL within the convolution window.
-           
+
            One means full weight.
 
            It can have magnitude greater than or less than one.  It can be
-           positive or negative.  
+           positive or negative.
         */
     unsigned int bias;
         /* The amount to be added to the linear combination of sample values.
@@ -441,7 +441,7 @@ warnBadKernel(struct ConvKernel * const convKernelP) {
 
     for (plane = 0; plane < convKernelP->planes; ++plane)
         sum[plane] = 0.0; /* initial value */
-    
+
     for (row = 0; row < convKernelP->rows; ++row) {
         unsigned int col;
         for (col = 0; col < convKernelP->cols; ++col) {
@@ -462,9 +462,9 @@ warnBadKernel(struct ConvKernel * const convKernelP) {
             if (sum[plane] < 0.0)
                 negative = true;
         }
-    
+
         if (biased) {
-            pm_message("WARNING - this convolution matrix is biased.  " 
+            pm_message("WARNING - this convolution matrix is biased.  "
                        "red, green, and blue average weights: %f, %f, %f "
                        "(unbiased would be 1).",
                        sum[PAM_RED_PLANE],
@@ -488,15 +488,14 @@ warnBadKernel(struct ConvKernel * const convKernelP) {
 
 static void
 convKernelCreatePnm(struct pam *         const cpamP,
-                    tuple * const *      const ctuples, 
+                    tuple * const *      const ctuples,
                     unsigned int         const depth,
                     bool                 const offsetPnm,
                     struct ConvKernel ** const convKernelPP) {
 /*----------------------------------------------------------------------------
-   Compute the convolution matrix in normalized form from the PGM form
-   'ctuples'/'cpamP'.  Each element of the output matrix is the actual weight
-   we give an input pixel -- i.e. the thing by which we multiple a value from
-   the input image.
+   Compute the convolution matrix from the PGM form 'ctuples'/'cpamP'.  Each
+   element of the output matrix is the actual weight we give an input pixel --
+   i.e. the thing by which we multiple a value from the input image.
 
    'depth' is the required number of planes in the kernel.  If 'ctuples' has
    fewer planes than that, we duplicate as necessary.  E.g. if 'ctuples' is
@@ -527,7 +526,7 @@ convKernelCreatePnm(struct pam *         const cpamP,
         unsigned int row;
 
         MALLOCARRAY_NOFAIL(convKernelP->weight[plane], cpamP->height);
-    
+
         for (row = 0; row < cpamP->height; ++row) {
             unsigned int col;
 
@@ -570,15 +569,18 @@ convKernelDestroy(struct ConvKernel * const convKernelP) {
 static void
 normalizeKernelPlane(struct ConvKernel * const convKernelP,
                      unsigned int        const plane) {
-
+/*----------------------------------------------------------------------------
+   Modify *convKernelP by scaling every weight in plane 'plane' by the same
+   factor such that the weights in the plane all add up to 1.
+-----------------------------------------------------------------------------*/
     unsigned int row;
     float sum;
 
     for (row = 0, sum = 0.0; row < convKernelP->rows; ++row) {
         unsigned int col;
-            
+
         for (col = 0; col < convKernelP->cols; ++col) {
-                
+
             sum += convKernelP->weight[plane][row][col];
         }
     }
@@ -590,7 +592,7 @@ normalizeKernelPlane(struct ConvKernel * const convKernelP,
 
         for (row = 0; row < convKernelP->rows; ++row) {
             unsigned int col;
-                
+
             for (col = 0; col < convKernelP->cols; ++col)
                 convKernelP->weight[plane][row][col] *= scaler;
         }
@@ -602,8 +604,9 @@ normalizeKernelPlane(struct ConvKernel * const convKernelP,
 static void
 normalizeKernel(struct ConvKernel * const convKernelP) {
 /*----------------------------------------------------------------------------
-   Modify *convKernelP by scaling every weight in a plane by the same factor
-   such that the weights in the plane all add up to 1.
+   Modify *convKernelP by scaling each plane as follows: Scale every weight in
+   the plane by the same factor such that the weights in the plane all add up
+   to 1.
 -----------------------------------------------------------------------------*/
     unsigned int plane;
 
@@ -638,7 +641,7 @@ getKernelPnm(const char *         const fileName,
     /* Read in the convolution matrix. */
     ctuples = pnm_readpam(cifP, &cpam, PAM_STRUCT_SIZE(tuple_type));
     pm_close(cifP);
-    
+
     validateKernelDimensions(cpam.width, cpam.height);
 
     convKernelCreatePnm(&cpam, ctuples, depth, offset, convKernelPP);
@@ -648,20 +651,14 @@ getKernelPnm(const char *         const fileName,
 
 static void
 convKernelCreateMatrixOpt(struct matrixOpt     const matrixOpt,
-                          bool                 const normalize,
                           unsigned int         const depth,
                           unsigned int         const bias,
                           struct ConvKernel ** const convKernelPP) {
 /*----------------------------------------------------------------------------
    Create a convolution kernel as described by a -matrix command line
    option.
-   
-   The option value is 'matrixOpt'.
 
-   If 'normalize' is true, we normalize whatever numbers the option specifies
-   so that they add up to one; otherwise, we take the numbers as we find them,
-   so they may form a biased matrix -- i.e. one which brightens or dims the
-   image overall.
+   The option value is 'matrixOpt'.
 -----------------------------------------------------------------------------*/
     struct ConvKernel * convKernelP;
     unsigned int plane;
@@ -681,17 +678,14 @@ convKernelCreateMatrixOpt(struct matrixOpt     const matrixOpt,
 
             MALLOCARRAY_NOFAIL(convKernelP->weight[plane][row],
                                matrixOpt.width);
-    
+
             for (col = 0; col < matrixOpt.width; ++col)
                 convKernelP->weight[plane][row][col] =
                     matrixOpt.weight[row][col];
         }
     }
-    if (normalize)
-        normalizeKernel(convKernelP);
 
     convKernelP->bias = bias;
-
     *convKernelPP = convKernelP;
 }
 
@@ -734,12 +728,12 @@ parsePlaneFileLine(const char *   const line,
         else {
             char * trailingJunk;
             weight[colCt] = strtod(token, &trailingJunk);
-            if (*trailingJunk != '\0') 
+            if (*trailingJunk != '\0')
                 pm_error("The Column %u element of the row '%s' in the "
                          "-matrix value is not a valid floating point "
                          "number", colCt, line);
 
-                ++colCt;
+            ++colCt;
         }
         pm_strfree(token);
     }
@@ -750,7 +744,7 @@ parsePlaneFileLine(const char *   const line,
 
 
 static void
-readPlaneFile(FILE *         const ifP, 
+readPlaneFile(FILE *         const ifP,
               float ***      const weightP,
               unsigned int * const widthP,
               unsigned int * const heightP) {
@@ -789,7 +783,7 @@ readPlaneFile(FILE *         const ifP,
                 eof = true;
             else {
                 REALLOCARRAY(weight, rowCt + 1);
-            
+
                 if (weight == NULL)
                     pm_error("Unable to allocate memory for "
                              "convolution matrix");
@@ -805,7 +799,7 @@ readPlaneFile(FILE *         const ifP,
                             pm_error("Multiple row widths in the convolution "
                                      "matrix file: %u columns and %u columns.",
                                      width, thisWidth);
-                    }                    
+                    }
                     ++rowCt;
                 }
                 pm_strfree(line);
@@ -824,7 +818,7 @@ readPlaneFile(FILE *         const ifP,
 static void
 copyWeight(float **       const srcWeight,
            unsigned int   const width,
-           unsigned int   const height, 
+           unsigned int   const height,
            float ***      const dstWeightP) {
 /*----------------------------------------------------------------------------
    Make a copy, in dynamically allocated memory, of the weight matrix
@@ -838,7 +832,7 @@ copyWeight(float **       const srcWeight,
 
     if (dstWeight == NULL)
         pm_error("Could not allocate memory for convolution matrix");
-   
+
     for (row = 0; row < height; ++row) {
         unsigned int col;
 
@@ -859,7 +853,6 @@ copyWeight(float **       const srcWeight,
 
 static void
 convKernelCreateSimpleFile(const char **        const fileNameList,
-                           bool                 const normalize,
                            unsigned int         const depth,
                            unsigned int         const bias,
                            struct ConvKernel ** const convKernelPP) {
@@ -869,11 +862,6 @@ convKernelCreateSimpleFile(const char **        const fileNameList,
    legacy pseudo-PNM thing.
 
    The name of the file is 'fileNameList'.
-
-   If 'normalize' is true, we normalize whatever numbers we find in the file
-   so that they add up to one; otherwise, we take the numbers as we find them,
-   so they may form a biased matrix -- i.e. one which brightens or dims the
-   image overall.
 -----------------------------------------------------------------------------*/
     struct ConvKernel * convKernelP;
     unsigned int fileCt;
@@ -923,9 +911,6 @@ convKernelCreateSimpleFile(const char **        const fileNameList,
         }
     }
 
-    if (normalize)
-        normalizeKernel(convKernelP);
-
     convKernelP->cols = width;
     convKernelP->rows = height;
     convKernelP->bias = bias;
@@ -953,11 +938,14 @@ getKernel(struct cmdlineInfo   const cmdline,
         getKernelPnm(cmdline.pnmMatrixFileName, depth, !cmdline.nooffset,
                      &convKernelP);
     else if (cmdline.matrixfile)
-        convKernelCreateSimpleFile(cmdline.matrixfile, cmdline.normalize,
-                                   depth, cmdline.bias, &convKernelP);
+        convKernelCreateSimpleFile(cmdline.matrixfile, depth, cmdline.bias,
+            &convKernelP);
     else if (cmdline.matrixSpec)
-        convKernelCreateMatrixOpt(cmdline.matrix, cmdline.normalize,
-                                  depth, cmdline.bias, &convKernelP);
+        convKernelCreateMatrixOpt(cmdline.matrix, depth, cmdline.bias,
+            &convKernelP);
+
+    if (cmdline.normalize)
+        normalizeKernel(convKernelP);
 
     warnBadKernel(convKernelP);
 
@@ -985,7 +973,7 @@ validateEnoughImageToConvolve(const struct pam *        const inpamP,
         pm_error("Image is too short (%u rows) to convolve with this "
                  "%u-row convolution kernel.",
                  inpamP->height, convKernelP->rows);
-    
+
     if (inpamP->width < convKernelP->cols + 1)
         pm_error("Image is too narrow (%u columns) to convolve with this "
                  "%u-column convolution kernel.",
@@ -1006,7 +994,7 @@ allocRowbuf(struct pam * const pamP,
         pm_error("Failed to allocate %u-row buffer", height);
     else {
         unsigned int row;
-    
+
         for (row = 0; row < height; ++row)
             rowbuf[row] = pnm_allocpamrow(pamP);
     }
@@ -1055,7 +1043,7 @@ readAndScaleRows(struct pam *              const inpamP,
                  unsigned int              const outputDepth) {
 /*----------------------------------------------------------------------------
   Read in 'count' rows into rowbuf[].
-  
+
   Scale the contents to maxval 'outputMaxval' and expand to depth
   'outputDepth'.
 -----------------------------------------------------------------------------*/
@@ -1185,7 +1173,7 @@ convolveGeneralRowPlane(struct pam *              const pamP,
     unsigned int const ccolso2 = convKernelP->cols / 2;
 
     unsigned int col;
-    
+
     for (col = 0; col < pamP->width; ++col) {
         if (col < ccolso2 || col >= pamP->width - ccolso2)
             /* The unconvolved left or right edge */
@@ -1295,7 +1283,7 @@ allocSum(unsigned int const depth,
 
         for (plane = 0; plane < depth; ++plane) {
             MALLOCARRAY(sum[plane], size);
-            
+
             if (!sum[plane])
                 pm_error("Could not allocate memory for %u sums", size);
         }
@@ -1342,7 +1330,7 @@ computeInitialColumnSums(struct pam *              const pamP,
                  row < convKernelP->rows;
                  ++row)
                 convColumnSum[plane][col] += window[row][col][plane];
-        }            
+        }
     }
 }
 
@@ -1370,7 +1358,7 @@ convolveRowWithColumnSumsMean(const struct ConvKernel * const convKernelP,
   be convolved because the convolution window runs off the edge).
 -----------------------------------------------------------------------------*/
     unsigned int plane;
-    
+
     for (plane = 0; plane < pamP->depth; ++plane) {
         unsigned int const crowso2 = convKernelP->rows / 2;
         unsigned int const ccolso2 = convKernelP->cols / 2;
@@ -1397,7 +1385,7 @@ convolveRowWithColumnSumsMean(const struct ConvKernel * const convKernelP,
                 } else {
                     /* Column numbers to subtract or add to isum */
                     unsigned int const subcol = col - ccolso2 - 1;
-                    unsigned int const addcol = col + ccolso2;  
+                    unsigned int const addcol = col + ccolso2;
 
                     gisum -= convColumnSum[plane][subcol];
                     gisum += convColumnSum[plane][addcol];
@@ -1441,7 +1429,7 @@ convolveRowWithColumnSumsVertical(
 
     for (plane = 0; plane < pamP->depth; ++plane) {
         unsigned int col;
-    
+
         for (col = 0; col < pamP->width; ++col) {
             if (col < ccolso2 || col >= pamP->width - ccolso2) {
                 /* The unconvolved left or right edge */
@@ -1525,12 +1513,12 @@ convolveMeanRowPlane(struct pam *              const pamP,
             } else {
                 /* Column numbers to subtract or add to isum */
                 unsigned int const subcol = col - ccolso2 - 1;
-                unsigned int const addcol = col + ccolso2;  
-                
+                unsigned int const addcol = col + ccolso2;
+
                 convColumnSum[addcol] = convColumnSum[addcol]
                     - window[subrow][addcol][plane]
                     + window[addrow][addcol][plane];
-                
+
                 gisum = gisum - convColumnSum[subcol] + convColumnSum[addcol];
             }
             outputrow[col][plane] =
@@ -1686,7 +1674,7 @@ allocRowSum(unsigned int const depth,
 
         for (plane = 0; plane < depth; ++plane) {
             MALLOCARRAY(sum[plane], height);
-            
+
             if (!sum[plane])
                 pm_error("Could not allocate memory for %u rows of sums",
                          height);
@@ -1695,7 +1683,7 @@ allocRowSum(unsigned int const depth,
 
                 for (row = 0; row < height; ++row) {
                     MALLOCARRAY(sum[plane][row], width);
-                    
+
                     if (!sum[plane][row])
                         pm_error("Could not allocate memory "
                                  "for a row of sums");
@@ -1759,7 +1747,7 @@ convolveHorizontalRowPlane0(struct pam *              const outpamP,
                    starts at the left edge of the image.
                 */
                 unsigned int const leftcol = 0;
-            
+
                 unsigned int crow;
 
                 for (crow = 0, matrixSum = 0.0;
@@ -1768,7 +1756,7 @@ convolveHorizontalRowPlane0(struct pam *              const outpamP,
                     tuple * const tuplesInWindow = &window[crow][leftcol];
 
                     unsigned int ccol;
-                
+
                     sumWindow[crow][col] = 0;
                     for (ccol = 0; ccol < convKernelP->cols; ++ccol)
                         sumWindow[crow][col] += tuplesInWindow[ccol][plane];
@@ -1781,7 +1769,7 @@ convolveHorizontalRowPlane0(struct pam *              const outpamP,
                 unsigned int const addcol  = col + ccolso2;
 
                 unsigned int crow;
-                
+
                 for (crow = 0, matrixSum = 0.0;
                      crow < convKernelP->rows;
                      ++crow) {
@@ -1810,7 +1798,7 @@ setupCircMap2(tuple **     const rowbuf,
               unsigned int const windowHeight) {
 
     unsigned int const toprow = windowTopRow % windowHeight;
-    
+
     unsigned int crow;
     unsigned int i;
 
@@ -1886,7 +1874,7 @@ convolveHorizontalRowPlane(struct pam *              const pamP,
             }
         } else {
             unsigned int const subcol  = col - ccolso2 - 1;
-            unsigned int const addcol  = col + ccolso2;  
+            unsigned int const addcol  = col + ccolso2;
 
             unsigned int crow;
 
@@ -1959,7 +1947,7 @@ convolveHorizontal(struct pam *              const inpamP,
 
         for (crow = 0; crow < convKernelP->rows; ++crow)
             sumCircMap[crow] = convRowSum[plane][crow];
- 
+
         convolveHorizontalRowPlane0(outpamP, circMap, convKernelP, plane,
                                     outputrow, sumCircMap);
     }
@@ -1981,13 +1969,13 @@ convolveHorizontal(struct pam *              const inpamP,
 
             readAndScaleRow(inpamP, rowbuf[windowBotRow % windowHeight],
                             outpamP->maxval, outpamP->depth);
-            
+
             setupCircMap2(rowbuf, convRowSum[plane], circMap, sumCircMap,
                           windowTopRow, windowHeight);
 
             convolveHorizontalRowPlane(outpamP, circMap, convKernelP, plane,
                                        outputrow, sumCircMap);
-            
+
             pnm_writepamrow(outpamP, outputrow);
         }
     }
@@ -2017,7 +2005,7 @@ convolveVerticalRowPlane(struct pam *              const pamP,
         */
     unsigned int const addrow = 1 + (convKernelP->rows - 1);
         /* Bottom row of convolution window: What we add to running sum */
-    
+
     unsigned int col;
 
     for (col = 0; col < pamP->width; ++col) {
@@ -2316,7 +2304,7 @@ main(int argc, char * argv[]) {
  original) in January 1995.
 
  Reduce run time by general optimizations and handling special cases of
- convolution matrices.  Program automatically determines if convolution 
+ convolution matrices.  Program automatically determines if convolution
  matrix is one of the types it can make use of so no extra command line
  arguments are necessary.
 
@@ -2334,7 +2322,7 @@ main(int argc, char * argv[]) {
  -------------------------------------------
  Created separate functions as code was getting too large to put keep both
  PGM and PPM cases in same function and also because SWITCH statement in
- inner loop can take progressively more time the larger the size of the 
+ inner loop can take progressively more time the larger the size of the
  convolution matrix.  GCC is affected this way.
 
  Removed use of MOD (%) operator from innermost loop by modifying manner in
@@ -2343,50 +2331,50 @@ main(int argc, char * argv[]) {
  This is from the file pnmconvol.README, dated August 1995, extracted in
  April 2000, which was in the March 1994 Netpbm release:
 
- ----------------------------------------------------------------------------- 
+ -----------------------------------------------------------------------------
  This is a faster version of the pnmconvol.c program that comes with netpbm.
  There are no changes to the command line arguments, so this program can be
  dropped in without affecting the way you currently run it.  An updated man
  page is also included.
- 
+
  My original intention was to improve the running time of applying a
  neighborhood averaging convolution matrix to an image by using a different
  algorithm, but I also improved the run time of performing the general
  convolution by optimizing that code.  The general convolution runs in 1/4 to
  1/2 of the original time and neighborhood averaging runs in near constant
  time for the convolution masks I tested (3x3, 5x5, 7x7, 9x9).
- 
+
  Sample times for two computers are below.  Times are in seconds as reported
  by /bin/time for a 512x512 pgm image.
- 
+
  Matrix                  IBM RS6000      SUN IPC
  Size & Type                220
- 
+
  3x3
  original pnmconvol         6.3            18.4
  new general case           3.1             6.0
  new average case           1.8             2.6
- 
+
  5x5
  original pnmconvol        11.9            44.4
  new general case           5.6            11.9
  new average case           1.8             2.6
- 
+
  7x7
  original pnmconvol        20.3            82.9
  new general case           9.4            20.7
  new average case           1.8             2.6
- 
+
  9x9
  original pnmconvol        30.9           132.4
  new general case          14.4            31.8
  new average case           1.8             2.6
- 
- 
+
+
  Send all questions/comments/bugs to me at burns@chem.psu.edu.
- 
+
  - Mike
- 
+
  ----------------------------------------------------------------------------
  Mike Burns                                              System Administrator
  burns@chem.psu.edu                                   Department of Chemistry
diff --git a/editor/pnmcrop.c b/editor/pnmcrop.c
index c6aabff1..d6bae1d3 100644
--- a/editor/pnmcrop.c
+++ b/editor/pnmcrop.c
@@ -1,4 +1,4 @@
-/* pnmcrop.c - crop a portable anymap
+/* pnmcrop.c - crop a Netpbm image
 **
 ** Copyright (C) 1988 by Jef Poskanzer.
 **
@@ -29,10 +29,24 @@
 #include "pnm.h"
 #include "shhopt.h"
 #include "mallocvar.h"
+#include "nstring.h"
 
-enum bg_choice {BG_BLACK, BG_WHITE, BG_DEFAULT, BG_SIDES};
+static double const sqrt3 = 1.73205080756887729352;
+    /* The square root of 3 */
+static double const EPSILON = 1.0e-5;
 
-typedef enum { LEFT = 0, RIGHT = 1, TOP = 2, BOTTOM = 3} edgeLocation;
+enum BgChoice {BG_BLACK, BG_WHITE, BG_DEFAULT, BG_SIDES, BG_CORNER, BG_COLOR};
+
+enum BaseOp {OP_CROP, OP_REPORT_FULL, OP_REPORT_SIZE};
+
+enum BlankMode {BLANK_ABORT, BLANK_PASS, BLANK_MINIMIZE, BLANK_MAXCROP};
+
+typedef enum {LEFT = 0, RIGHT = 1, TOP = 2, BOTTOM = 3} EdgeLocation;
+
+typedef struct {
+    EdgeLocation v;
+    EdgeLocation h;
+} CornerLocation;
 
 static const char * const edgeName[] = {
     "left",
@@ -53,24 +67,32 @@ typedef enum {
         /* Immediately after the raster */
 } imageFilePos;
 
-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;
-    enum bg_choice background;
+    enum BaseOp baseOperation;
+    enum BgChoice background;
     bool wantCrop[4];
         /* User wants crop of left, right, top, bottom, resp. */
-    unsigned int verbose;
     unsigned int margin;
     const char * borderfile;  /* NULL if none */
+    float closeness;
+    CornerLocation bgCorner;     /* valid if background == BG_CORNER */
+    const char * bgColor;  /* valid if background == BG_COLOR */
+        /* Note that we can have only the name of the color, not the color
+           itself, because we don't know the maxval at option parsing time.
+        */
+    enum BlankMode blankMode;
+    unsigned int verbose;
 };
 
 
 
 static void
 parseCommandLine(int argc, const char ** argv,
-                 struct cmdlineInfo *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.
@@ -81,26 +103,42 @@ parseCommandLine(int argc, const char ** argv,
     optStruct3 opt;
 
     unsigned int blackOpt, whiteOpt, sidesOpt;
-    unsigned int marginSpec, borderfileSpec;
+    unsigned int marginSpec, borderfileSpec, closenessSpec;
     unsigned int leftOpt, rightOpt, topOpt, bottomOpt;
-    
+    unsigned int bgCornerSpec, bgColorSpec;
+    unsigned int blankModeSpec;
+    unsigned int reportFullOpt, reportSizeOpt;
+
     unsigned int option_def_index;
 
+    char * bgCornerOpt;
+    char * blankModeOpOpt;
+
     MALLOCARRAY_NOFAIL(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENT3 */
-    OPTENT3(0, "black",      OPT_FLAG, NULL, &blackOpt,            0);
-    OPTENT3(0, "white",      OPT_FLAG, NULL, &whiteOpt,            0);
-    OPTENT3(0, "sides",      OPT_FLAG, NULL, &sidesOpt,            0);
-    OPTENT3(0, "left",       OPT_FLAG, NULL, &leftOpt,             0);
-    OPTENT3(0, "right",      OPT_FLAG, NULL, &rightOpt,            0);
-    OPTENT3(0, "top",        OPT_FLAG, NULL, &topOpt,              0);
-    OPTENT3(0, "bottom",     OPT_FLAG, NULL, &bottomOpt,           0);
-    OPTENT3(0, "verbose",    OPT_FLAG, NULL, &cmdlineP->verbose,   0);
-    OPTENT3(0, "margin",     OPT_UINT,   &cmdlineP->margin,    
+    OPTENT3(0, "black",       OPT_FLAG,   NULL, &blackOpt,            0);
+    OPTENT3(0, "white",       OPT_FLAG,   NULL, &whiteOpt,            0);
+    OPTENT3(0, "sides",       OPT_FLAG,   NULL, &sidesOpt,            0);
+    OPTENT3(0, "bg-color",    OPT_STRING, &cmdlineP->bgColor,
+            &bgColorSpec,   0);
+    OPTENT3(0, "bg-corner",   OPT_STRING, &bgCornerOpt,
+            &bgCornerSpec,  0);
+    OPTENT3(0, "left",        OPT_FLAG,   NULL, &leftOpt,             0);
+    OPTENT3(0, "right",       OPT_FLAG,   NULL, &rightOpt,            0);
+    OPTENT3(0, "top",         OPT_FLAG,   NULL, &topOpt,              0);
+    OPTENT3(0, "bottom",      OPT_FLAG,   NULL, &bottomOpt,           0);
+    OPTENT3(0, "margin",      OPT_UINT,   &cmdlineP->margin,
             &marginSpec,     0);
-    OPTENT3(0, "borderfile", OPT_STRING, &cmdlineP->borderfile,
+    OPTENT3(0, "borderfile",  OPT_STRING, &cmdlineP->borderfile,
             &borderfileSpec, 0);
+    OPTENT3(0, "closeness",   OPT_FLOAT,  &cmdlineP->closeness,
+            &closenessSpec,  0);
+    OPTENT3(0, "blank-image", OPT_STRING, &blankModeOpOpt,
+            &blankModeSpec,  0);
+    OPTENT3(0, "reportfull",  OPT_FLAG,   NULL, &reportFullOpt,       0);
+    OPTENT3(0, "reportsize",  OPT_FLAG,   NULL, &reportSizeOpt,       0);
+    OPTENT3(0, "verbose",     OPT_FLAG,   NULL, &cmdlineP->verbose,   0);
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
@@ -108,30 +146,73 @@ parseCommandLine(int argc, const char ** argv,
 
     pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
-        
+
     free(option_def);
 
     if (argc-1 == 0)
         cmdlineP->inputFilespec = "-";  /* stdin */
     else if (argc-1 == 1)
         cmdlineP->inputFilespec = argv[1];
-    else 
+    else
         pm_error("Too many arguments (%d).  "
                  "Only need one: the input filespec", argc-1);
 
-    if (blackOpt && whiteOpt)
-        pm_error("You cannot specify both -black and -white");
-    else if (sidesOpt &&( blackOpt || whiteOpt ))
-        pm_error("You cannot specify both -sides and either -black or -white");
+    /* Base operation */
+
+    if (reportFullOpt && reportSizeOpt)
+        pm_error("You cannot specify both -reportfull and -reportsize");
+
+    if ((reportFullOpt || reportSizeOpt) && borderfileSpec)
+        pm_error("You cannot specify -reportfull or -reportsize "
+                 "with -borderfile");
+
+    if (reportFullOpt)
+        cmdlineP->baseOperation = OP_REPORT_FULL;
+    else if (reportSizeOpt)
+        cmdlineP->baseOperation = OP_REPORT_SIZE;
+    else
+        cmdlineP->baseOperation = OP_CROP;
+
+    /* Background color */
+
+    if (blackOpt + whiteOpt + sidesOpt + bgColorSpec + bgCornerSpec > 1)
+        pm_error("You cannot specify more than one of "
+                 "-black, -white, -sides, -bg-color, -bg-corner");
     else if (blackOpt)
         cmdlineP->background = BG_BLACK;
     else if (whiteOpt)
         cmdlineP->background = BG_WHITE;
     else if (sidesOpt)
         cmdlineP->background = BG_SIDES;
+    else if (bgColorSpec)
+        cmdlineP->background = BG_COLOR;
+    else if (bgCornerSpec)
+        cmdlineP->background = BG_CORNER;
     else
         cmdlineP->background = BG_DEFAULT;
 
+    if (bgCornerSpec) {
+        if (false) {
+        } else if (streq(bgCornerOpt, "topleft")) {
+            cmdlineP->bgCorner.v = TOP;
+            cmdlineP->bgCorner.h = LEFT;
+        } else if (streq(bgCornerOpt, "topright")) {
+            cmdlineP->bgCorner.v = TOP;
+            cmdlineP->bgCorner.h = RIGHT;
+        } else if (streq(bgCornerOpt, "bottomleft")) {
+            cmdlineP->bgCorner.v = BOTTOM;
+            cmdlineP->bgCorner.h = LEFT;
+        } else if (streq(bgCornerOpt, "bottomright")) {
+            cmdlineP->bgCorner.v = BOTTOM;
+            cmdlineP->bgCorner.h = RIGHT;
+        } else
+            pm_error("Invalid value for -bg-corner."
+                     "Must be one of "
+                     "'topleft', 'topright', 'bottomleft', 'bottomright'");
+    }
+
+    /* Border specification */
+
     if (!leftOpt && !rightOpt && !topOpt && !bottomOpt) {
         unsigned int i;
         for (i = 0; i < 4; ++i)
@@ -142,11 +223,45 @@ parseCommandLine(int argc, const char ** argv,
         cmdlineP->wantCrop[TOP]    = !!topOpt;
         cmdlineP->wantCrop[BOTTOM] = !!bottomOpt;
     }
+
+    /* Blank image handling */
+
+    if (blankModeSpec) {
+        if (false) {
+        } else if (streq(blankModeOpOpt, "abort"))
+            cmdlineP->blankMode = BLANK_ABORT;
+        else if (streq(blankModeOpOpt,   "pass"))
+            cmdlineP->blankMode = BLANK_PASS;
+        else if (streq(blankModeOpOpt,   "minimize"))
+            cmdlineP->blankMode = BLANK_MINIMIZE;
+        else if (streq(blankModeOpOpt,   "maxcrop")) {
+            if (cmdlineP->baseOperation == OP_CROP)
+                pm_error("Option -blank-image=maxcrop requires "
+                         "-reportfull or -reportsize");
+            else
+                cmdlineP->blankMode = BLANK_MAXCROP;
+        } else
+            pm_error ("Invalid value for -blank-image");
+    } else
+        cmdlineP->blankMode = BLANK_ABORT; /* the default */
+
+    /* Other options */
+
     if (!marginSpec)
         cmdlineP->margin = 0;
 
     if (!borderfileSpec)
         cmdlineP->borderfile = NULL;
+
+    if (!closenessSpec)
+        cmdlineP->closeness = 0.0;
+
+    if (cmdlineP->closeness < 0.0)
+        pm_error("-closeness value %f is negative", cmdlineP->closeness);
+
+    if (cmdlineP->closeness > 100.0)
+        pm_error("-closeness value %f is more than 100%%",
+                 cmdlineP->closeness);
 }
 
 
@@ -163,21 +278,21 @@ typedef struct {
         /* Size in pixels of the border to remove */
     unsigned int padSize;
         /* Size in pixels of the border to add */
-} cropOp;
+} CropOp;
 
 
 typedef struct {
-    cropOp op[4];
-} cropSet;
+    CropOp op[4];
+} CropSet;
 
 
 
 static xel
-background3Corners(FILE * const ifP,
-                   int    const rows,
-                   int    const cols,
-                   pixval const maxval,
-                   int    const format) {
+background3Corners(FILE *       const ifP,
+                   unsigned int const rows,
+                   unsigned int const cols,
+                   pixval       const maxval,
+                   int          const format) {
 /*----------------------------------------------------------------------------
   Read in the whole image, and check all the corners to determine the
   background color.  This is a quite reliable way to determine the
@@ -186,14 +301,14 @@ background3Corners(FILE * const ifP,
   Expect the file to be positioned to the start of the raster, and leave
   it positioned arbitrarily.
 ----------------------------------------------------------------------------*/
-    int row;
+    unsigned int row;
     xel ** xels;
     xel background;   /* our return value */
 
     xels = pnm_allocarray(cols, rows);
 
     for (row = 0; row < rows; ++row)
-        pnm_readpnmrow( ifP, xels[row], cols, maxval, format );
+        pnm_readpnmrow(ifP, xels[row], cols, maxval, format);
 
     background = pnm_backgroundxel(xels, cols, rows, maxval, format);
 
@@ -205,10 +320,10 @@ background3Corners(FILE * const ifP,
 
 
 static xel
-background2Corners(FILE * const ifP,
-                   int    const cols,
-                   pixval const maxval,
-                   int    const format) {
+background2Corners(FILE *       const ifP,
+                   unsigned int const cols,
+                   pixval       const maxval,
+                   int          const format) {
 /*----------------------------------------------------------------------------
   Look at just the top row of pixels and determine the background
   color from the top corners; often this is enough to accurately
@@ -217,9 +332,9 @@ background2Corners(FILE * const ifP,
   Expect the file to be positioned to the start of the raster, and leave
   it positioned arbitrarily.
 ----------------------------------------------------------------------------*/
-    xel *xelrow;
+    xel * xelrow;
     xel background;   /* our return value */
-    
+
     xelrow = pnm_allocrow(cols);
 
     pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
@@ -234,24 +349,118 @@ background2Corners(FILE * const ifP,
 
 
 static xel
-computeBackground(FILE *         const ifP,
-                  int            const cols,
-                  int            const rows,
-                  xelval         const maxval,
+background1Corner(FILE *         const ifP,
+                  unsigned int   const rows,
+                  unsigned int   const cols,
+                  pixval         const maxval,
                   int            const format,
-                  enum bg_choice const backgroundChoice) {
+                  CornerLocation const corner) {
+/*----------------------------------------------------------------------------
+  Let the pixel in corner 'corner' be the background.
+
+  Expect the file to be positioned to the start of the raster, and leave
+  it positioned arbitrarily.
+----------------------------------------------------------------------------*/
+    xel * xelrow;
+    xel background;   /* our return value */
+
+    xelrow = pnm_allocrow(cols);
+
+    if (corner.v == BOTTOM) {
+        /* read and discard all but bottom row */
+        unsigned int row;
+
+        for (row = 0; row < rows - 1; ++row)
+            pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
+    }
+    pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
+
+    background = corner.h == LEFT ? xelrow[0] : xelrow[cols - 1];
+
+    pnm_freerow(xelrow);
+
+    return background;
+}
+
+
+
+static xel
+backgroundColorFmName(const char * const colorName,
+                      xelval       const maxval,
+                      int          const format) {
+/*----------------------------------------------------------------------------
+   The color indicated by 'colorName'.
+
+   The return value is based on maxval 'maxval' and format 'format'.
+
+   Abort the program if 'colorName' names a color that cannot be represented
+   in format 'format'.
+
+   Development note: It would be logical to relax the above restriction when
+   -closeness is specified.  Implementation is harder than it seems because of
+   the -margin option.  It is unlikely that there is demand for this feature.
+   If really necessary, the user can convert the input image to PPM.
+-----------------------------------------------------------------------------*/
+    pixel const backgroundColor    =
+        ppm_parsecolor(colorName, maxval);
+
+    pixel const backgroundColorMax =
+        ppm_parsecolor(colorName, PNM_MAXMAXVAL);
+
+    bool const hasColor =
+        !(backgroundColorMax.r == backgroundColorMax.g &&
+          backgroundColorMax.r == backgroundColorMax.b);
+
+    bool const hasGray  =
+        !hasColor &&
+        (backgroundColorMax.r != PNM_MAXMAXVAL &&
+         backgroundColorMax.r !=  0 );
+
+    xel backgroundXel;
+
+    if (PPM_FORMAT_TYPE(format)) {
+        backgroundXel = pnm_pixeltoxel(backgroundColor);
+    } else {
+        /* Derive PBM or PGM xel from pixel 'backgroundColor' */
+        if (hasColor)
+            pm_error("Invalid color specified: '%s'. "
+                     "Image does not have color.", colorName);
+        else {
+            if (PBM_FORMAT_TYPE(format) == PBM_TYPE && hasGray)
+                pm_error("Invalid color specified: '%s'. "
+                         "Image has no intermediate levels of gray.",
+                         colorName);
+            else
+                PNM_ASSIGN1(backgroundXel, backgroundColor.r);
+        }
+    }
+
+    return backgroundXel;
+}
+
+
+
+static xel
+backgroundColor(FILE *         const ifP,
+                int            const cols,
+                int            const rows,
+                xelval         const maxval,
+                int            const format,
+                enum BgChoice  const backgroundChoice,
+                CornerLocation const corner,
+                const char   * const colorName) {
 /*----------------------------------------------------------------------------
    Determine what color is the background color of the image in file
    *ifP, which is described by 'cols', 'rows', 'maxval', and 'format'.
 
    'backgroundChoice' is the method we are to use in determining the
    background color.
-   
+
    Expect the file to be positioned to the start of the raster, and leave
    it positioned arbitrarily.
 -----------------------------------------------------------------------------*/
     xel background;  /* Our return value */
-    
+
     switch (backgroundChoice) {
     case BG_WHITE:
         background = pnm_whitexel(maxval, format);
@@ -259,17 +468,45 @@ computeBackground(FILE *         const ifP,
     case BG_BLACK:
         background = pnm_blackxel(maxval, format);
         break;
-    case BG_SIDES: 
-        background = 
+    case BG_COLOR:
+        background =
+            backgroundColorFmName(colorName, maxval, format);
+        break;
+    case BG_SIDES:
+        background =
             background3Corners(ifP, rows, cols, maxval, format);
         break;
-    case BG_DEFAULT: 
-        background = 
+    case BG_DEFAULT:
+        background =
             background2Corners(ifP, cols, maxval, format);
         break;
+    case BG_CORNER:
+        background =
+            background1Corner(ifP, rows, cols, maxval, format, corner);
+        break;
+
+    default:
+        pm_error("internal error");
     }
 
-    return(background);
+    return background;
+}
+
+
+
+static bool
+colorMatches(pixel        const comparand,
+             pixel        const comparator,
+             unsigned int const allowableDiff) {
+/*----------------------------------------------------------------------------
+   The colors 'comparand' and 'comparator' are within 'allowableDiff'
+   color levels of each other, in cartesian distance.
+-----------------------------------------------------------------------------*/
+    /* Fast path for usual case */
+    if (allowableDiff < EPSILON)
+        return PPM_EQUAL(comparand, comparator);
+
+    return PPM_DISTANCE(comparand, comparator) <= SQR(allowableDiff);
 }
 
 
@@ -281,62 +518,66 @@ findBordersInImage(FILE *         const ifP,
                    xelval         const maxval,
                    int            const format,
                    xel            const backgroundColor,
+                   double         const closeness,
                    bool *         const hasBordersP,
                    borderSet *    const borderSizeP) {
 /*----------------------------------------------------------------------------
    Find the left, right, top, and bottom borders in the image 'ifP'.
    Return their sizes in pixels as borderSize[n].
-   
+
    Iff the image is all background, *hasBordersP == FALSE.
 
    Expect the input file to be positioned to the beginning of the
    image raster and leave it positioned arbitrarily.
 -----------------------------------------------------------------------------*/
+    unsigned int const allowableDiff = ROUNDU(sqrt3 * maxval * closeness/100);
+
     xel * xelrow;        /* A row of the input image */
     int row;
-    bool gottop;
+    bool gotTop;
     int left, right, bottom, top;
         /* leftmost, etc. nonbackground pixel found so far; -1 for none */
 
     xelrow = pnm_allocrow(cols);
-    
+
     left   = cols;  /* initial value */
     right  = -1;    /* initial value */
     top    = rows;  /* initial value */
     bottom = -1;    /* initial value */
 
-    gottop = FALSE;
-    for (row = 0; row < rows; ++row) {
+    for (row = 0, gotTop = false; row < rows; ++row) {
         int col;
         int thisRowLeft;
         int thisRowRight;
 
         pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
-        
+
         col = 0;
-        while (col < cols && PNM_EQUAL(xelrow[col], backgroundColor))
+        while (col < cols &&
+               colorMatches(xelrow[col], backgroundColor, allowableDiff))
             ++col;
         thisRowLeft = col;
 
         col = cols-1;
-        while (col >= thisRowLeft && PNM_EQUAL(xelrow[col], backgroundColor))
+        while (col >= thisRowLeft &&
+               colorMatches(xelrow[col], backgroundColor, allowableDiff))
             --col;
         thisRowRight = col + 1;
 
         if (thisRowLeft < cols) {
             /* This row is not entirely background */
-            
+
             left  = MIN(thisRowLeft,  left);
             right = MAX(thisRowRight, right);
 
-            if (!gottop) {
-                gottop = TRUE;
+            if (!gotTop) {
                 top = row;
+                gotTop = true;
             }
             bottom = row + 1;   /* New candidate */
         }
     }
-    
+
     free(xelrow);
 
     if (right == -1)
@@ -359,7 +600,10 @@ analyzeImage(FILE *         const ifP,
              unsigned int   const rows,
              xelval         const maxval,
              int            const format,
-             enum bg_choice const backgroundReq,
+             enum BgChoice  const backgroundReq,
+             double         const closeness,
+             CornerLocation const corner,
+             const char   * const colorName,
              imageFilePos   const newFilePos,
              xel *          const backgroundColorP,
              bool *         const hasBordersP,
@@ -383,13 +627,13 @@ analyzeImage(FILE *         const ifP,
 
     pm_tell2(ifP, &rasterpos, sizeof(rasterpos));
 
-    background = computeBackground(ifP, cols, rows, maxval, format,
-                                   backgroundReq);
+    background = backgroundColor(ifP, cols, rows, maxval, format,
+                                 backgroundReq, corner, colorName);
 
     pm_seek2(ifP, &rasterpos, sizeof(rasterpos));
 
-    findBordersInImage(ifP, cols, rows, maxval, format, 
-                       background, hasBordersP, borderSizeP);
+    findBordersInImage(ifP, cols, rows, maxval, format,
+                       background, closeness, hasBordersP, borderSizeP);
 
     if (newFilePos == FILEPOS_BEG)
         pm_seek2(ifP, &rasterpos, sizeof(rasterpos));
@@ -408,7 +652,7 @@ ending(unsigned int const n) {
 
 
 static void
-reportCroppingParameters(cropSet const crop) {
+reportCroppingParameters(CropSet const crop) {
 
     unsigned int i;
 
@@ -431,6 +675,85 @@ reportCroppingParameters(cropSet const crop) {
 
 
 
+static void
+reportDimensions(CropSet      const crop,
+                 unsigned int const cols,
+                 unsigned int const rows) {
+
+    unsigned int i;
+
+    for (i = 0; i < ARRAY_SIZE(crop.op); ++i) {
+        if (crop.op[i].removeSize > 0 && crop.op[i].padSize > 0)
+            pm_error("Attempt to add %u and crop %u on %s edge.  "
+                     "Simultaneous pad and crop is not allowed",
+                     crop.op[i].padSize, crop.op[i].removeSize, edgeName[i]);
+        else if (crop.op[i].removeSize > 0)   /* crop */
+            printf ("-%u ", crop.op[i].removeSize);
+        else if (crop.op[i].removeSize == 0) {
+            if (crop.op[i].padSize == 0)      /* no operation */
+                printf ("0 ");
+            else                              /* pad */
+                printf ("+%u ", crop.op[i].padSize);
+        }
+    }
+
+    {
+        unsigned int outputCols, outputRows;
+
+        if (crop.op[LEFT ].removeSize == cols ||
+            crop.op[RIGHT].removeSize == cols)
+            outputCols = cols;
+        else {
+            outputCols =
+                cols - crop.op[LEFT].removeSize - crop.op[RIGHT].removeSize +
+                crop.op[LEFT].padSize + crop.op[RIGHT].padSize;
+        }
+
+        if (crop.op[TOP   ].removeSize == rows ||
+            crop.op[BOTTOM].removeSize == rows)
+            outputRows = rows;
+        else
+            outputRows =
+                rows - crop.op[TOP].removeSize - crop.op[BOTTOM].removeSize +
+                crop.op[TOP].padSize + crop.op[BOTTOM].padSize;
+
+        printf("%u %u", outputCols, outputRows);
+    }
+}
+
+
+static void
+reportSize(CropSet      const crop,
+           unsigned int const cols,
+           unsigned int const rows) {
+
+    reportDimensions(crop, cols, rows);
+
+    putchar('\n');
+
+}
+
+
+
+static void
+reportFull(CropSet      const crop,
+           unsigned int const cols,
+           unsigned int const rows,
+           int          const format,
+           xelval       const maxval,
+           xel          const bgColor,
+           float        const closeness) {
+
+    pixel const backgroundPixel = pnm_xeltopixel(bgColor, format);
+
+    reportDimensions(crop, cols, rows);
+
+    printf(" rgb-%u:%u/%u/%u %f\n", maxval,
+           backgroundPixel.r, backgroundPixel.g, backgroundPixel.b,
+           closeness);
+}
+
+
 
 static void
 fillRow(xel *        const xelrow,
@@ -485,7 +808,7 @@ outputNewBorderNonPbm(unsigned int const height,
 
     for (i = 0; i < height; ++i)
         pnm_writepnmrow(ofP, xelrow, width, maxval, format, 0);
-    
+
     pnm_freerow(xelrow);
 }
 
@@ -497,7 +820,7 @@ writeCroppedNonPbm(FILE *       const ifP,
                    unsigned int const rows,
                    xelval       const maxval,
                    int          const format,
-                   cropSet      const crop,
+                   CropSet      const crop,
                    xel          const backgroundColor,
                    FILE *       const ofP) {
 
@@ -529,7 +852,7 @@ writeCroppedNonPbm(FILE *       const ifP,
        the buffer that lines up the first foreground pixel at
        'foregroundLeft'.
 
-       When we output the row, we pick a starting location in the 
+       When we output the row, we pick a starting location in the
        buffer that includes the proper number of left border pixels
        before 'foregroundLeft'.
 
@@ -542,7 +865,7 @@ writeCroppedNonPbm(FILE *       const ifP,
 
     unsigned int const foregroundCols =
         cols - crop.op[LEFT].removeSize - crop.op[RIGHT].removeSize;
-    unsigned int const outputCols     = 
+    unsigned int const outputCols     =
         foregroundCols + crop.op[LEFT].padSize + crop.op[RIGHT].padSize;
     unsigned int const foregroundRows =
         rows - crop.op[TOP].removeSize - crop.op[BOTTOM].removeSize;
@@ -581,19 +904,19 @@ writeCroppedNonPbm(FILE *       const ifP,
 
     /* Read and output foreground rows */
     for (i = 0; i < foregroundRows; ++i) {
- 
+
         /* Read foreground pixels */
         pnm_readpnmrow(ifP,
                        &(xelrow[foregroundLeft - crop.op[LEFT].removeSize]),
                        cols, maxval, format);
-        
+
         pnm_writepnmrow(ofP,
                         &(xelrow[foregroundLeft - crop.op[LEFT].padSize]),
                         outputCols, maxval, format, 0);
     }
 
     readOffBorderNonPbm(crop.op[BOTTOM].removeSize, ifP, cols, maxval, format);
-    
+
     outputNewBorderNonPbm(crop.op[BOTTOM].padSize, outputCols,
                           backgroundColor,
                           ofP, maxval, format);
@@ -616,10 +939,10 @@ fillRowPBM(unsigned char * const bitrow,
     unsigned int i;
 
     assert(blackWhite == 0 || blackWhite == 1);
-    
+
     for (i = 0; i < colChars; ++i)
         bitrow[i] = blackWhite * 0xff;
-        
+
     if (cols % 8 > 0)
         bitrow[colChars-1] <<= 8 - cols % 8;
 }
@@ -664,7 +987,7 @@ outputNewBorderPbm(unsigned int const height,
 
     for (i = 0; i < height; ++i)
         pbm_writepbmrow_packed(ofP, bitrow, width, 0);
-    
+
     pbm_freerow_packed(bitrow);
 }
 
@@ -675,17 +998,17 @@ writeCroppedPBM(FILE *       const ifP,
                 unsigned int const cols,
                 unsigned int const rows,
                 int          const format,
-                cropSet      const crop,
+                CropSet      const crop,
                 xel          const backgroundColor,
                 FILE *       const ofP) {
-    
-    /* See comments for writeCroppedNonPBM(), which uses identical logic flow. 
+
+    /* See comments for writeCroppedNonPBM(), which uses identical logic flow.
        Uses pbm functions instead of general pnm functions.
     */
 
     unsigned int const foregroundCols =
         cols - crop.op[LEFT].removeSize - crop.op[RIGHT].removeSize;
-    unsigned int const outputCols     = 
+    unsigned int const outputCols     =
         foregroundCols + crop.op[LEFT].padSize + crop.op[RIGHT].padSize;
     unsigned int const foregroundRows =
         rows - crop.op[TOP].removeSize - crop.op[BOTTOM].removeSize;
@@ -697,7 +1020,7 @@ writeCroppedPBM(FILE *       const ifP,
     unsigned int const foregroundRight = foregroundLeft + foregroundCols;
 
     unsigned int const allocCols =
-        foregroundRight + 
+        foregroundRight +
         MAX(crop.op[RIGHT].removeSize, crop.op[RIGHT].padSize);
 
     unsigned int const backgroundBlackWhite =
@@ -709,7 +1032,7 @@ writeCroppedPBM(FILE *       const ifP,
     unsigned int const lastWriteChar = writeOffset/8 + (outputCols-1)/8;
     unsigned char * bitrow;
     unsigned int i;
-    
+
     pbm_writepbminit(ofP, outputCols, outputRows, 0);
 
     bitrow = pbm_allocrow_packed(allocCols);
@@ -726,15 +1049,15 @@ writeCroppedPBM(FILE *       const ifP,
     for (i = 0; i < foregroundRows; ++i) {
         /* Read foreground pixels */
         pbm_readpbmrow_bitoffset(ifP, bitrow, cols, format, readOffset);
-  
+
         pbm_writepbmrow_bitoffset(ofP,
                                   bitrow, outputCols, format, writeOffset);
-                              
+
         /* If there is right-side padding, repair the write buffer
-           distorted by pbm_writepbmrow_bitoffset() 
+           distorted by pbm_writepbmrow_bitoffset()
            (No need to mend any left-side padding)
         */
-        if (crop.op[RIGHT].padSize > 0)    
+        if (crop.op[RIGHT].padSize > 0)
             bitrow[lastWriteChar] = backgroundBlackWhite * 0xff;
     }
 
@@ -749,29 +1072,168 @@ writeCroppedPBM(FILE *       const ifP,
 
 
 
-static void
-determineCrops(struct cmdlineInfo const cmdline,
-               borderSet *        const oldBorderSizeP,
-               cropSet *          const cropP) {
+static CropSet
+crops(struct CmdlineInfo const cmdline,
+      borderSet          const oldBorderSize) {
 
-    edgeLocation i;
+    CropSet retval;
 
-    for (i = 0; i < 4; ++i) {
+    EdgeLocation i;
+
+    for (i = 0; i < ARRAY_SIZE(retval.op); ++i) {
         if (cmdline.wantCrop[i]) {
-            if (oldBorderSizeP->size[i] > cmdline.margin) {
-                cropP->op[i].removeSize =
-                    oldBorderSizeP->size[i] - cmdline.margin;
-                cropP->op[i].padSize    = 0;
+            if (oldBorderSize.size[i] > cmdline.margin) {
+                retval.op[i].removeSize =
+                    oldBorderSize.size[i] - cmdline.margin;
+                retval.op[i].padSize    = 0;
             } else {
-                cropP->op[i].removeSize = 0;
-                cropP->op[i].padSize    =
-                    cmdline.margin - oldBorderSizeP->size[i];
+                retval.op[i].removeSize = 0;
+                retval.op[i].padSize    =
+                    cmdline.margin - oldBorderSize.size[i];
             }
         } else {
-            cropP->op[i].removeSize = 0;
-            cropP->op[i].padSize    = 0;
+            retval.op[i].removeSize = 0;
+            retval.op[i].padSize    = 0;
         }
     }
+    return retval;
+}
+
+
+
+static CropSet
+noCrops(struct CmdlineInfo const cmdline) {
+
+    CropSet retval;
+
+    EdgeLocation i;
+
+    if (cmdline.verbose)
+        pm_message("The image is entirely background; "
+                   "there is nothing to crop.  Copying to output.");
+
+    if (cmdline.margin > 0)
+        pm_message ("-margin value %u ignored", cmdline.margin);
+
+    for (i = 0; i < 4; ++i) {
+        retval.op[i].removeSize = 0;
+        retval.op[i].padSize    = 0;
+    }
+    return retval;
+}
+
+
+
+static CropSet
+extremeCrops(struct CmdlineInfo const cmdline,
+             unsigned int       const cols,
+             unsigned int       const rows) {
+/*----------------------------------------------------------------------------
+   Crops that crop as much as possible, reducing output to a single pixel.
+-----------------------------------------------------------------------------*/
+    CropSet retval;
+
+    if (cmdline.verbose)
+        pm_message("Input image has no distinction between "
+                   "border and content");
+
+    /* We can't just pick a representive pixel, say top-left corner.
+       If -top and/or -bottom was specified but not -left and -right,
+       the output should be one row, not a single pixel.
+
+       The "entirely background" image may have several colors: this
+       happens when -closeness was specified.
+    */
+
+    if (cmdline.wantCrop[LEFT] && cmdline.wantCrop[RIGHT]) {
+        retval.op[LEFT ].removeSize = cols / 2;
+        retval.op[RIGHT].removeSize = cols - retval.op[LEFT].removeSize -1;
+    } else if (cmdline.wantCrop[LEFT]) {
+        retval.op[LEFT ].removeSize = cols - 1;
+        retval.op[RIGHT].removeSize = 0;
+    } else if (cmdline.wantCrop[RIGHT]) {
+        retval.op[LEFT ].removeSize = 0;
+        retval.op[RIGHT].removeSize = cols - 1;
+    } else {
+        retval.op[LEFT ].removeSize = 0;
+        retval.op[RIGHT].removeSize = 0;
+    }
+
+    if (cmdline.wantCrop[TOP] && cmdline.wantCrop[BOTTOM]) {
+        retval.op[ TOP  ].removeSize = rows / 2;
+        retval.op[BOTTOM].removeSize = rows - retval.op[TOP].removeSize -1;
+    } else if (cmdline.wantCrop[TOP]) {
+        retval.op[ TOP  ].removeSize = rows - 1;
+        retval.op[BOTTOM].removeSize = 0;
+    } else if (cmdline.wantCrop[BOTTOM]) {
+        retval.op[ TOP  ].removeSize = 0;
+        retval.op[BOTTOM].removeSize = rows - 1;
+    } else {
+        retval.op[ TOP  ].removeSize = 0;
+        retval.op[BOTTOM].removeSize = 0;
+    }
+
+    if (cmdline.margin > 0)
+        pm_message ("-margin value %u ignored", cmdline.margin);
+
+    {
+        EdgeLocation i;
+        for (i = 0; i < ARRAY_SIZE(retval.op); ++i)
+            retval.op[i].padSize = 0;
+    }
+    return retval;
+}
+
+
+
+static CropSet
+maxcropReport(struct CmdlineInfo const cmdline,
+              unsigned int       const cols,
+              unsigned int       const rows) {
+/*----------------------------------------------------------------------------
+   Report maximum possible crop extents.
+-----------------------------------------------------------------------------*/
+    CropSet retval;
+
+    if (cmdline.wantCrop[LEFT] && cmdline.wantCrop[RIGHT]) {
+        retval.op[LEFT ].removeSize = cols;
+        retval.op[RIGHT].removeSize = cols;
+    } else if (cmdline.wantCrop[LEFT]) {
+        retval.op[LEFT ].removeSize = cols;
+        retval.op[RIGHT].removeSize = 0;
+    } else if (cmdline.wantCrop[RIGHT]) {
+        retval.op[LEFT ].removeSize = 0;
+        retval.op[RIGHT].removeSize = cols;
+    } else {
+        retval.op[LEFT ].removeSize = 0;
+        retval.op[RIGHT].removeSize = 0;
+    }
+
+    if (cmdline.wantCrop[TOP] && cmdline.wantCrop[BOTTOM]) {
+        retval.op[ TOP  ].removeSize = rows;
+        retval.op[BOTTOM].removeSize = rows;
+    } else if (cmdline.wantCrop[TOP]) {
+        retval.op[ TOP  ].removeSize = rows;
+        retval.op[BOTTOM].removeSize = 0;
+    } else if (cmdline.wantCrop[BOTTOM]) {
+        retval.op[ TOP  ].removeSize = 0;
+        retval.op[BOTTOM].removeSize = rows;
+    } else {
+        retval.op[ TOP  ].removeSize = 0;
+        retval.op[BOTTOM].removeSize = 0;
+    }
+
+
+    if (cmdline.margin > 0)
+        pm_message("-margin value %u ignored", cmdline.margin);
+
+    {
+        EdgeLocation i;
+
+        for (i = 0; i < ARRAY_SIZE(retval.op); ++i)
+            retval.op[i].padSize = 0;
+    }
+    return retval;
 }
 
 
@@ -779,7 +1241,7 @@ determineCrops(struct cmdlineInfo const cmdline,
 static void
 validateComputableSize(unsigned int const cols,
                        unsigned int const rows,
-                       cropSet      const crop) {
+                       CropSet      const crop) {
 
     double const newcols =
         (double)cols +
@@ -798,7 +1260,7 @@ validateComputableSize(unsigned int const cols,
 
 
 static void
-cropOneImage(struct cmdlineInfo const cmdline,
+cropOneImage(struct CmdlineInfo const cmdline,
              FILE *             const ifP,
              FILE *             const bdfP,
              FILE *             const ofP) {
@@ -811,51 +1273,95 @@ cropOneImage(struct cmdlineInfo const cmdline,
 
    Both files are seekable.
 -----------------------------------------------------------------------------*/
-    xelval maxval, bmaxval;
-    int format, bformat;
-    int rows, cols, brows, bcols;
+    int rows, cols, format;
+    xelval maxval;      /* The input file image */
+
+    int brows, bcols, bformat;
+    xelval bmaxval;     /* The separate border file, if specified */
+
+    FILE * afP;
+    int arows, acols, aformat;
+    xelval amaxval;
+    /* The file we use for analysis, either the input file or border file */
+
     bool hasBorders;
     borderSet oldBorder;
         /* The sizes of the borders in the input image */
-    cropSet crop;
+    CropSet crop;
         /* The crops we have to do on each side */
     xel background;
 
     pnm_readpnminit(ifP, &cols, &rows, &maxval, &format);
 
-    if (bdfP)
+    if (bdfP) {
         pnm_readpnminit(bdfP, &bcols, &brows, &bmaxval, &bformat);
 
-    if (bdfP)
-        analyzeImage(bdfP, bcols, brows, bmaxval, bformat, cmdline.background,
-                     FILEPOS_END,
-                     &background, &hasBorders, &oldBorder);
-    else
-        analyzeImage(ifP, cols, rows, maxval, format, cmdline.background,
-                     FILEPOS_BEG,
-                     &background, &hasBorders, &oldBorder);
+        if (cols != bcols || rows != brows)
+            pm_error("Input file image [%u x %u] and border file image "
+                     "[%u x %u] differ in size", cols, rows, bcols, brows);
+        else {
+            afP = bdfP;
+            acols = bcols; arows = brows;
+            amaxval = maxval;
+            aformat = bformat;
+        }
+    } else {
+        afP = ifP;
+        acols = cols; arows = rows;
+        amaxval = maxval;
+        aformat = format;
+    }
+
+    analyzeImage(afP, acols, arows, amaxval, aformat,
+                 cmdline.background, cmdline.closeness,
+                 cmdline.bgCorner, cmdline.bgColor,
+                 (bdfP || cmdline.baseOperation != OP_CROP) ?
+                     FILEPOS_END : FILEPOS_BEG,
+                 &background, &hasBorders, &oldBorder);
 
     if (cmdline.verbose) {
         pixel const backgroundPixel = pnm_xeltopixel(background, format);
-        pm_message("Background color is %s", 
+        pm_message("Background color is %s",
                    ppm_colorname(&backgroundPixel, maxval, TRUE /*hexok*/));
     }
-    if (!hasBorders)
-        pm_error("The image is entirely background; "
-                 "there is nothing to crop.");
-
-    determineCrops(cmdline, &oldBorder, &crop);
+    if (!hasBorders) {
+        switch (cmdline.blankMode) {
+        case BLANK_ABORT:
+            pm_error("The image is entirely background; "
+                     "there is nothing to crop.");
+            break;
+        case BLANK_PASS:
+            crop = noCrops(cmdline);                   break;
+        case BLANK_MINIMIZE:
+            crop = extremeCrops(cmdline, cols, rows);  break;
+        case BLANK_MAXCROP:
+            crop = maxcropReport(cmdline, cols, rows); break;
+        }
+    } else {
+        crop = crops(cmdline, oldBorder);
 
-    validateComputableSize(cols, rows, crop);
+        validateComputableSize(cols, rows, crop);
 
-    if (cmdline.verbose) 
-        reportCroppingParameters(crop);
+        if (cmdline.verbose)
+            reportCroppingParameters(crop);
+    }
 
-    if (PNM_FORMAT_TYPE(format) == PBM_TYPE)
-        writeCroppedPBM(ifP, cols, rows, format, crop, background, ofP);
-    else
-        writeCroppedNonPbm(ifP, cols, rows, maxval, format, crop,
-                           background, ofP);
+    switch (cmdline.baseOperation) {
+    case OP_CROP:
+        if (PNM_FORMAT_TYPE(format) == PBM_TYPE)
+            writeCroppedPBM(ifP, cols, rows, format, crop, background, ofP);
+        else
+            writeCroppedNonPbm(ifP, cols, rows, maxval, format, crop,
+                               background, ofP);
+        break;
+    case OP_REPORT_FULL:
+        reportFull(crop, cols, rows,
+                   aformat, amaxval, background, cmdline.closeness);
+        break;
+    case OP_REPORT_SIZE:
+        reportSize(crop, cols, rows);
+        break;
+    }
 }
 
 
@@ -863,8 +1369,8 @@ cropOneImage(struct cmdlineInfo const cmdline,
 int
 main(int argc, const char *argv[]) {
 
-    struct cmdlineInfo cmdline;
-    FILE * ifP;   
+    struct CmdlineInfo cmdline;
+    FILE * ifP;
         /* The program's regular input file.  Could be a seekable copy of
            it in a temporary file.
         */
@@ -884,18 +1390,17 @@ main(int argc, const char *argv[]) {
     else
         bdfP = NULL;
 
-    eof = beof = FALSE;
-    while (!eof) {
+    for (eof = beof = FALSE; !eof; ) {
         cropOneImage(cmdline, ifP, bdfP, stdout);
 
         pnm_nextimage(ifP, &eof);
 
         if (bdfP) {
             pnm_nextimage(bdfP, &beof);
-            
+
             if (eof != beof) {
                 if (!eof)
-                    pm_error("Input file has more images than border file."); 
+                    pm_error("Input file has more images than border file.");
                 else
                     pm_error("Border file has more images than image file.");
             }
diff --git a/editor/pnmhisteq.c b/editor/pnmhisteq.c
index 8af42019..a339f73f 100644
--- a/editor/pnmhisteq.c
+++ b/editor/pnmhisteq.c
@@ -51,9 +51,9 @@ parseCommandLine(int argc, char ** argv,
     MALLOCARRAY_NOFAIL(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENT3 */
-    OPTENT3(0, "rmap",     OPT_STRING, &cmdlineP->rmap, 
+    OPTENT3(0, "rmap",     OPT_STRING, &cmdlineP->rmap,
             &rmapSpec,          0);
-    OPTENT3(0, "wmap",     OPT_STRING, &cmdlineP->wmap, 
+    OPTENT3(0, "wmap",     OPT_STRING, &cmdlineP->wmap,
             &wmapSpec,          0);
     OPTENT3(0, "gray",     OPT_FLAG,   NULL,
             &cmdlineP->gray,    0);
@@ -175,27 +175,27 @@ readMapFile(const char * const rmapFileName,
             xelval       const maxval,
             gray *       const lumamap) {
 
-    int rmcols, rmrows; 
+    int rmcols, rmrows;
     gray rmmaxv;
     int rmformat;
     FILE * rmapfP;
-        
+
     rmapfP = pm_openr(rmapFileName);
     pgm_readpgminit(rmapfP, &rmcols, &rmrows, &rmmaxv, &rmformat);
-    
+
     if (rmmaxv != maxval)
         pm_error("maxval in map file (%u) different from input (%u)",
                  rmmaxv, maxval);
-    
+
     if (rmrows != 1)
         pm_error("Map must have 1 row.  Yours has %u", rmrows);
-    
+
     if (rmcols != maxval + 1)
         pm_error("Map must have maxval + 1 (%u) columns.  Yours has %u",
                  maxval + 1, rmcols);
-    
+
     pgm_readpgmrow(rmapfP, lumamap, maxval+1, rmmaxv, rmformat);
-    
+
     pm_close(rmapfP);
 }
 
@@ -245,7 +245,7 @@ equalize(const unsigned int * const lumahist,
         maxLumaPresent(lumahist, darkestRemap, brightestRemap);
 
     unsigned int const range = brightestRemap - darkestRemap;
-    
+
     {
         xelval origLum;
         unsigned int pixsum;
@@ -253,7 +253,7 @@ equalize(const unsigned int * const lumahist,
         for (origLum = darkestRemap, pixsum = 0;
              origLum <= brightestRemap;
              ++origLum) {
-            
+
             /* With 16 bit grays, the following calculation can overflow a 32
                bit long.  So, we do it in floating point.
             */
@@ -261,7 +261,7 @@ equalize(const unsigned int * const lumahist,
             lumamap[origLum] =
                 darkestRemap +
                 ROUNDU((((double) pixsum * range)) / remapPixelCount);
-            
+
             pixsum += lumahist[origLum];
         }
 
@@ -277,7 +277,7 @@ equalize(const unsigned int * const lumahist,
 
         for (origLum = darkestRemap; origLum <= brightestRemap; ++origLum)
             lumamap[origLum] =
-                MIN(brightestRemap, 
+                MIN(brightestRemap,
                     darkestRemap + ROUNDU(lumamap[origLum] * lscale));
     }
 }
@@ -301,7 +301,7 @@ computeMap(const unsigned int * const lumahist,
 
   'pixelCount' is the number of pixels in the image, which is redundant
   with 'lumahist' but provided for computational convenience.
-   
+
   'noblack' means don't include the black pixels in the equalization and
   make the black pixels in the output the same ones as in the input.
 
@@ -407,7 +407,7 @@ scaleXel(xel    const thisXel,
                ((xelval)(PNM_GETR(thisXel) * scaler + 0.5)),
                ((xelval)(PNM_GETG(thisXel) * scaler + 0.5)),
                ((xelval)(PNM_GETB(thisXel) * scaler + 0.5)));
-    
+
     return retval;
 }
 
@@ -426,10 +426,10 @@ remapRgbValue(xel          const thisXel,
     struct hsv const hsv =
         ppm_hsv_from_color(thisXel, maxval);
     xelval const oldValue =
-        MIN(maxval, ROUNDU(hsv.v * maxval));
+        MIN(maxval, pnm_unnormalize(hsv.v, maxval));
     xelval const newValue =
         lumamap[oldValue];
-    
+
     return scaleXel(thisXel, (double)newValue/oldValue);
 }
 
diff --git a/editor/pnminvert.c b/editor/pnminvert.c
index 4bee8837..d4aad503 100644
--- a/editor/pnminvert.c
+++ b/editor/pnminvert.c
@@ -16,8 +16,8 @@
    implements the for statements in our algorithm with instructions that do 16
    bytes at a time on CPUs that have them (movdqa on x86).  This is "tree
    vectorization."  A more primitive compiler will do one byte at a time; we
-   could change the code to use libnetpbm's wordaccess.h facility and it will
-   do one word at a time.  (But we don't think it's worth complicating the
+   could change the code to use uint32_t or uint64_t and it will do four or
+   eight bytes at a time.  (But we don't think it's worth complicating the
    code for that).
 */
 
diff --git a/editor/pnmmargin b/editor/pnmmargin
index 46cedbb5..6e70aba3 100755
--- a/editor/pnmmargin
+++ b/editor/pnmmargin
@@ -11,9 +11,8 @@
 # documentation.  This software is provided "as is" without express or
 # implied warranty.
 
-tempdir="${TMPDIR-/tmp}/pnmmargin.$$"
-mkdir -m 0700 $tempdir || \
-  { echo "Could not create temporary file. Exiting." 1>&2; exit 1;}
+tempdir=$(mktemp -d "${TMPDIR:-/tmp}/netpbm.XXXXXXXX") ||
+    { echo "Could not create temporary file. Exiting." 1>&2; exit 1; }
 trap 'rm -rf $tempdir' 0 1 3 15
 
 tmp1=$tempdir/pnmm1
@@ -27,6 +26,9 @@ plainopt=""
 # Parse args.
 while true ; do
     case "$1" in
+        -version|--version )
+        pnmpad --version; exit $?;
+        ;;
         -p|-pl|-pla|-plai|-plain )
         plainopt="-plain"
         shift
diff --git a/editor/pnmmontage.c b/editor/pnmmontage.c
index e54afc45..7bd00dbe 100644
--- a/editor/pnmmontage.c
+++ b/editor/pnmmontage.c
@@ -10,6 +10,7 @@
  * implied warranty.
  */
 
+#define _DEFAULT_SOURCE /* New name for SVID & BSD source defines */
 #define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
 #define _BSD_SOURCE  /* Make sure strdup() is in <string.h> */
 #include <assert.h>
diff --git a/editor/pnmnorm.c b/editor/pnmnorm.c
index a3341eed..3a181bf3 100644
--- a/editor/pnmnorm.c
+++ b/editor/pnmnorm.c
@@ -9,20 +9,20 @@
 
   Ppmnorm is by Wilson H. Bent, Jr. (whb@usc.edu)
   Extensively hacked from pgmnorm.c, which carries the following note:
-  
+
   Copyright (C) 1989, 1991 by Jef Poskanzer.
-  
+
   Permission to use, copy, modify, and distribute this software and its
   documentation for any purpose and without fee is hereby granted, provided
   that the above copyright notice appear in all copies and that both that
   copyright notice and this permission notice appear in supporting
   documentation.  This software is provided "as is" without express or
   implied warranty.
-  
+
   (End of note from pgmnorm.c)
 
   Pgmnorm's man page also said:
-  
+
   Partially based on the fbnorm filter in Michael Mauldin's "Fuzzy Pixmap"
   package.
 *****************************************************************************/
@@ -73,7 +73,7 @@ parseCommandLine(int argc, const char ** argv,
                  struct cmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    parse program command line described in Unix standard form by argc
-   and argv.  Return the information in the options as *cmdlineP.  
+   and argv.  Return the information in the options as *cmdlineP.
 
    If command line is internally inconsistent (invalid options, etc.),
    issue error message to stderr and abort program.
@@ -89,41 +89,41 @@ parseCommandLine(int argc, const char ** argv,
     unsigned int luminosity, colorvalue, saturation;
     unsigned int middleSpec, maxexpandSpec;
     float maxexpand;
-    
+
     unsigned int option_def_index;
 
     MALLOCARRAY_NOFAIL(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENT3 */
-    OPTENT3(0,   "bpercent",      OPT_FLOAT,   
+    OPTENT3(0,   "bpercent",      OPT_FLOAT,
             &cmdlineP->bpercent,   &cmdlineP->bpercentSpec, 0);
-    OPTENT3(0,   "wpercent",      OPT_FLOAT,   
+    OPTENT3(0,   "wpercent",      OPT_FLOAT,
             &cmdlineP->wpercent,   &cmdlineP->wpercentSpec, 0);
-    OPTENT3(0,   "bvalue",        OPT_UINT,   
+    OPTENT3(0,   "bvalue",        OPT_UINT,
             &cmdlineP->bvalue,     &cmdlineP->bvalueSpec, 0);
-    OPTENT3(0,   "wvalue",        OPT_UINT,   
+    OPTENT3(0,   "wvalue",        OPT_UINT,
             &cmdlineP->wvalue,     &cmdlineP->wvalueSpec, 0);
-    OPTENT3(0,   "bsingle",       OPT_FLAG,   
+    OPTENT3(0,   "bsingle",       OPT_FLAG,
             NULL,                 &cmdlineP->bsingle, 0);
-    OPTENT3(0,   "wsingle",       OPT_FLAG,   
+    OPTENT3(0,   "wsingle",       OPT_FLAG,
             NULL,                 &cmdlineP->wsingle, 0);
-    OPTENT3(0,   "middle",        OPT_FLOAT,   
+    OPTENT3(0,   "middle",        OPT_FLOAT,
             &cmdlineP->middle,     &middleSpec, 0);
-    OPTENT3(0,   "midvalue",      OPT_UINT,   
+    OPTENT3(0,   "midvalue",      OPT_UINT,
             &cmdlineP->midvalue,   &cmdlineP->midvalueSpec, 0);
-    OPTENT3(0,   "maxexpand",     OPT_FLOAT,   
+    OPTENT3(0,   "maxexpand",     OPT_FLOAT,
             &maxexpand,            &maxexpandSpec, 0);
-    OPTENT3(0,   "keephues",      OPT_FLAG,   
+    OPTENT3(0,   "keephues",      OPT_FLAG,
             NULL,                  &cmdlineP->keephues, 0);
-    OPTENT3(0,   "luminosity",    OPT_FLAG,   
+    OPTENT3(0,   "luminosity",    OPT_FLAG,
             NULL,                  &luminosity, 0);
-    OPTENT3(0,   "colorvalue",    OPT_FLAG,   
+    OPTENT3(0,   "colorvalue",    OPT_FLAG,
             NULL,                  &colorvalue, 0);
-    OPTENT3(0,   "saturation",    OPT_FLAG,   
+    OPTENT3(0,   "saturation",    OPT_FLAG,
             NULL,                  &saturation, 0);
-    OPTENT3(0,   "brightmax",     OPT_FLAG,   
+    OPTENT3(0,   "brightmax",     OPT_FLAG,
             NULL,                  &colorvalue, 0);
-    OPTENT3(0,   "verbose",       OPT_FLAG,   
+    OPTENT3(0,   "verbose",       OPT_FLAG,
             NULL,                  &cmdlineP->verbose, 0);
 
     /* Note: -brightmax was documented and accepted long before it was
@@ -203,7 +203,7 @@ parseCommandLine(int argc, const char ** argv,
 
 
 static void
-buildHistogram(FILE *   const ifp, 
+buildHistogram(FILE *   const ifp,
                int      const cols,
                int      const rows,
                xelval   const maxval,
@@ -226,7 +226,7 @@ buildHistogram(FILE *   const ifp,
 -----------------------------------------------------------------------------*/
     int row;
     xel * xelrow;
-    
+
     xelrow = pnm_allocrow(cols);
 
     {
@@ -307,10 +307,10 @@ maximumValue(const unsigned int * const hist,
 
 
 static void
-computeBottomPercentile(unsigned int         hist[], 
+computeBottomPercentile(unsigned int         hist[],
                         unsigned int   const highest,
                         unsigned int   const total,
-                        float          const percent, 
+                        float          const percent,
                         unsigned int * const percentileP) {
 /*----------------------------------------------------------------------------
    Compute the lowest index of hist[] such that the sum of the hist[]
@@ -332,17 +332,17 @@ computeBottomPercentile(unsigned int         hist[],
                      "values");
         ++percentile;
         count += hist[percentile];
-    }        
+    }
     *percentileP = percentile;
 }
 
 
 
 static void
-computeTopPercentile(unsigned int         hist[], 
-                     unsigned int   const highest, 
+computeTopPercentile(unsigned int         hist[],
+                     unsigned int   const highest,
                      unsigned int   const total,
-                     float          const percent, 
+                     float          const percent,
                      unsigned int * const percentileP) {
 /*----------------------------------------------------------------------------
    Compute the highest index of hist[] such that the sum of the hist[]
@@ -400,7 +400,7 @@ computeAdjustmentForExpansionLimit(xelval   const maxval,
            to 0 .. maxval, if we used the unlimited bvalue and wvalue
         */
     float const unlExpansion = (float)newRange/oldRange;
-    
+
     if (unlExpansion <= maxExpansion) {
         /* No capping is necessary.  Unlimited values are already within
            range.
@@ -527,7 +527,7 @@ resolvePercentParams(FILE *             const ifP,
             *bvalueP = cmdline.bvalue;
         } else {
             xelval percentBvalue;
-            computeBottomPercentile(hist, maxval, cols*rows, cmdline.bpercent, 
+            computeBottomPercentile(hist, maxval, cols*rows, cmdline.bpercent,
                                     &percentBvalue);
             if (cmdline.bvalueSpec)
                 *bvalueP = MIN(percentBvalue, cmdline.bvalue);
@@ -541,7 +541,7 @@ resolvePercentParams(FILE *             const ifP,
             *wvalueP = cmdline.wvalue;
         } else {
             xelval percentWvalue;
-            computeTopPercentile(hist, maxval, cols*rows, cmdline.wpercent, 
+            computeTopPercentile(hist, maxval, cols*rows, cmdline.wpercent,
                                  &percentWvalue);
             if (cmdline.wvalueSpec)
                 *wvalueP = MAX(percentWvalue, cmdline.wvalue);
@@ -630,8 +630,8 @@ computeLinearTransfer(xelval   const bvalue,
        newBrightness[i] = (i-bvalue)*maxval/range);
        (with proper rounding)
     */
-    for (i = bvalue, val = range/2; 
-         i <= wvalue; 
+    for (i = bvalue, val = range/2;
+         i <= wvalue;
          ++i, val += maxval)
         newBrightness[i] = MIN(val / range, maxval);
 
@@ -674,11 +674,11 @@ computeQuadraticFunction(xelval   const bvalue,
     a[0][0] = SQR(bvalue);   a[0][1] = bvalue;   a[0][2] = 1.0;
     a[1][0] = SQR(midvalue); a[1][1] = midvalue; a[1][2] = 1.0;
     a[2][0] = SQR(wvalue);   a[2][1] = wvalue;   a[2][2] = 1.0;
-        
+
     c[0] = 0.0;
     c[1] = middle;
     c[2] = maxval;
-    
+
     pm_solvelineareq(a, x, c, 3, &error);
 
     if (error) {
@@ -714,11 +714,11 @@ computeQuadraticTransfer(xelval   const bvalue,
 
    Set this mapping in newBrightness[].
 -----------------------------------------------------------------------------*/
-    xelval const middle = ROUNDU(middleNorm * maxval);
+    xelval const middle = pnm_unnormalize(middleNorm, maxval);
 
     /* Computing this function is just the task of finding a parabola that
        passes through 3 given points:
-        
+
            (bvalue, 0)
            (midvalue, middle)
            (wvalue, maxval)
@@ -772,8 +772,8 @@ computeQuadraticTransfer(xelval   const bvalue,
 
 static void
 computeTransferFunction(bool      const quadratic,
-                        xelval    const bvalue, 
-                        xelval    const midvalue, 
+                        xelval    const bvalue,
+                        xelval    const midvalue,
                         xelval    const wvalue,
                         float     const middle,
                         xelval    const maxval,
@@ -809,12 +809,12 @@ computeTransferFunction(bool      const quadratic,
         pm_error("Unable to allocate memory for transfer function.");
 
     /* Clip the lowest brightnesses to zero */
-    if (bvalue > 0) 
+    if (bvalue > 0)
         for (i = 0; i < bvalue; ++i)
             newBrightness[i] = 0;
 
     /* Map the middle brightnesses onto 0..maxval */
-    
+
     if (quadratic)
         computeQuadraticTransfer(bvalue, midvalue, wvalue, middle, maxval,
                                  verbose, newBrightness);
@@ -827,7 +827,7 @@ computeTransferFunction(bool      const quadratic,
 
     *newBrightnessP = newBrightness;
 }
-            
+
 
 
 static float
@@ -846,7 +846,7 @@ brightScaler(xel               const p,
 -----------------------------------------------------------------------------*/
     xelval oldBrightness;
     float scaler;
-             
+
     switch (brightMethod) {
     case BRIGHT_LUMINOSITY:
         oldBrightness = ppm_luminosity(p);
@@ -867,7 +867,7 @@ brightScaler(xel               const p,
 
     return scaler;
 }
-            
+
 
 
 static void
@@ -880,13 +880,13 @@ writeRowNormalized(xel *             const xelrow,
                    xelval            const newBrightness[],
                    xel *             const rowbuf) {
 /*----------------------------------------------------------------------------
-   Write to Standard Output a normalized version of the xel row 
+   Write to Standard Output a normalized version of the xel row
    'xelrow'.  Normalize it via the transfer function newBrightness[].
 
    Use 'rowbuf' as a work buffer.  It is at least 'cols' columns wide.
 -----------------------------------------------------------------------------*/
     xel * const outrow = rowbuf;
-                
+
     unsigned int col;
     for (col = 0; col < cols; ++col) {
         xel const p = xelrow[col];
@@ -900,12 +900,12 @@ writeRowNormalized(xel *             const xelrow,
                 xelval const g = MIN(ROUNDU(PPM_GETG(p)*scaler), maxval);
                 xelval const b = MIN(ROUNDU(PPM_GETB(p)*scaler), maxval);
                 PNM_ASSIGN(outrow[col], r, g, b);
-            } else 
-                PNM_ASSIGN(outrow[col], 
-                           newBrightness[PPM_GETR(p)], 
-                           newBrightness[PPM_GETG(p)], 
+            } else
+                PNM_ASSIGN(outrow[col],
+                           newBrightness[PPM_GETR(p)],
+                           newBrightness[PPM_GETG(p)],
                            newBrightness[PPM_GETB(p)]);
-        } else 
+        } else
             PNM_ASSIGN1(outrow[col], newBrightness[PNM_GET1(p)]);
     }
     pnm_writepnmrow(stdout, outrow, cols, maxval, format, 0);
@@ -924,7 +924,7 @@ reportTransferParm(bool   const quadratic,
     if (quadratic)
         pm_message("remapping %u..%u..%u to %u..%u..%u",
                    bvalue, midvalue, wvalue,
-                   0, ROUNDU(maxval*middle), maxval);
+                   0, pnm_unnormalize(middle, maxval), maxval);
     else
         pm_message("remapping %u..%u to %u..%u",
                    bvalue, wvalue, 0, maxval);
@@ -942,7 +942,7 @@ main(int argc, const char *argv[]) {
     int rows, cols, format;
     bool quadratic;
     xelval bvalue, midvalue, wvalue;
-    
+
     pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
@@ -953,14 +953,14 @@ main(int argc, const char *argv[]) {
     pnm_readpnminit(ifP, &cols, &rows, &maxval, &format);
     pm_tell2(ifP, &imagePos, sizeof(imagePos));
 
-    computeEndValues(ifP, cols, rows, maxval, format, cmdline, 
+    computeEndValues(ifP, cols, rows, maxval, format, cmdline,
                      &bvalue, &wvalue, &quadratic, &midvalue);
     {
         xelval * newBrightness;
         int row;
         xel * xelrow;
         xel * rowbuf;
-        
+
         assert(wvalue > bvalue);
 
         xelrow = pnm_allocrow(cols);
@@ -968,7 +968,7 @@ main(int argc, const char *argv[]) {
         reportTransferParm(quadratic, bvalue, midvalue, wvalue, maxval,
                            cmdline.middle);
 
-        
+
         computeTransferFunction(quadratic, bvalue, midvalue, wvalue,
                                 cmdline.middle, maxval, cmdline.verbose,
                                 &newBrightness);
@@ -987,7 +987,7 @@ main(int argc, const char *argv[]) {
         free(newBrightness);
         pnm_freerow(rowbuf);
         pnm_freerow(xelrow);
-    } 
+    }
     pm_close(ifP);
     return 0;
 }
diff --git a/editor/pnmpad.c b/editor/pnmpad.c
index a4f7c5cb..7cc53b69 100644
--- a/editor/pnmpad.c
+++ b/editor/pnmpad.c
@@ -2,6 +2,7 @@
    ** AJCD 4/9/90
  */
 
+#include <assert.h>
 #include <string.h>
 #include <stdio.h>
 
@@ -315,8 +316,10 @@ computePadSizeBeforeMult(unsigned int   const unpaddedSize,
                 (unpaddedSize + endPadReq);
         } else {
             if (sizeReq > unpaddedSize) {
+                assert(align <= 1.0 && align >= 0.0);
                 *begPadP = ROUNDU((sizeReq - unpaddedSize) * align);
                 *endPadP = sizeReq - unpaddedSize - *begPadP;
+                assert(*begPadP + unpaddedSize + *endPadP == sizeReq);
             } else {
                 *begPadP = 0;
                 *endPadP = 0;
diff --git a/editor/pnmpaste.c b/editor/pnmpaste.c
index c27e288c..3baaec7d 100644
--- a/editor/pnmpaste.c
+++ b/editor/pnmpaste.c
@@ -19,7 +19,7 @@
 #include "pnm.h"
 
 
-enum boolOp {REPLACE, AND, OR, XOR /*, NAND, NOR, NXOR */ };
+enum boolOp {REPLACE, AND, OR, XOR, NAND, NOR, NXOR};
 
 struct CmdlineInfo {
     /* All the information the user supplied in the command line,
@@ -47,7 +47,7 @@ parseCommandLine(int argc, const char ** argv,
     optStruct3 opt;
 
     unsigned int option_def_index;
-    unsigned int replaceOpt, andOpt, orOpt, xorOpt;
+    unsigned int replaceOpt, andOpt, orOpt, xorOpt, nandOpt, norOpt, nxorOpt;
 
     MALLOCARRAY_NOFAIL(option_def, 100);
 
@@ -60,6 +60,12 @@ parseCommandLine(int argc, const char ** argv,
             &orOpt,                0);
     OPTENT3(0,   "xor",         OPT_FLAG,    NULL,
             &xorOpt,               0);
+    OPTENT3(0,   "nand",        OPT_FLAG,    NULL,
+            &nandOpt,              0);
+    OPTENT3(0,   "nor",         OPT_FLAG,    NULL,
+            &norOpt,               0);
+    OPTENT3(0,   "nxor",        OPT_FLAG,    NULL,
+            &nxorOpt,              0);
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
@@ -68,16 +74,20 @@ parseCommandLine(int argc, const char ** argv,
     pm_optParseOptions3(&argc, (char **)argv, opt, sizeof opt, 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
-    if (replaceOpt + andOpt + orOpt + xorOpt > 1)
-        pm_error("You may specify only one of -replace, -and, -or, and -xor");
+    if (replaceOpt + andOpt + orOpt + xorOpt + nandOpt + norOpt + nxorOpt > 1)
+        pm_error("You may specify only one of -replace, -and, -or, "
+                 "-xor, -nand, -nor and -nxor");
 
     cmdlineP->operation =
         replaceOpt ? REPLACE :
         andOpt     ? AND     :
         orOpt      ? OR      :
         xorOpt     ? XOR     :
+        nandOpt    ? NAND    :
+        norOpt     ? NOR     :
+        nxorOpt    ? NXOR    :
         replaceOpt;
-        
+
 
     if (argc-1 >= 3) {
         cmdlineP->insetFilename = argv[1];
@@ -168,11 +178,9 @@ insertDirect(FILE *          const ifP,
             case AND: destrow[i] |= buffer[i]; break;
             case OR : destrow[i] &= buffer[i]; break;
             case XOR: destrow[i]  = ~( destrow[i] ^ buffer[i] ) ; break;
-            /*
             case NAND: destrow[i] = ~( destrow[i] | buffer[i] ) ; break;
             case NOR : destrow[i] = ~( destrow[i] & buffer[i] ) ; break;
             case NXOR: destrow[i] ^= buffer[i]  ; break;
-            */
             case REPLACE: assert(false); break;
             }
         }
@@ -229,11 +237,9 @@ insertShift(FILE *          const ifP,
         case AND:     destrow[i] |= t; break;
         case OR :     destrow[i] &= t; break;
         case XOR:     destrow[i] = ~ (destrow[i] ^ t); break;
-        /*
         case NAND:    destrow[i] = ~ (destrow[i] | t); break;
         case NOR :    destrow[i] = ~ (destrow[i] & t); break;
         case NXOR:    destrow[i] ^= t; break;
-        */
         }
     }
 
@@ -244,7 +250,7 @@ insertShift(FILE *          const ifP,
 
     destrow[0] = leftBits(origLeft, offset) |
         rightBits(destrow[0], 8-offset);
-   
+
     if (padOffset % 8 > 0)
         destrow[last] = leftBits(destrow[last], padOffset) |
             rightBits(origRight , 8-padOffset);
@@ -279,7 +285,7 @@ pastePbm(FILE *       const fpInset,
 
     for (row = 0; row < baseRows; ++row) {
         pbm_readpbmrow_packed(fpBase, baserow, baseCols, baseFormat);
-        
+
         if (row >= insertRow && row < insertRow + insetRows) {
             if (shiftOffset == 0)
                 insertDirect(fpInset, &baserow[shiftByteCt], insetCols,
@@ -316,7 +322,7 @@ pasteNonPbm(FILE *       const fpInset,
             unsigned int const insertCol,
             unsigned int const insertRow) {
 
-    /* Logic works for PBM, but cannot do bitwise operations */             
+    /* Logic works for PBM, but cannot do bitwise operations */
 
     xelval const newmaxval = MAX(maxvalInset, maxvalBase);
 
@@ -344,7 +350,7 @@ pasteNonPbm(FILE *       const fpInset,
         }
         pnm_writepnmrow(stdout, xelrowBase, colsBase, newmaxval, newformat, 0);
     }
-    
+
     pnm_freerow(xelrowBase);
     pnm_freerow(xelrowInset);
 }
diff --git a/editor/pnmquant b/editor/pnmquant
index 93d452cd..0bb328d2 100755
--- a/editor/pnmquant
+++ b/editor/pnmquant
@@ -42,6 +42,21 @@ my ($TRUE, $FALSE) = (1,0);
 
 my ($SEEK_SET, $SEEK_CUR, $SEEK_END) = (0, 1, 2);
 
+
+
+sub doVersionHack($) {
+    my ($argvR) = @_;
+
+    my $arg1 = $argvR->[0];
+
+    if (defined($arg1) && (($arg1 eq "--version") || ($arg1 eq "-version"))) {
+        my $termStatus = system('pnmcolormap', '--version');
+        exit($termStatus == 0 ? 0 : 1);
+    }
+}
+
+
+
 sub tempFile($) {
 
     # We trust Perl's File::Temp to do a better job of creating the temp
@@ -81,6 +96,8 @@ sub parseCommandLine(@) {
                                   "spreadbrightness",
                                   "spreadluminosity",
                                   "floyd|fs!",
+                                  "norandom",
+                                  "randomseed=i",
                                   "quiet",
                                   "plain");
 
@@ -93,7 +110,7 @@ sub parseCommandLine(@) {
               scalar(@ARGV), "\n");
         exit(1);
     } 
-    if (@ARGV < 1) {
+    elsif (@ARGV < 1) {
         print(STDERR 
               "You must specify the number of colors as an argument.\n");
         exit(1);
@@ -233,9 +250,10 @@ sub makeColormap($$$$$) {
 
 
 
-sub remap($$$$) {
+sub remap($$$$$$) {
 
-    my ($mapfileSpec, $opt_floyd, $opt_plain, $opt_quiet) = @_;
+    my ($mapfileSpec, $opt_floyd, $opt_norandom, $opt_randomseed,
+        $opt_plain, $opt_quiet) = @_;
 
     # Remap the image on Standard Input to Standard Output, using the colors
     # from the colormap file named $mapfileSpec.
@@ -246,6 +264,17 @@ sub remap($$$$) {
     if ($opt_floyd) {
         push(@options, "-floyd");
     }
+    if ($opt_norandom) {
+        push(@options, "-norandom");
+    }
+    if (defined($opt_randomseed)) {
+        if ($opt_randomseed < 0) {
+             print(STDERR "-randomseed value must not be negative.  " .
+                   "You specified $opt_randomseed\n");
+             exit(10);
+        }
+        push(@options, "-randomseed=$opt_randomseed");
+    }
     if ($opt_plain) {
         push(@options, "-plain");
     }
@@ -267,6 +296,8 @@ sub remap($$$$) {
 #                              MAIN PROGRAM
 ##############################################################################
 
+doVersionHack(\@ARGV);
+
 my $cmdlineR = parseCommandLine(@ARGV);
 
 openSeekableAsStdin($cmdlineR->{infile}); 
@@ -294,6 +325,8 @@ open(STDOUT, ">&OLDOUT");
 
 remap($mapfileSpec, 
       $cmdlineR->{floyd}, 
+      $cmdlineR->{norandom}, 
+      $cmdlineR->{randomseed}, 
       $cmdlineR->{plain},
       $cmdlineR->{quiet});
 
diff --git a/editor/pnmquantall b/editor/pnmquantall
index 2f1a3adf..aea6cc84 100755
--- a/editor/pnmquantall
+++ b/editor/pnmquantall
@@ -62,6 +62,19 @@ my $TRUE=1; my $FALSE = 0;
 
 
 
+sub doVersionHack($) {
+    my ($argvR) = @_;
+
+    my $arg1 = $argvR->[0];
+
+    if (defined($arg1) && (($arg1 eq "--version") || ($arg1 eq "-version"))) {
+        my $termStatus = system('pnmcolormap', '--version');
+        exit($termStatus == 0 ? 0 : 1);
+    }
+}
+
+
+
 sub parseArgs($$$$) {
     my ($argvR, $extR, $newColorCtR, $fileNamesR) = @_;
 
@@ -157,7 +170,7 @@ sub remapFiles($$$$) {
             my $pnmremapTermStatus = system($pnmremapCmd);
 
             if ($pnmremapTermStatus != 0) {
-                $errorR =
+                $$errorR =
                     "Shell command to quantize '$inFileName'  failed:  " .
                     "'$pnmremapCmd'";
             } else {
@@ -165,7 +178,7 @@ sub remapFiles($$$$) {
 
                 unlink($newFileName);
                 File::Copy::move($outputFileName, $newFileName)
-                    or $errorR = "Rename to '$newFileName' failed.";
+                    or $$errorR = "Rename to '$newFileName' failed.";
             }
         }
         unlink($outputFileName);  # In case something failed
@@ -180,6 +193,8 @@ sub remapFiles($$$$) {
 
 my $progError;
 
+doVersionHack(\@ARGV);
+
 parseArgs(\@ARGV, \my $ext, \my $newColorCt, \my @fileNames);
 
 my ($colorMapFh, $colorMapFileName) = tempFile("pnm");
@@ -190,7 +205,6 @@ if (!defined($colorMapFh)) {
 if (!$progError) {
     makeColorMap(\@fileNames, $newColorCt, $colorMapFileName, \$progError);
 }
-print ("got color map\n");
 if (!$progError) {
     remapFiles(\@fileNames, $colorMapFileName, $ext, \$progError);
 }
diff --git a/editor/pnmremap.c b/editor/pnmremap.c
index ed758aa3..0038f4d7 100644
--- a/editor/pnmremap.c
+++ b/editor/pnmremap.c
@@ -41,6 +41,17 @@ enum MissingMethod {
     MISSING_CLOSE
 };
 
+enum InitRandom {
+    RANDOM_NONE,
+    RANDOM_WITHSEED,
+    RANDOM_NOSEED
+};
+
+struct Random {
+    enum InitRandom init;
+    unsigned int seed;
+};
+
 struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
@@ -48,9 +59,9 @@ struct CmdlineInfo {
     const char * inputFilespec;  /* Filespec of input file */
     const char * mapFilespec;    /* Filespec of colormap file */
     unsigned int floyd;   /* Boolean: -floyd/-fs option */
-    unsigned int norandom;
+    struct Random random;
     enum MissingMethod missingMethod;
-    char * missingcolor;      
+    char * missingcolor;
         /* -missingcolor value.  Null if not specified */
     unsigned int verbose;
 };
@@ -62,7 +73,7 @@ parseCommandLine (int argc, const char ** argv,
                   struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    parse program command line described in Unix standard form by argc
-   and argv.  Return the information in the options as *cmdlineP.  
+   and argv.  Return the information in the options as *cmdlineP.
 
    If command line is internally inconsistent (invalid options, etc.),
    issue error message to stderr and abort program.
@@ -78,28 +89,30 @@ parseCommandLine (int argc, const char ** argv,
     unsigned int option_def_index;
 
     unsigned int nofloyd, firstisdefault;
-    unsigned int missingSpec, mapfileSpec;
+    unsigned int missingSpec, mapfileSpec, norandomSpec, randomseedSpec;
 
     MALLOCARRAY_NOFAIL(option_def, 100);
-    
+
     option_def_index = 0;   /* incremented by OPTENT3 */
-    OPTENT3(0,   "floyd",          OPT_FLAG,   
+    OPTENT3(0,   "floyd",          OPT_FLAG,
             NULL,                       &cmdlineP->floyd,    0);
-    OPTENT3(0,   "fs",             OPT_FLAG,   
+    OPTENT3(0,   "fs",             OPT_FLAG,
             NULL,                       &cmdlineP->floyd,    0);
-    OPTENT3(0,   "nofloyd",        OPT_FLAG,   
+    OPTENT3(0,   "nofloyd",        OPT_FLAG,
             NULL,                       &nofloyd,            0);
-    OPTENT3(0,   "nofs",           OPT_FLAG,   
+    OPTENT3(0,   "nofs",           OPT_FLAG,
             NULL,                       &nofloyd,            0);
-    OPTENT3(0,   "norandom",       OPT_FLAG,   
-            NULL,                       &cmdlineP->norandom, 0);
-    OPTENT3(0,   "firstisdefault", OPT_FLAG,   
+    OPTENT3(0,   "norandom",       OPT_FLAG,
+            NULL,                       &norandomSpec,       0);
+    OPTENT3(0,   "randomseed",     OPT_UINT,
+            &cmdlineP->random.seed,     &randomseedSpec,     0);
+    OPTENT3(0,   "firstisdefault", OPT_FLAG,
             NULL,                       &firstisdefault,     0);
-    OPTENT3(0,   "mapfile",        OPT_STRING, 
+    OPTENT3(0,   "mapfile",        OPT_STRING,
             &cmdlineP->mapFilespec,    &mapfileSpec,         0);
-    OPTENT3(0,   "missingcolor",   OPT_STRING, 
+    OPTENT3(0,   "missingcolor",   OPT_STRING,
             &cmdlineP->missingcolor,   &missingSpec,         0);
-    OPTENT3(0, "verbose",          OPT_FLAG,   NULL,                  
+    OPTENT3(0, "verbose",          OPT_FLAG,   NULL,
             &cmdlineP->verbose,                              0);
 
     opt.opt_table = option_def;
@@ -107,13 +120,32 @@ parseCommandLine (int argc, const char ** argv,
     opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
 
     cmdlineP->missingcolor = NULL;  /* default value */
-    
+
     pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdline_p and others. */
 
     if (cmdlineP->floyd && nofloyd)
         pm_error("You cannot specify both -floyd and -nofloyd options.");
 
+    if (cmdlineP->floyd) {
+        if (norandomSpec) {
+            if (randomseedSpec)
+                pm_error("You cannot specify both -norandom and -randomseed.");
+            else
+                cmdlineP->random.init = RANDOM_NONE;
+        } else {
+            if (randomseedSpec)
+                cmdlineP->random.init = RANDOM_WITHSEED;
+            else
+                cmdlineP->random.init = RANDOM_NOSEED;
+        }
+    } else {
+        if (norandomSpec)
+            pm_message("-floyd not specified.  -norandom has no effect.");
+        if (randomseedSpec)
+            pm_message("-floyd not specified.  Ignoring -randomseed value.");
+    }
+
     if (firstisdefault && missingSpec)
         pm_error("You cannot specify both -missing and -firstisdefault.");
 
@@ -176,7 +208,7 @@ grayscaleToDepth3(tuple const tuple) {
 static void
 adjustDepthTuple(tuple           const tuple,
                  depthAdjustment const adjustment) {
-    
+
     switch (adjustment) {
     case ADJUST_NONE:
         break;
@@ -194,7 +226,7 @@ adjustDepthTuple(tuple           const tuple,
 static void
 inverseAdjustDepthTuple(tuple           const tuple,
                         depthAdjustment const adjustment) {
-    
+
     switch (adjustment) {
     case ADJUST_NONE:
         break;
@@ -249,7 +281,7 @@ selectDepthAdjustment(const struct pam * const pamP,
 
    The only depth changes we know how to do are:
 
-     - from tuple type RGB, depth 3 to depth 1 
+     - from tuple type RGB, depth 3 to depth 1
 
        We change it to grayscale or black and white.
 
@@ -296,7 +328,7 @@ selectDepthAdjustment(const struct pam * const pamP,
                      "that I know how to convert to the map depth.  "
                      "I can convert RGB, GRAYSCALE, and BLACKANDWHITE.  "
                      "The input image is '%.*s'.",
-                     newDepth, pamP->depth, 
+                     newDepth, pamP->depth,
                      (int)sizeof(pamP->tuple_type), pamP->tuple_type);
         }
     }
@@ -305,8 +337,8 @@ selectDepthAdjustment(const struct pam * const pamP,
 
 
 static void
-computeColorMapFromMap(struct pam *   const mappamP, 
-                       tuple **       const maptuples, 
+computeColorMapFromMap(struct pam *   const mappamP,
+                       tuple **       const maptuples,
                        tupletable *   const colormapP,
                        unsigned int * const newcolorsP) {
 /*----------------------------------------------------------------------------
@@ -317,12 +349,12 @@ computeColorMapFromMap(struct pam *   const mappamP,
 
    Return the number of colors in the returned colormap as *newcolorsP.
 -----------------------------------------------------------------------------*/
-    unsigned int colors; 
+    unsigned int colors;
 
     if (mappamP->width == 0 || mappamP->height == 0)
         pm_error("colormap file contains no pixels");
 
-    *colormapP = 
+    *colormapP =
         pnm_computetuplefreqtable(mappamP, maptuples, MAXCOLORS, &colors);
     if (*colormapP == NULL)
         pm_error("too many colors in colormap!");
@@ -334,7 +366,7 @@ computeColorMapFromMap(struct pam *   const mappamP,
 
 #define FS_SCALE 1024
 
-struct fserr {
+struct Fserr {
     unsigned int width;
         /* Width of the image being dithered */
     long ** thiserr;
@@ -359,20 +391,26 @@ struct fserr {
 
 
 static void
-randomizeError(long **      const err,
-               unsigned int const width,
-               unsigned int const depth) {
+randomizeError(long **       const err,
+               unsigned int  const width,
+               unsigned int  const depth,
+               struct Random const random) {
 /*----------------------------------------------------------------------------
    Set a random error in the range [-1 .. 1] (normalized via FS_SCALE)
    in the error array err[][].
 -----------------------------------------------------------------------------*/
+    unsigned int const seed = (random.init == RANDOM_WITHSEED) ?
+        random.seed : pm_randseed();
+
     unsigned int col;
 
-    srand(pm_randseed());
+    assert(random.init != RANDOM_NONE);
+
+    srand(seed);
 
     for (col = 0; col < width; ++col) {
         unsigned int plane;
-        for (plane = 0; plane < depth; ++plane) 
+        for (plane = 0; plane < depth; ++plane)
             err[plane][col] = rand() % (FS_SCALE * 2) - FS_SCALE;
     }
 }
@@ -390,7 +428,7 @@ zeroError(long **      const err,
 
     for (col = 0; col < width; ++col) {
         unsigned int plane;
-        for (plane = 0; plane < depth; ++plane) 
+        for (plane = 0; plane < depth; ++plane)
             err[plane][col] = 0;
     }
 }
@@ -398,7 +436,7 @@ zeroError(long **      const err,
 
 
 static void
-fserrSetForward(struct fserr * const fserrP) {
+fserr_setForward(struct Fserr * const fserrP) {
 
     fserrP->fsForward = TRUE;
     fserrP->begCol = 0;
@@ -409,7 +447,7 @@ fserrSetForward(struct fserr * const fserrP) {
 
 
 static void
-fserrSetBackward(struct fserr * const fserrP) {
+fserr_setBackward(struct Fserr * const fserrP) {
 
     fserrP->fsForward = FALSE;
     fserrP->begCol = fserrP->width - 1;
@@ -420,9 +458,9 @@ fserrSetBackward(struct fserr * const fserrP) {
 
 
 static void
-initFserr(struct pam *   const pamP,
-          struct fserr * const fserrP,
-          bool           const initRandom) {
+fserr_init(struct pam *   const pamP,
+           struct Fserr * const fserrP,
+           struct Random  const random) {
 /*----------------------------------------------------------------------------
    Initialize the Floyd-Steinberg error vectors
 -----------------------------------------------------------------------------*/
@@ -440,7 +478,7 @@ initFserr(struct pam *   const pamP,
     if (fserrP->nexterr == NULL)
         pm_error("Out of memory allocating Floyd-Steinberg structures "
                  "for depth %u", pamP->depth);
-    
+
     for (plane = 0; plane < pamP->depth; ++plane) {
         MALLOCARRAY(fserrP->thiserr[plane], fserrSize);
         if (fserrP->thiserr[plane] == NULL)
@@ -452,24 +490,25 @@ initFserr(struct pam *   const pamP,
                      "for Plane %u, size %u", plane, fserrSize);
     }
 
-    if (initRandom)
-        randomizeError(fserrP->thiserr, fserrSize, pamP->depth);
+    if (random.init != RANDOM_NONE)
+        randomizeError(fserrP->thiserr, fserrSize, pamP->depth, random);
     else
         zeroError(fserrP->thiserr, fserrSize, pamP->depth);
 
-    fserrSetForward(fserrP);
+    fserr_setForward(fserrP);
 }
 
 
 
 static void
-floydInitRow(struct pam * const pamP, struct fserr * const fserrP) {
+floydInitRow(struct pam *   const pamP,
+             struct Fserr * const fserrP) {
+
+    unsigned int col;
 
-    int col;
-    
     for (col = 0; col < pamP->width + 2; ++col) {
         unsigned int plane;
-        for (plane = 0; plane < pamP->depth; ++plane) 
+        for (plane = 0; plane < pamP->depth; ++plane)
             fserrP->nexterr[plane][col] = 0;
     }
 }
@@ -477,10 +516,10 @@ floydInitRow(struct pam * const pamP, struct fserr * const fserrP) {
 
 
 static void
-floydAdjustColor(struct pam *   const pamP, 
-                 tuple          const intuple, 
-                 tuple          const outtuple, 
-                 struct fserr * const fserrP, 
+floydAdjustColor(struct pam *   const pamP,
+                 tuple          const intuple,
+                 tuple          const outtuple,
+                 struct Fserr * const fserrP,
                  int            const col) {
 /*----------------------------------------------------------------------------
   Use Floyd-Steinberg errors to adjust actual color.
@@ -497,10 +536,10 @@ floydAdjustColor(struct pam *   const pamP,
 
 
 static void
-floydPropagateErr(struct pam *   const pamP, 
-                  struct fserr * const fserrP, 
-                  int            const col, 
-                  tuple          const oldtuple, 
+floydPropagateErr(struct pam *   const pamP,
+                  struct Fserr * const fserrP,
+                  int            const col,
+                  tuple          const oldtuple,
                   tuple          const newtuple) {
 /*----------------------------------------------------------------------------
   Propagate Floyd-Steinberg error terms.
@@ -515,7 +554,7 @@ floydPropagateErr(struct pam *   const pamP,
         long const newSample = newtuple[plane];
         long const oldSample = oldtuple[plane];
         long const err = (oldSample - newSample) * FS_SCALE;
-            
+
         if (fserrP->fsForward) {
             fserrP->thiserr[plane][col + 2] += ( err * 7 ) / 16;
             fserrP->nexterr[plane][col    ] += ( err * 3 ) / 16;
@@ -533,7 +572,8 @@ floydPropagateErr(struct pam *   const pamP,
 
 
 static void
-floydSwitchDir(struct pam * const pamP, struct fserr * const fserrP) {
+floydSwitchDir(struct pam *   const pamP,
+               struct Fserr * const fserrP) {
 
     unsigned int plane;
 
@@ -544,9 +584,9 @@ floydSwitchDir(struct pam * const pamP, struct fserr * const fserrP) {
     }
 
     if (fserrP->fsForward)
-        fserrSetBackward(fserrP);
+        fserr_setBackward(fserrP);
     else
-        fserrSetForward(fserrP);
+        fserr_setForward(fserrP);
 }
 
 
@@ -567,7 +607,7 @@ struct colormapFinder {
         /* The value by which our intermediate distance calculations
            have to be divided to make sure we don't overflow our
            unsigned int data structure.
-           
+
            To the extent 'distanceDivider' is greater than 1, closest
            color results will be approximate -- there could
            conceivably be a closer one that we miss.
@@ -590,13 +630,13 @@ createColormapFinder(struct pam *             const pamP,
     colormapFinderP->colors = colors;
 
     {
-        unsigned int const maxHandleableSqrDiff = 
+        unsigned int const maxHandleableSqrDiff =
             (unsigned int)UINT_MAX / pamP->depth;
-        
+
         if (SQR(pamP->maxval) > maxHandleableSqrDiff)
             colormapFinderP->distanceDivider = (unsigned int)
                 (SQR(pamP->maxval) / maxHandleableSqrDiff + 0.1 + 1.0);
-                /* The 0.1 is a fudge factor to keep us out of rounding 
+                /* The 0.1 is a fudge factor to keep us out of rounding
                    trouble.  The 1.0 effects a round-up.
                 */
         else
@@ -664,8 +704,8 @@ searchColormapClose(struct pam *            const pamP,
         newdist = 0;
 
         for (plane=0; plane < pamP->depth; ++plane) {
-            newdist += 
-                SQR(tuple[plane] - colorFinderP->colormap[i]->tuple[plane]) 
+            newdist +=
+                SQR(tuple[plane] - colorFinderP->colormap[i]->tuple[plane])
                 / colorFinderP->distanceDivider;
         }
         if (newdist < dist) {
@@ -695,15 +735,15 @@ searchColormapExact(struct pam *            const pamP,
 -----------------------------------------------------------------------------*/
     unsigned int i;
     bool found;
-    
+
     found = FALSE;  /* initial value */
     for (i = 0; i < colorFinderP->colors && !found; ++i) {
         unsigned int plane;
         found = TRUE;  /* initial assumption */
-        for (plane=0; plane < pamP->depth; ++plane) 
-            if (tuple[plane] != colorFinderP->colormap[i]->tuple[plane]) 
+        for (plane=0; plane < pamP->depth; ++plane)
+            if (tuple[plane] != colorFinderP->colormap[i]->tuple[plane])
                 found = FALSE;
-        if (found) 
+        if (found)
             *colormapIndexP = i;
     }
     *foundP = found;
@@ -712,11 +752,11 @@ searchColormapExact(struct pam *            const pamP,
 
 
 static void
-lookupThroughHash(struct pam *            const pamP, 
-                  tuple                   const tuple, 
+lookupThroughHash(struct pam *            const pamP,
+                  tuple                   const tuple,
                   bool                    const needExactMatch,
                   struct colormapFinder * const colorFinderP,
-                  tuplehash               const colorhash,       
+                  tuplehash               const colorhash,
                   int *                   const colormapIndexP,
                   bool *                  const usehashP) {
 /*----------------------------------------------------------------------------
@@ -748,11 +788,11 @@ lookupThroughHash(struct pam *            const pamP,
                                 colormapIndexP, &found);
             if (!found)
                 *colormapIndexP = -1;
-        } else 
+        } else
             searchColormapClose(pamP, tuple, colorFinderP, colormapIndexP);
         if (*usehashP) {
             int fits;
-            pnm_addtotuplehash(pamP, colorhash, tuple, *colormapIndexP, 
+            pnm_addtotuplehash(pamP, colorhash, tuple, *colormapIndexP,
                                &fits);
             if (!fits) {
                 pm_message("out of memory adding to hash table; "
@@ -771,7 +811,7 @@ mapTuple(struct pam *            const pamP,
          tuple                   const defaultColor,
          tupletable              const colormap,
          struct colormapFinder * const colorFinderP,
-         tuplehash               const colorhash, 
+         tuplehash               const colorhash,
          bool *                  const usehashP,
          tuple                   const outTuple,
          bool *                  const missingP) {
@@ -781,7 +821,7 @@ mapTuple(struct pam *            const pamP,
            there is no usable color in the color map.
         */
 
-    lookupThroughHash(pamP, inTuple, !!defaultColor, colorFinderP, 
+    lookupThroughHash(pamP, inTuple, !!defaultColor, colorFinderP,
                       colorhash, &colormapIndex, usehashP);
 
     if (colormapIndex == -1) {
@@ -800,12 +840,12 @@ mapTuple(struct pam *            const pamP,
 
 static void
 convertRowStraight(struct pam *            const inpamP,
-                   struct pam *            const outpamP, 
+                   struct pam *            const outpamP,
                    tuple                         inrow[],
                    depthAdjustment         const depthAdjustment,
                    tupletable              const colormap,
                    struct colormapFinder * const colorFinderP,
-                   tuplehash               const colorhash, 
+                   tuplehash               const colorhash,
                    bool *                  const usehashP,
                    tuple                   const defaultColor,
                    tuple                         outrow[],
@@ -822,7 +862,7 @@ convertRowStraight(struct pam *            const inpamP,
 -----------------------------------------------------------------------------*/
     unsigned int col;
     unsigned int missingCount;
-    
+
     /* The following modify tuplerow, to make it consistent with
      *outpamP instead of *inpamP.
      */
@@ -833,7 +873,7 @@ convertRowStraight(struct pam *            const inpamP,
     adjustDepthRow(outrow, outpamP->width, depthAdjustment);
 
     missingCount = 0;  /* initial value */
-    
+
     for (col = 0; col < outpamP->width; ++col) {
         bool missing;
         mapTuple(outpamP, outrow[col], defaultColor,
@@ -856,10 +896,10 @@ convertRowDither(struct pam *            const inpamP,
                  depthAdjustment         const depthAdjustment,
                  tupletable              const colormap,
                  struct colormapFinder * const colorFinderP,
-                 tuplehash               const colorhash, 
+                 tuplehash               const colorhash,
                  bool *                  const usehashP,
                  tuple                   const defaultColor,
-                 struct fserr *          const fserrP,
+                 struct Fserr *          const fserrP,
                  tuple                         outrow[],
                  unsigned int *          const missingCountP) {
 /*----------------------------------------------------------------------------
@@ -885,7 +925,7 @@ convertRowDither(struct pam *            const inpamP,
     floydInitRow(inpamP, fserrP);
 
     missingCount = 0;  /* initial value */
-    
+
     for (col = fserrP->begCol; col != fserrP->endCol; col += fserrP->step) {
         bool missing;
 
@@ -929,11 +969,11 @@ convertRow(struct pam *            const inpamP,
            depthAdjustment               depthAdjustment,
            tupletable              const colormap,
            struct colormapFinder * const colorFinderP,
-           tuplehash               const colorhash, 
+           tuplehash               const colorhash,
            bool *                  const usehashP,
-           bool                    const floyd, 
+           bool                    const floyd,
            tuple                   const defaultColor,
-           struct fserr *          const fserrP,
+           struct Fserr *          const fserrP,
            tuple                         outrow[],
            unsigned int *          const missingCountP) {
 /*----------------------------------------------------------------------------
@@ -959,7 +999,7 @@ convertRow(struct pam *            const inpamP,
                          depthAdjustment, colormap, colorFinderP, colorhash,
                          usehashP, defaultColor,
                          fserrP, outrow, missingCountP);
-    else 
+    else
         convertRowStraight(inpamP, outpamP, inrow,
                            depthAdjustment, colormap, colorFinderP, colorhash,
                            usehashP, defaultColor,
@@ -969,14 +1009,14 @@ convertRow(struct pam *            const inpamP,
 
 
 static void
-copyRaster(struct pam *       const inpamP, 
-           struct pam *       const outpamP,
-           tupletable         const colormap, 
-           unsigned int       const colormapSize,
-           bool               const floyd, 
-           bool               const randomize,
-           tuple              const defaultColor, 
-           unsigned int *     const missingCountP) {
+copyRaster(struct pam *   const inpamP,
+           struct pam *   const outpamP,
+           tupletable     const colormap,
+           unsigned int   const colormapSize,
+           bool           const floyd,
+           struct Random  const random,
+           tuple          const defaultColor,
+           unsigned int * const missingCountP) {
 
     tuplehash const colorhash = pnm_createtuplehash();
 
@@ -992,7 +1032,7 @@ copyRaster(struct pam *       const inpamP,
     depthAdjustment depthAdjustment;
     struct colormapFinder * colorFinderP;
     bool usehash;
-    struct fserr fserr;
+    struct Fserr fserr;
     int row;
 
     workpam = *outpamP;
@@ -1017,7 +1057,7 @@ copyRaster(struct pam *       const inpamP,
     createColormapFinder(outpamP, colormap, colormapSize, &colorFinderP);
 
     if (floyd)
-        initFserr(inpamP, &fserr, randomize);
+        fserr_init(inpamP, &fserr, random);
 
     *missingCountP = 0;  /* initial value */
 
@@ -1030,9 +1070,9 @@ copyRaster(struct pam *       const inpamP,
                    depthAdjustment, colormap, colorFinderP, colorhash,
                    &usehash, floyd, defaultColor,
                    &fserr,  outrow, &missingCount);
-        
+
         *missingCountP += missingCount;
-        
+
         pnm_writepamrow(outpamP, outrow);
     }
     destroyColormapFinder(colorFinderP);
@@ -1046,10 +1086,10 @@ copyRaster(struct pam *       const inpamP,
 static void
 remap(FILE *             const ifP,
       const struct pam * const outpamCommonP,
-      tupletable         const colormap, 
+      tupletable         const colormap,
       unsigned int       const colormapSize,
       bool               const floyd,
-      bool               const randomize,
+      struct Random      const random,
       tuple              const defaultColor,
       bool               const verbose) {
 /*----------------------------------------------------------------------------
@@ -1075,7 +1115,7 @@ remap(FILE *             const ifP,
             */
 
         pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(allocation_depth));
-    
+
         outpam = *outpamCommonP;
         outpam.width  = inpam.width;
         outpam.height = inpam.height;
@@ -1086,13 +1126,13 @@ remap(FILE *             const ifP,
            convert the input to the output depth.
         */
         pnm_setminallocationdepth(&inpam, outpam.depth);
-    
+
         copyRaster(&inpam, &outpam, colormap, colormapSize, floyd,
-                   randomize, defaultColor, &missingCount);
-        
+                   random, defaultColor, &missingCount);
+
         if (verbose)
             pm_message("%u pixels not matched in color map", missingCount);
-        
+
         pnm_nextimage(ifP, &eof);
     }
 }
@@ -1130,7 +1170,7 @@ processMapFile(const char *   const mapFileName,
 
     pnm_freepamarray(maptuples, &mappam);
 
-    *outpamCommonP = mappam; 
+    *outpamCommonP = mappam;
     outpamCommonP->file = stdout;
 }
 
@@ -1142,7 +1182,7 @@ getSpecifiedMissingColor(struct pam * const pamP,
                          tuple *      const specColorP) {
 
     tuple specColor;
-                             
+
     specColor = pnm_allocpamtuple(pamP);
 
     if (colorName) {
@@ -1213,8 +1253,8 @@ main(int argc, const char * argv[] ) {
         break;
     }
 
-    remap(ifP, &outpamCommon, colormap, colormapSize, 
-          cmdline.floyd, !cmdline.norandom, defaultColor,
+    remap(ifP, &outpamCommon, colormap, colormapSize,
+          cmdline.floyd, cmdline.random, defaultColor,
           cmdline.verbose);
 
     pnm_freepamtuple(firstColor);
diff --git a/editor/pnmrotate.c b/editor/pnmrotate.c
index da5de3a8..44952a59 100644
--- a/editor/pnmrotate.c
+++ b/editor/pnmrotate.c
@@ -10,7 +10,7 @@
 ** implied warranty.
 */
 
-#define _XOPEN_SOURCE   /* get M_PI in math.h */
+#define _XOPEN_SOURCE 500  /* get M_PI in math.h */
 
 #include <math.h>
 #include <assert.h>
diff --git a/editor/pnmshear.c b/editor/pnmshear.c
index 02323824..c705c261 100644
--- a/editor/pnmshear.c
+++ b/editor/pnmshear.c
@@ -10,7 +10,7 @@
 ** implied warranty.
 */
 
-#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/editor/pnmstitch.c b/editor/pnmstitch.c
index 849445fb..eae5e1b9 100644
--- a/editor/pnmstitch.c
+++ b/editor/pnmstitch.c
@@ -82,6 +82,7 @@
  *      - user selectable blending algorithms?
  */
 
+#define _DEFAULT_SOURCE 1 /* New name for SVID & BSD source defines */
 #define _BSD_SOURCE 1   /* Make sure strdup() is in string.h */
 #define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
 
diff --git a/editor/ppmbrighten.c b/editor/ppmbrighten.c
index a3e9a270..0446bb75 100644
--- a/editor/ppmbrighten.c
+++ b/editor/ppmbrighten.c
@@ -1,28 +1,19 @@
-/* ppmbrighten.c - allow user control over Value and Saturation of PPM file
-**
-** Copyright (C) 1989 by Jef Poskanzer.
-** Copyright (C) 1990 by Brian Moffet.
-**
-** Permission to use, copy, modify, and distribute this software and its
-** documentation for any purpose and without fee is hereby granted, provided
-** that the above copyright notice appear in all copies and that both that
-** copyright notice and this permission notice appear in supporting
-** documentation.  This software is provided "as is" without express or
-** implied warranty.
-*/
+/*=============================================================================
+                              ppmbrighten
+===============================================================================
+  Change Value and Saturation of PPM image.
+=============================================================================*/
 
 #include "pm_c_util.h"
 #include "ppm.h"
 #include "shhopt.h"
 #include "mallocvar.h"
 
-#define MULTI   1000
-
-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;  /* '-' if stdin */
+    const char * inputFileName;  /* '-' if stdin */
     float saturation;
     float value;
     unsigned int normalize;
@@ -31,11 +22,11 @@ struct cmdlineInfo {
 
 
 static void
-parseCommandLine(int argc, char ** argv,
-                 struct cmdlineInfo * const cmdlineP) {
+parseCommandLine(int argc, const char ** argv,
+                 struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    parse program command line described in Unix standard form by argc
-   and argv.  Return the information in the options as *cmdlineP.  
+   and argv.  Return the information in the options as *cmdlineP.
 
    If command line is internally inconsistent (invalid options, etc.),
    issue error message to stderr and abort program.
@@ -63,14 +54,13 @@ parseCommandLine(int argc, char ** argv,
     OPTENT3(0, "normalize",   OPT_FLAG,   NULL,
             &cmdlineP->normalize, 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, 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 (saturationSpec) {
         if (saturationOpt < -100)
             pm_error("Saturation reduction cannot be more than 100%%.  "
@@ -90,172 +80,41 @@ parseCommandLine(int argc, char ** argv,
         cmdlineP->value = 1.0;
 
     if (argc-1 < 1)
-        cmdlineP->inputFilespec = "-";
+        cmdlineP->inputFileName = "-";
     else if (argc-1 == 1)
-        cmdlineP->inputFilespec = argv[1];
+        cmdlineP->inputFileName = argv[1];
     else
         pm_error("Program takes at most one argument:  file specification");
 }
 
 
 
-static __inline__ unsigned int
-mod(int const dividend, unsigned int const divisor) {
-
-    int remainder = dividend % (int)divisor;
-
-    if (remainder < 0)
-        return divisor + remainder;
-    else 
-        return (unsigned int) remainder;
-}
-
-
-
-static void 
-RGBtoHSV(pixel          const color,
-         pixval         const maxval,
-         unsigned int * const hP, 
-         unsigned int * const sP, 
-         unsigned int * const vP) {
-
-    unsigned int const R = (MULTI * PPM_GETR(color) + maxval - 1) / maxval;
-    unsigned int const G = (MULTI * PPM_GETG(color) + maxval - 1) / maxval;
-    unsigned int const B = (MULTI * PPM_GETB(color) + maxval - 1) / maxval;
-
-    unsigned int s, v;
-    unsigned int t;
-    unsigned int sector;
-
-    v = MAX(R, MAX(G, B));
-
-    t = MIN(R, MIN(G, B));
-
-    if (v == 0)
-        s = 0;
-    else
-        s = ((v - t)*MULTI)/v;
-
-    if (s == 0)
-        sector = 0;
-    else {
-        unsigned int const cr = (MULTI * (v - R))/(v - t);
-        unsigned int const cg = (MULTI * (v - G))/(v - t);
-        unsigned int const cb = (MULTI * (v - B))/(v - t);
-
-        if (R == v)
-            sector = mod((int)(cb - cg), 6*MULTI);
-        else if (G == v)
-            sector = mod((int)((2*MULTI) + cr - cb), 6*MULTI);
-        else if (B == v)
-            sector = mod((int)((4*MULTI) + cg - cr), 6*MULTI);
-        else
-            pm_error("Internal error: neither r, g, nor b is maximum");
-    }
-
-    *hP = sector * 60;
-    *sP = s;
-    *vP = v;
-}
-
-
-
-static void
-HSVtoRGB(unsigned int   const h, 
-         unsigned int   const s, 
-         unsigned int   const v, 
-         pixval         const maxval,
-         pixel *        const colorP) {
-    
-    unsigned int R, G, B;
-
-    if (s == 0) {
-        R = v;
-        G = v;
-        B = v;
-    } else {
-        unsigned int const sectorSize = 60 * MULTI;
-            /* Color wheel is divided into six 60 degree sectors. */
-        unsigned int const sector = (h/sectorSize);
-            /* The sector in which our color resides.  Value is in 0..5 */
-        unsigned int const f = (h - sector*sectorSize)/60;
-            /* The fraction of the way the color is from one side of
-               our sector to the other side, going clockwise.  Value is
-               in [0, MULTI).
-            */
-        unsigned int const m = (v * (MULTI - s)) / MULTI;
-        unsigned int const n = (v * (MULTI - (s * f)/MULTI)) / MULTI;
-        unsigned int const k = (v * (MULTI - (s * (MULTI - f))/MULTI)) / MULTI;
-
-        switch (sector) {
-        case 0:
-            R = v;
-            G = k;
-            B = m;
-            break;
-        case 1:
-            R = n;
-            G = v;
-            B = m;
-            break;
-        case 2:
-            R = m;
-            G = v;
-            B = k;
-            break;
-        case 3:
-            R = m;
-            G = n;
-            B = v;
-            break;
-        case 4:
-            R = k;
-            G = m;
-            B = v;
-            break;
-        case 5:
-            R = v;
-            G = m;
-            B = n;
-            break;
-        default:
-            pm_error("Invalid H value passed to HSVtoRGB: %u/%u", h, MULTI);
-        }
-    }
-    PPM_ASSIGN(*colorP, 
-               (R * maxval) / MULTI,
-               (G * maxval) / MULTI,
-               (B * maxval) / MULTI);
-}
-
-
-
 static void
-getMinMax(FILE *         const ifP,
-          int            const cols,
-          int            const rows,
-          pixval         const maxval,
-          int            const format,
-          unsigned int * const minValueP,
-          unsigned int * const maxValueP) {
+getMinMax(FILE *       const ifP,
+          unsigned int const cols,
+          unsigned int const rows,
+          pixval       const maxval,
+          int          const format,
+          double *     const minValueP,
+          double *     const maxValueP) {
 
     pixel * pixelrow;
-    unsigned int minValue, maxValue;
-    int row;
+    double minValue, maxValue;
+    unsigned int row;
 
     pixelrow = ppm_allocrow(cols);
 
-    maxValue = 0;
-    minValue = MULTI;
-    for (row = 0; row < rows; ++row) {
+    for (row = 0, minValue = 65536.0, maxValue = 0.0; row < rows; ++row) {
         unsigned int col;
+
         ppm_readppmrow(ifP, pixelrow, cols, maxval, format);
+
         for (col = 0; col < cols; ++col) {
-            unsigned int H, S, V;
+            struct hsv const pixhsv =
+                ppm_hsv_from_color(pixelrow[col], maxval);
 
-            RGBtoHSV(pixelrow[col], maxval, &H, &S, &V);
-            maxValue = MAX(maxValue, V);
-            minValue = MIN(minValue, V);
+            maxValue = MAX(maxValue, pixhsv.v);
+            minValue = MIN(minValue, pixhsv.v);
         }
     }
     ppm_freerow(pixelrow);
@@ -267,23 +126,24 @@ getMinMax(FILE *         const ifP,
 
 
 int
-main(int argc, char * argv[]) {
+main(int argc, const char ** argv) {
 
-    struct cmdlineInfo cmdline;
+    double const EPSILON = 1.0e-5;
+    struct CmdlineInfo cmdline;
     FILE * ifP;
-    pixval minValue, maxValue;
     pixel * pixelrow;
     pixval maxval;
     int rows, cols, format, row;
+    double minValue, maxValue;
 
-    ppm_init(&argc, argv);
+    pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
 
     if (cmdline.normalize)
-        ifP = pm_openr_seekable(cmdline.inputFilespec);
+        ifP = pm_openr_seekable(cmdline.inputFileName);
     else
-        ifP = pm_openr(cmdline.inputFilespec);
+        ifP = pm_openr(cmdline.inputFileName);
 
     ppm_readppminit(ifP, &cols, &rows, &maxval, &format);
 
@@ -292,17 +152,17 @@ main(int argc, char * argv[]) {
         pm_tell2(ifP, &rasterPos, sizeof(rasterPos));
         getMinMax(ifP, cols, rows, maxval, format, &minValue, &maxValue);
         pm_seek2(ifP, &rasterPos, sizeof(rasterPos));
-        if (maxValue > minValue) {
-            pm_message("Minimum value %u%% of full intensity "
+        if (maxValue - minValue > EPSILON) {
+            pm_message("Minimum value %.0f%% of full intensity "
                        "being remapped to zero.",
-                       (minValue*100+MULTI/2)/MULTI);
-            pm_message("Maximum value %u%% of full intensity "
+                       (minValue * 100.0));
+            pm_message("Maximum value %.0f%% of full intensity "
                        "being remapped to full.",
-                       (maxValue*100+MULTI/2)/MULTI);
+                       (maxValue * 100.0));
         } else
-            pm_message("Sole intensity value %u%% of full intensity "
+            pm_message("Sole value of %.0f%% of full intensity "
                        "not being remapped",
-                       (minValue*100+MULTI/2)/MULTI);
+                       (maxValue * 100.0));
     }
 
     pixelrow = ppm_allocrow(cols);
@@ -311,34 +171,48 @@ main(int argc, char * argv[]) {
 
     for (row = 0; row < rows; ++row) {
         unsigned int col;
+
         ppm_readppmrow(ifP, pixelrow, cols, maxval, format);
+
         for (col = 0; col < cols; ++col) {
-            unsigned int H, S, V;
+            struct hsv pixhsv;
+
+            pixhsv = ppm_hsv_from_color(pixelrow[col], maxval);
+                /* initial value */
 
-            RGBtoHSV(pixelrow[col], maxval, &H, &S, &V);
-            
             if (cmdline.normalize) {
-                if (maxValue > minValue) {
-                    V -= minValue;
-                    V = (V * MULTI) /
-                        (MULTI - (minValue+MULTI-maxValue));
-                }
+                if (maxValue - minValue > EPSILON)
+                    pixhsv.v = (pixhsv.v - minValue) / (maxValue - minValue);
             }
-
-            S = MIN(MULTI, (unsigned int) (S * cmdline.saturation + 0.5));
-            V = MIN(MULTI, (unsigned int) (V * cmdline.value + 0.5));
-
-            HSVtoRGB(H, S, V, maxval, &pixelrow[col]);
+            pixhsv.s = pixhsv.s * cmdline.saturation;
+            pixhsv.s = MAX(0.0, MIN(1.0, pixhsv.s));
+            pixhsv.v = pixhsv.v * cmdline.value;
+            pixhsv.v = MAX(0.0, MIN(1.0, pixhsv.v));
+            pixelrow[col] = ppm_color_from_hsv(pixhsv, maxval);
         }
-
         ppm_writeppmrow(stdout, pixelrow, cols, maxval, 0);
     }
     ppm_freerow(pixelrow);
 
     pm_close(ifP);
 
-    /* If the program failed, it previously aborted with nonzero completion
-       code, via various function calls.
+    /* If the program failed, it previously aborted with nonzero exit status
+       via various function calls.
     */
     return 0;
 }
+
+
+
+/**
+** Copyright (C) 1989 by Jef Poskanzer.
+** Copyright (C) 1990 by Brian Moffet.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
diff --git a/editor/ppmchange.c b/editor/ppmchange.c
index dea85a77..cfc34769 100644
--- a/editor/ppmchange.c
+++ b/editor/ppmchange.c
@@ -19,20 +19,22 @@
 #include "mallocvar.h"
 
 #define TCOLS 256
-#define SQRT3 1.73205080756887729352
+static double const sqrt3 = 1.73205080756887729352;
     /* The square root of 3 */
+static double const EPSILON = 1.0e-5;
 
-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 *input_filespec;  /* Filespecs of input files */
     int ncolors;      /* Number of valid entries in color0[], color1[] */
-    char * oldcolorname[TCOLS];  /* colors user wants replaced */
-    char * newcolorname[TCOLS];  /* colors with which he wants them replaced */
-    int closeness;    
-       /* -closeness option value.  Zero if no -closeness option */
-    char * remainder_colorname;  
+    const char * oldcolorname[TCOLS];
+        /* colors user wants replaced */
+    const char * newcolorname[TCOLS];
+        /* colors with which he wants them replaced */
+    float closeness;    
+    const char * remainder_colorname;  
       /* Color user specified for -remainder.  Null pointer if he didn't
          specify -remainder.
       */
@@ -42,8 +44,8 @@ struct cmdlineInfo {
 
 
 static void
-parseCommandLine(int argc, char ** argv,
-                 struct cmdlineInfo * const cmdlineP) {
+parseCommandLine(int argc, const 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.
@@ -59,7 +61,7 @@ parseCommandLine(int argc, char ** argv,
     MALLOCARRAY_NOFAIL(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENTRY */
-    OPTENT3(0, "closeness",     OPT_UINT,
+    OPTENT3(0, "closeness",     OPT_FLOAT,
             &cmdlineP->closeness,           &closenessSpec,     0);
     OPTENT3(0, "remainder",     OPT_STRING,
             &cmdlineP->remainder_colorname, &remainderSpec,     0);
@@ -70,15 +72,22 @@ parseCommandLine(int argc, char ** argv,
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
     opt.allowNegNum = FALSE;  /* We may have 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 (!remainderSpec)
         cmdlineP->remainder_colorname = NULL;
 
     if (!closenessSpec)
-        cmdlineP->closeness = 0;
+        cmdlineP->closeness = 0.0;
 
+    if (cmdlineP->closeness < 0.0)
+        pm_error("-closeness value %f is negative", cmdlineP->closeness);
+
+    if (cmdlineP->closeness > 100)
+        pm_error("-closeness value %f is more than 100%%",
+                 cmdlineP->closeness);
+    
     if ((argc-1) % 2 == 0) 
         cmdlineP->input_filespec = "-";
     else
@@ -99,26 +108,19 @@ parseCommandLine(int argc, char ** argv,
 
 
 
-static double
-sqrf(float const F) {
-    return F*F;
-}
-
-
-
-static int 
-colormatch(pixel const comparand, 
-           pixel const comparator, 
-           float const closeness) {
+static bool
+colorMatches(pixel        const comparand, 
+             pixel        const comparator, 
+             unsigned int const allowableDiff) {
 /*----------------------------------------------------------------------------
-   Return true iff 'comparand' matches 'comparator' in color within the
-   fuzz factor 'closeness'.
+   The colors 'comparand' and 'comparator' are within 'allowableDiff'
+   color levels of each other, in cartesian distance.
 -----------------------------------------------------------------------------*/
     /* Fast path for usual case */
-    if (closeness == 0)
+    if (allowableDiff < EPSILON)
         return PPM_EQUAL(comparand, comparator);
 
-    return PPM_DISTANCE(comparand, comparator) <= sqrf(closeness);
+    return PPM_DISTANCE(comparand, comparator) <= SQR(allowableDiff);
 }
 
 
@@ -132,15 +134,18 @@ changeRow(const pixel * const inrow,
           const pixel         colorto[],
           bool          const remainder_specified, 
           pixel         const remainder_color, 
-          float         const closeness) {
+          unsigned int  const allowableDiff) {
 /*----------------------------------------------------------------------------
    Replace the colors in a single row.  There are 'ncolors' colors to 
    replace.  The to-replace colors are in the array colorfrom[], and the
    replace-with colors are in corresponding elements of colorto[].
    Iff 'remainder_specified' is true, replace all colors not mentioned
-   in colorfrom[] with 'remainder_color'.  Use the closeness factor
-   'closeness' in determining if something in the input row matches
-   a color in colorfrom[].
+   in colorfrom[] with 'remainder_color'.  
+
+   Consider the color in inrow[] to match a color in colorfrom[] if it is
+   within 'allowableDiff' color levels of it, in cartesian distance (e.g.
+   color (1,1,1) is sqrt(12) = 3.5 color levels distant from (3,3,3),
+   so if 'allowableDiff' is 4, they match).
 
    The input row is 'inrow'.  The output is returned as 'outrow', in
    storage which must be already allocated.  Both are 'cols' columns wide.
@@ -157,7 +162,7 @@ changeRow(const pixel * const inrow,
 
         haveMatch = FALSE;  /* haven't found a match yet */
         for (i = 0; i < ncolors && !haveMatch; ++i) {
-            haveMatch = colormatch(inrow[col], colorfrom[i], closeness);
+            haveMatch = colorMatches(inrow[col], colorfrom[i], allowableDiff);
             newcolor = colorto[i];
         }
         if (haveMatch)
@@ -172,16 +177,23 @@ changeRow(const pixel * const inrow,
 
 
 int
-main(int argc, char *argv[]) {
-    struct cmdlineInfo cmdline;
+main(int argc, const char ** const argv) {
+
+    struct CmdlineInfo cmdline;
     FILE * ifP;
     int format;
     int rows, cols;
     pixval maxval;
-    float closeness;
+    unsigned int allowableDiff;
+        /* The amount of difference between two colors we allow and still
+           consider those colors to be the same, for the purposes of
+           determining which pixels in the image to change.  This is a
+           cartesian distance between the color triples, on a maxval scale
+           (which means it can be as high as sqrt(3) * maxval)
+        */
     int row;
-    pixel* inrow;
-    pixel* outrow;
+    pixel * inrow;
+    pixel * outrow;
 
     pixel oldcolor[TCOLS];  /* colors user wants replaced */
     pixel newcolor[TCOLS];  /* colors with which he wants them replaced */
@@ -190,7 +202,7 @@ main(int argc, char *argv[]) {
          specify -remainder.
       */
 
-    ppm_init(&argc, argv);
+    pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
     
@@ -210,8 +222,8 @@ main(int argc, char *argv[]) {
                                           cmdline.closeok);
         }
     }
-    closeness = SQRT3 * maxval * cmdline.closeness/100;
-
+    allowableDiff = ROUNDU(sqrt3 * maxval * cmdline.closeness/100);
+    
     ppm_writeppminit( stdout, cols, rows, maxval, 0 );
     inrow = ppm_allocrow(cols);
     outrow = ppm_allocrow(cols);
@@ -222,7 +234,7 @@ main(int argc, char *argv[]) {
 
         changeRow(inrow, outrow, cols, cmdline.ncolors, oldcolor, newcolor,
                   cmdline.remainder_colorname != NULL,
-                  remainder_color, closeness);
+                  remainder_color, allowableDiff);
 
         ppm_writeppmrow(stdout, outrow, cols, maxval, 0);
     }
diff --git a/editor/ppmcolormask.c b/editor/ppmcolormask.c
index 5ef8d1c1..3812ac86 100644
--- a/editor/ppmcolormask.c
+++ b/editor/ppmcolormask.c
@@ -9,6 +9,7 @@
   Contributed to the public domain by its author.
 =========================================================================*/
 
+#define _DEFAULT_SOURCE /* New name for SVID & BSD source defines */
 #define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
 #define _BSD_SOURCE  /* Make sure strdup() is in <string.h> */
 #include <assert.h>
@@ -19,23 +20,23 @@
 #include "mallocvar.h"
 #include "nstring.h"
 #include "ppm.h"
-#include "pbm.h"
+#include "pam.h"
 
-enum matchType {
+typedef enum {
     MATCH_EXACT,
     MATCH_BK
-};
+} MatchType;
 
-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 * inputFilename;
-    unsigned int colorCount;
+    unsigned int colorCt;
     struct {
-        enum matchType matchType;
+        MatchType matchType;
         union {
-            pixel    color;   /* matchType == MATCH_EXACT */
+            tuplen   color;   /* matchType == MATCH_EXACT */
             bk_color bkColor; /* matchType == MATCH_BK */
         } u;
     } maskColor[16];
@@ -45,46 +46,59 @@ struct cmdlineInfo {
 
 
 static void
+freeCmdline(struct CmdlineInfo * const cmdlineP) {
+
+    unsigned int i;
+
+    for (i = 0; i < cmdlineP->colorCt; ++ i) {
+        if (cmdlineP->maskColor[i].matchType == MATCH_EXACT)
+            free(cmdlineP->maskColor[i].u.color);
+    }
+}
+
+
+
+static void
 parseColorOpt(const char *         const colorOpt,
-              struct cmdlineInfo * const cmdlineP) {
+              struct CmdlineInfo * const cmdlineP) {
 
-    unsigned int colorCount;
+    unsigned int colorCt;
     char * colorOptWork;
     char * cursor;
     bool eol;
-    
+
     colorOptWork = strdup(colorOpt);
     cursor = &colorOptWork[0];
-    
+
     eol = FALSE;    /* initial value */
-    colorCount = 0; /* initial value */
-    while (!eol && colorCount < ARRAY_SIZE(cmdlineP->maskColor)) {
+    colorCt = 0;    /* initial value */
+    while (!eol && colorCt < ARRAY_SIZE(cmdlineP->maskColor)) {
         const char * token;
         token = pm_strsep(&cursor, ",");
         if (token) {
             if (strneq(token, "bk:", 3)) {
-                cmdlineP->maskColor[colorCount].matchType = MATCH_BK;
-                cmdlineP->maskColor[colorCount].u.bkColor =
+                cmdlineP->maskColor[colorCt].matchType = MATCH_BK;
+                cmdlineP->maskColor[colorCt].u.bkColor =
                     ppm_bk_color_from_name(&token[3]);
             } else {
-                cmdlineP->maskColor[colorCount].matchType = MATCH_EXACT;
-                cmdlineP->maskColor[colorCount].u.color =
-                    ppm_parsecolor(token, PPM_MAXMAXVAL);
+                cmdlineP->maskColor[colorCt].matchType = MATCH_EXACT;
+                cmdlineP->maskColor[colorCt].u.color =
+                    pnm_parsecolorn(token);
             }
-            ++colorCount;
+            ++colorCt;
         } else
             eol = TRUE;
     }
     free(colorOptWork);
 
-    cmdlineP->colorCount = colorCount;
+    cmdlineP->colorCt = colorCt;
 }
 
 
 
 static void
-parseCommandLine(int argc, char ** argv,
-                 struct cmdlineInfo *cmdlineP) {
+parseCommandLine(int argc, const char ** argv,
+                 struct CmdlineInfo *cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that many of the strings that this function returns in the
    *cmdlineP structure are actually in the supplied argv array.  And
@@ -109,7 +123,7 @@ parseCommandLine(int argc, char ** argv,
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
     opt.allowNegNum = FALSE;  /* We may have 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 all of *cmdlineP. */
 
     if (colorSpec)
@@ -128,16 +142,15 @@ parseCommandLine(int argc, char ** argv,
         if (argc-1 < 1)
             pm_error("You must specify the -color option.");
         else {
-            cmdlineP->colorCount = 1;
+            cmdlineP->colorCt = 1;
             cmdlineP->maskColor[0].matchType = MATCH_EXACT;
-            cmdlineP->maskColor[0].u.color =
-                ppm_parsecolor(argv[1], PPM_MAXMAXVAL);
+            cmdlineP->maskColor[0].u.color = pnm_parsecolorn(argv[1]);
 
             if (argc - 1 < 2)
                 cmdlineP->inputFilename = "-";  /* he wants stdin */
             else if (argc-1 == 2)
                 cmdlineP->inputFilename = argv[2];
-            else 
+            else
                 pm_error("Too many arguments.  The only arguments accepted "
                          "are the mask color and optional input file name");
         }
@@ -146,16 +159,47 @@ parseCommandLine(int argc, char ** argv,
 
 
 
+static void
+setupOutput(FILE *       const fileP,
+            unsigned int const width,
+            unsigned int const height,
+            struct pam * const outPamP) {
+
+    outPamP->size             = sizeof(*outPamP);
+    outPamP->len              = PAM_STRUCT_SIZE(tuple_type);
+    outPamP->file             = fileP;
+    outPamP->format           = RPBM_FORMAT;
+    outPamP->plainformat      = 0;
+    outPamP->height           = height;
+    outPamP->width            = width;
+    outPamP->depth            = 1;
+    outPamP->maxval           = 1;
+    outPamP->bytes_per_sample = 1;
+    strcpy(outPamP->tuple_type, PAM_PBM_TUPLETYPE);
+}
+
+
+
 static bool
-isBkColor(pixel    const comparator,
-          pixval   const maxval,
-          bk_color const comparand) {
+isBkColor(tuple        const comparator,
+          struct pam * const pamP,
+          bk_color     const comparand) {
+
+    pixel comparatorPixel;
+    bk_color comparatorBk;
 
     /* TODO: keep a cache of the bk color for each color in
        a colorhash_table.
     */
-    
-    bk_color const comparatorBk = ppm_bk_color_from_color(comparator, maxval);
+
+    assert(pamP->depth >= 3);
+
+    PPM_ASSIGN(comparatorPixel,
+               comparator[PAM_RED_PLANE],
+               comparator[PAM_GRN_PLANE],
+               comparator[PAM_BLU_PLANE]);
+
+    comparatorBk = ppm_bk_color_from_color(comparatorPixel, pamP->maxval);
 
     return comparatorBk == comparand;
 }
@@ -163,86 +207,94 @@ isBkColor(pixel    const comparator,
 
 
 static bool
-colorIsInSet(pixel              const color,
-             pixval             const maxval,
-             struct cmdlineInfo const cmdline) {
+colorIsInSet(tuple              const color,
+             struct pam *       const pamP,
+             struct CmdlineInfo const cmdline) {
 
     bool isInSet;
     unsigned int i;
+    tuple maskColorUnnorm;
+
+    maskColorUnnorm = pnm_allocpamtuple(pamP);
 
-    for (i = 0, isInSet = FALSE;
-         i < cmdline.colorCount && !isInSet; ++i) {
+    for (i = 0, isInSet = FALSE; i < cmdline.colorCt && !isInSet; ++i) {
 
         assert(i < ARRAY_SIZE(cmdline.maskColor));
 
         switch(cmdline.maskColor[i].matchType) {
         case MATCH_EXACT:
-            if (PPM_EQUAL(color, cmdline.maskColor[i].u.color))
+            pnm_unnormalizetuple(pamP,
+                                 cmdline.maskColor[i].u.color,
+                                 maskColorUnnorm);
+            if (pnm_tupleequal(pamP, color, maskColorUnnorm))
                 isInSet = TRUE;
             break;
         case MATCH_BK:
-            if (isBkColor(color, maxval, cmdline.maskColor[i].u.bkColor))
+            if (isBkColor(color, pamP, cmdline.maskColor[i].u.bkColor))
                 isInSet = TRUE;
             break;
         }
     }
+
+    free(maskColorUnnorm);
+
     return isInSet;
 }
 
 
 
 int
-main(int argc, char *argv[]) {
+main(int argc, const char *argv[]) {
 
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
 
     FILE * ifP;
+    struct pam inPam;
+    struct pam outPam;
 
-    /* Parameters of input image: */
-    int rows, cols;
-    pixval maxval;
-    int format;
-
-    ppm_init(&argc, argv);
+    pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
 
     ifP = pm_openr(cmdline.inputFilename);
 
-    ppm_readppminit(ifP, &cols, &rows, &maxval, &format);
-    pbm_writepbminit(stdout, cols, rows, 0);
+    pnm_readpaminit(ifP, &inPam, PAM_STRUCT_SIZE(allocation_depth));
+
+    pnm_setminallocationdepth(&inPam, 3);
+
+    setupOutput(stdout, inPam.width, inPam.height, &outPam);
+
+    pnm_writepaminit(&outPam);
     {
-        pixel * const inputRow = ppm_allocrow(cols);
-        bit *   const maskRow  = pbm_allocrow(cols);
+        tuple * const inputRow = pnm_allocpamrow(&inPam);
+        tuple * const maskRow  = pnm_allocpamrow(&outPam);
 
         unsigned int numPixelsMasked;
 
         unsigned int row;
-        for (row = 0, numPixelsMasked = 0; row < rows; ++row) {
-            int col;
-            ppm_readppmrow(ifP, inputRow, cols, maxval, format);
-            for (col = 0; col < cols; ++col) {
-                pixel thisColor;
-                    /* Color of this pixel with same maxval as used in
-                       'cmdline'
-                    */
-                PPM_DEPTH(thisColor, inputRow[col], maxval, PPM_MAXMAXVAL);
-                if (colorIsInSet(thisColor, PPM_MAXMAXVAL, cmdline)) {
-                    maskRow[col] = PBM_BLACK;
+
+        for (row = 0, numPixelsMasked = 0; row < inPam.height; ++row) {
+            unsigned int col;
+            pnm_readpamrow(&inPam, inputRow);
+            pnm_makerowrgb(&inPam, inputRow);
+            for (col = 0; col < inPam.width; ++col) {
+                if (colorIsInSet(inputRow[col], &inPam, cmdline)) {
+                    maskRow[col][0] = PAM_BLACK;
                     ++numPixelsMasked;
-                } else 
-                    maskRow[col] = PBM_WHITE;
+                } else
+                    maskRow[col][0] = PAM_BW_WHITE;
             }
-            pbm_writepbmrow(stdout, maskRow, cols, 0);
+            pnm_writepamrow(&outPam, maskRow);
         }
 
         if (cmdline.verbose)
             pm_message("%u pixels found matching %u requested colors",
-                       numPixelsMasked, cmdline.colorCount);
+                       numPixelsMasked, cmdline.colorCt);
 
-        pbm_freerow(maskRow);
-        ppm_freerow(inputRow);
+        pnm_freepamrow(maskRow);
+        pnm_freepamrow(inputRow);
     }
+    freeCmdline(&cmdline);
     pm_close(ifP);
 
     return 0;
diff --git a/editor/ppmdraw.c b/editor/ppmdraw.c
index bd569e03..3453a7e1 100644
--- a/editor/ppmdraw.c
+++ b/editor/ppmdraw.c
@@ -1,4 +1,5 @@
-#define _XOPEN_SOURCE 500 
+#define _DEFAULT_SOURCE /* New name for SVID & BSD source defines */
+#define _XOPEN_SOURCE 500
    /* Make sure M_PI is in math.h, strdup is in string.h */
 #define _BSD_SOURCE      /* Make sure strdup is in string.h (alternate) */
 
@@ -49,7 +50,7 @@ parseCommandLine (int argc, const char ** argv,
                   struct cmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    parse program command line described in Unix standard form by argc
-   and argv.  Return the information in the options as *cmdlineP.  
+   and argv.  Return the information in the options as *cmdlineP.
 
    If command line is internally inconsistent (invalid options, etc.),
    issue error message to stderr and abort program.
@@ -83,7 +84,7 @@ parseCommandLine (int argc, const char ** argv,
 
     pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
-    
+
     if (!scriptSpec && !scriptfileSpec)
         pm_error("You must specify either -script or -scriptfile");
 
@@ -281,7 +282,7 @@ struct drawCommand {
 
 static void
 freeDrawCommand(const struct drawCommand * const commandP) {
-    
+
     switch (commandP->verb) {
     case VERB_SETPOS:
         break;
@@ -312,7 +313,7 @@ freeDrawCommand(const struct drawCommand * const commandP) {
         pm_strfree(commandP->u.textArg.text);
         break;
     }
-    
+
 
     free((void *) commandP);
 }
@@ -360,21 +361,21 @@ doFilledCircle(pixel **                   const pixels,
     struct fillobj * fhP;
 
     fhP = ppmd_fill_create();
-            
+
     ppmd_circle(pixels, cols, rows, maxval,
                 commandP->u.circleArg.cx,
                 commandP->u.circleArg.cy,
                 commandP->u.circleArg.radius,
                 ppmd_fill_drawproc,
                 fhP);
-            
+
     ppmd_fill(pixels, cols, rows, maxval,
               fhP,
               PPMD_NULLDRAWPROC,
               &drawStateP->color);
 
     ppmd_fill_destroy(fhP);
-} 
+}
 
 
 
@@ -385,7 +386,7 @@ doTextHere(pixel **                   const pixels,
            pixval                     const maxval,
            const struct drawCommand * const commandP,
            struct drawState *         const drawStateP) {
-    
+
     ppmd_text(pixels, cols, rows, maxval,
               drawStateP->currentPos.x,
               drawStateP->currentPos.y,
@@ -394,14 +395,14 @@ doTextHere(pixel **                   const pixels,
               commandP->u.textArg.text,
               PPMD_NULLDRAWPROC,
               &drawStateP->color);
-    
+
     {
         int left, top, right, bottom;
-        
+
         ppmd_text_box(commandP->u.textArg.height, 0,
                       commandP->u.textArg.text,
                       &left, &top, &right, &bottom);
-        
+
 
         drawStateP->currentPos.x +=
             ROUND((right-left) * cosdeg(commandP->u.textArg.angle));
@@ -528,10 +529,10 @@ executeScript(struct script * const scriptP,
 
 
 struct tokenSet {
-    
+
     const char * token[10];
     unsigned int count;
-    
+
 };
 
 
@@ -570,7 +571,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                 if (streq(typeArg, "normal"))
                     drawCommandP->u.setlinetypeArg.type = PPMD_LINETYPE_NORMAL;
                 else if (streq(typeArg, "nodiag"))
-                    drawCommandP->u.setlinetypeArg.type = 
+                    drawCommandP->u.setlinetypeArg.type =
                         PPMD_LINETYPE_NODIAGS;
                 else
                     pm_error("Invalid type");
@@ -609,7 +610,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                 drawCommandP->u.lineArg.y0 = atoi(commandTokens.token[2]);
                 drawCommandP->u.lineArg.x1 = atoi(commandTokens.token[3]);
                 drawCommandP->u.lineArg.y1 = atoi(commandTokens.token[4]);
-            } 
+            }
         } else if (streq(verb, "line_here")) {
             drawCommandP->verb = VERB_LINE_HERE;
             if (commandTokens.count < 3)
@@ -620,7 +621,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                     &drawCommandP->u.lineHereArg;
                 argP->right = atoi(commandTokens.token[1]);
                 argP->down = atoi(commandTokens.token[2]);
-            } 
+            }
        } else if (streq(verb, "spline3")) {
             drawCommandP->verb = VERB_SPLINE3;
             if (commandTokens.count < 7)
@@ -635,7 +636,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                 argP->y1 = atoi(commandTokens.token[4]);
                 argP->x2 = atoi(commandTokens.token[5]);
                 argP->y2 = atoi(commandTokens.token[6]);
-            } 
+            }
         } else if (streq(verb, "circle")) {
             drawCommandP->verb = VERB_CIRCLE;
             if (commandTokens.count < 4)
@@ -646,7 +647,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                 argP->cx     = atoi(commandTokens.token[1]);
                 argP->cy     = atoi(commandTokens.token[2]);
                 argP->radius = atoi(commandTokens.token[3]);
-            } 
+            }
         } else if (streq(verb, "filledcircle")) {
             drawCommandP->verb = VERB_FILLEDCIRCLE;
             if (commandTokens.count < 4)
@@ -657,7 +658,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                 argP->cx     = atoi(commandTokens.token[1]);
                 argP->cy     = atoi(commandTokens.token[2]);
                 argP->radius = atoi(commandTokens.token[3]);
-            } 
+            }
         } else if (streq(verb, "filledrectangle")) {
             drawCommandP->verb = VERB_FILLEDRECTANGLE;
             if (commandTokens.count < 5)
@@ -670,7 +671,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                 argP->y      = atoi(commandTokens.token[2]);
                 argP->width  = atoi(commandTokens.token[3]);
                 argP->height = atoi(commandTokens.token[4]);
-            } 
+            }
         } else if (streq(verb, "text")) {
             drawCommandP->verb = VERB_TEXT;
             if (commandTokens.count < 6)
@@ -712,9 +713,9 @@ disposeOfCommandTokens(struct tokenSet * const tokenSetP,
     /* We've got a whole command in 'tokenSet'.  Parse it into *scriptP
        and reset tokenSet to empty.
     */
-    
+
     struct commandListElt * commandListEltP;
-    
+
     MALLOCVAR(commandListEltP);
     if (commandListEltP == NULL)
         pm_error("Out of memory allocating command list element frame");
@@ -746,12 +747,14 @@ processToken(const char *      const scriptText,
              struct script *   const scriptP,
              struct tokenSet * const tokenSetP) {
 
-    char * token;
     unsigned int const tokenLength = cursor - tokenStart;
+
+    char * token;
+
     MALLOCARRAY_NOFAIL(token, tokenLength + 1);
     memcpy(token, &scriptText[tokenStart], tokenLength);
     token[tokenLength] = '\0';
-    
+
     if (streq(token, ";")) {
         disposeOfCommandTokens(tokenSetP, scriptP);
         free(token);
@@ -778,7 +781,7 @@ parseScript(const char *     const scriptText,
         */
     bool quotedToken;
         /* Current token is a quoted string.  Meaningless if 'intoken'
-           is false 
+           is false
         */
     struct tokenSet tokenSet;
 
@@ -802,7 +805,7 @@ parseScript(const char *     const scriptText,
 
     while (scriptText[cursor] != '\0') {
         char const scriptChar = scriptText[cursor];
-        
+
         if (intoken) {
             if ((quotedToken && scriptChar == '"') ||
                 (!quotedToken && (isspace(scriptChar) || scriptChar == ';'))) {
@@ -834,7 +837,7 @@ parseScript(const char *     const scriptText,
                 }
             }
             ++cursor;
-        }            
+        }
     }
 
     if (intoken) {
@@ -878,7 +881,7 @@ getScript(struct cmdlineInfo const cmdline,
     pm_strfree(scriptText);
 }
 
-          
+
 
 static void
 doOneImage(FILE *          const ifP,
@@ -887,13 +890,13 @@ doOneImage(FILE *          const ifP,
     pixel ** pixels;
     pixval maxval;
     int rows, cols;
-    
+
     pixels = ppm_readppm(ifP, &cols, &rows, &maxval);
-    
+
     executeScript(scriptP, pixels, cols, rows, maxval);
-    
+
     ppm_writeppm(stdout, pixels, cols, rows, maxval, 0);
-    
+
     ppm_freearray(pixels, rows);
 }
 
diff --git a/editor/ppmfade b/editor/ppmfade
index 027fc793..dcd7bf26 100755
--- a/editor/ppmfade
+++ b/editor/ppmfade
@@ -41,6 +41,19 @@ exec perl -w -x -S -- "$0" "$@"
 ##############################################################################
 use strict;
 
+sub doVersionHack($) {
+    my ($argvR) = @_;
+
+    my $arg1 = $argvR->[0];
+
+    if (defined($arg1) && (($arg1 eq "--version") || ($arg1 eq "-version"))) {
+        my $termStatus = system('ppmmix', '--version');
+        exit($termStatus == 0 ? 0 : 1);
+    }
+}
+
+
+
 my $SPREAD =  1;
 my $SHIFT =   2;
 my $RELIEF =  3;
@@ -59,6 +72,7 @@ my $base_name = "fade";		# default base name of output files
 my $image = "ppm";		# default output storage format
 my $mode = $SPREAD;		# default fading mode
 
+doVersionHack(\@ARGV);
 
 my $n;  # argument number
 
@@ -98,8 +112,6 @@ for ($n = 0; $n < @ARGV; $n++) {
         $mode = $BLOCK;
     } elsif ("$ARGV[$n]" eq "-mix") {
         $mode = $MIX;
-    } elsif ($ARGV[$n] eq "-help" || $ARGV[$n] eq "-h") {
-        usage();
     } else {
         print "Unknown argument: $ARGV[$n]\n";
         exit 100;
diff --git a/editor/ppmlabel.c b/editor/ppmlabel.c
index 885d7d36..389ab59e 100644
--- a/editor/ppmlabel.c
+++ b/editor/ppmlabel.c
@@ -7,7 +7,7 @@
                   June 1995
 */
 
-#define _XOPEN_SOURCE   /* get M_PI in math.h */
+#define _XOPEN_SOURCE 500  /* get M_PI in math.h */
 
 #include <math.h>
 #include <string.h>
diff --git a/editor/ppmquant b/editor/ppmquant
index 57963982..fe8ca046 100755
--- a/editor/ppmquant
+++ b/editor/ppmquant
@@ -35,6 +35,19 @@ exec perl -w -x -S -- "$0" "$@"
 
 use strict;
 
+sub doVersionHack($) {
+    my ($argvR) = @_;
+
+    my $arg1 = $argvR->[0];
+
+    if (defined($arg1) && (($arg1 eq "--version") || ($arg1 eq "-version"))) {
+        my $termStatus = system('pnmquant', '--version');
+        exit($termStatus == 0 ? 0 : 1);
+    }
+}
+
+doVersionHack(\@ARGV);
+
 use Getopt::Long;
 
 my @ppmquantArgv = @ARGV;
diff --git a/editor/ppmshadow b/editor/ppmshadow
index 62cdf8b8..ae6b1b0f 100755
--- a/editor/ppmshadow
+++ b/editor/ppmshadow
@@ -48,6 +48,7 @@ exec perl -w -x -S -- "$0" "$@"
 ##############################################################################
 
 use strict;
+use File::Temp;
 require 5.0;
 #  The good open() syntax, with the mode separate from the file name,
 #  came after 5.0.  So did mkdir() with default mode.
@@ -55,22 +56,60 @@ require 5.0;
 my $true=1; my $false=0;
 
 
-sub getDimensions($) {
+
+sub doVersionHack($) {
+    my ($argvR) = @_;
+
+    my $arg1 = $argvR->[0];
+
+    if (defined($arg1) && (($arg1 eq "--version") || ($arg1 eq "-version"))) {
+        my $termStatus = system('pamarith', '--version');
+        exit($termStatus == 0 ? 0 : 1);
+    }
+}
+
+
+
+sub imageDimensions($) {
     my ($fileName) = @_;
 #-----------------------------------------------------------------------------
-#  Return the dimensions of the Netpbm image in the named file
+#  Return the dimensions of the Netpbm image in the file named $fileName.
 #-----------------------------------------------------------------------------
-    my ($width, $height);
+    my ($width, $height, $depth);
     my $pamfileOutput = `pamfile $fileName`;
-    if ($pamfileOutput =~ m/.*\sP[BGP]M\s.*,\s*(\d*)\sby\s(\d*)/) {
-        ($width, $height) = ($1, $2);
+    if ($pamfileOutput =~
+            m/.*\sP[BGP]M\s.*,\s*(\d*)\sby\s(\d*)\s*maxval\s(\d*)/) {
+        ($width, $height, $depth) = ($1, $2, $3);
     } else {
         die("Unrecognized output from 'pamfile' shell command");
     }
-    return ($width, $height);
+    return ($width, $height, $depth);
+}    
+
+sub backgroundColor($) {
+    my ($fileName) = @_;
+#-----------------------------------------------------------------------------
+#  Return the color of the backround of the image in the file named $fileName.
+#-----------------------------------------------------------------------------
+    # We call the color of the top left pixel the background color.
+
+    my $ppmhistOut = qx{pamcut 0 0 1 1 $fileName | ppmhist -noheader -float};
+
+    my ($ired, $igrn, $iblu, $lum, $count);
+
+    if ($ppmhistOut =~
+        m{\s*([01].\d+)\s*([01].\d+)\s*([01].\d+)\s*([01].\d+)\s*(\d+)}) {
+        ($ired, $igrn, $iblu, $lum, $count) = ($1, $2, $3, $4, $5);
+    } else {
+        die("Unrecognized format of output from 'ppmhist' shell command");
+    }
+    my $irgb = sprintf("rgbi:%f/%f/%f", $ired, $igrn, $iblu);
+
+    return $irgb;
 }    
 
 
+
 sub makeConvolutionKernel($$) {
     my ($convkernelfile, $ckern) = @_;
 
@@ -95,15 +134,10 @@ sub makeConvolutionKernel($$) {
 #                           MAINLINE
 ##############################################################################
 
-
-my $tmpdir = $ENV{TMPDIR} || "/tmp";
-my $ourtmp = "$tmpdir/ppmshadow$$";
-mkdir($ourtmp, 0777) or
-    die("Unable to create directory for temporary files '$ourtmp");
+doVersionHack(\@ARGV);
 
 #   Process command line options
 
-
 my $ifile; # Input file name
 my ($xoffset, $yoffset);
 
@@ -113,6 +147,7 @@ my $translucent = $false;            # Default not translucent
 
 while (@ARGV) {
     my $arg = shift;
+
     if ((substr($arg, 0, 1) eq '-') && (length($arg) > 1)) {
         my $opt;
         $opt = substr($arg, 1, 1);
@@ -142,6 +177,8 @@ while (@ARGV) {
             if ($yoffset < 0) {
                 $yoffset = -$xoffset;
             }
+        } else {
+            die("Unknown option '$opt'\n");
         }
     } else {
         if (defined $ifile) {
@@ -151,6 +188,19 @@ while (@ARGV) {
     }
 }
 
+# Create temporary directory
+
+my $tmpdir = $ENV{TMPDIR} || "/tmp";
+my $ourtmp;
+
+if ($keeptemp) {
+    $ourtmp = "$tmpdir/ppmshadow$$";
+    mkdir($ourtmp, 0777) or
+        die("Unable to create directory for temporary files '$ourtmp");
+} else {
+    $ourtmp = File::Temp::tempdir("$tmpdir/ppmshadowXXXX", UNLINK=>1);
+}
+
 #   Apply defaults for arguments not specified
 
 if (!(defined $xoffset)) {
@@ -181,21 +231,25 @@ system("ppmtoppm");
 # seem to be able to open stdin and stdout pipes properly if stdin and 
 # stdout didn't already exist.  2002.09.07 BJH
 
-my ($sourceImageWidth, $sourceImageHeight) = getDimensions($infile);
+my ($sourceImageWidth, $sourceImageHeight, $sourceImageDepth) =
+    imageDimensions($infile);
+
+my $bgColorIrgb = backgroundColor($infile);
 
-#   Create an all-background-color image (same size as original image)
+# Create an all-background-color image (same size as original image),
+# named $backgroundfile. 
 
 my $backgroundfile = "$ourtmp/background.ppm";
-system("pamcut -left=0 -top=0 -width=1 -height=1 $infile | " .
-       "pamscale -xsize=$sourceImageWidth " .
-       "-ysize=$sourceImageHeight >$backgroundfile");
+system("ppmmake $bgColorIrgb $sourceImageWidth $sourceImageHeight " .
+    "-maxval $sourceImageDepth " .
+    ">$backgroundfile");
 
-#   Create mask file for background.  It is white wherever there is background
-#   image in the input.
+# Create mask file for background, named $bgmaskfile.  It is a PBM, white
+# wherever there is background image in the input.
 
 my $bgmaskfile = "$ourtmp/bgmask.pbm";
-system("pamarith -difference $infile $backgroundfile | pnminvert | ppmtopgm " .
-       "| pgmtopbm -thresh -value 1.0 >$bgmaskfile");
+system("ppmchange -remainder=black $bgColorIrgb white $infile | " .
+       "ppmtopgm | pgmtopbm -threshold -value=0.5 >$bgmaskfile"); 
 
 my $ckern = $convolve <= 11 ? $convolve : 11;
 
@@ -208,7 +262,7 @@ if ($translucent) {
     #   Convolve the input color image with the kernel
     #   to create a translucent shadow image.
 
-    system("pnmconvol $convkernelfile $infile >$ourtmp/blurred.ppm");
+    system("pnmconvol -quiet $convkernelfile $infile >$ourtmp/blurred.ppm");
     unlink("$convkernelfile") unless $keeptemp;
     while ($ckern < $convolve) {
         system("pnmsmooth $ourtmp/blurred.ppm >$ourtmp/convolvedx.ppm");
@@ -220,8 +274,8 @@ if ($translucent) {
     #   Convolve the positive mask with the kernel to create shadow
  
     my $blurredblackshadfile = "$ourtmp/blurredblackshad.pgm";
-    system("pamdepth -quiet 255 $bgmaskfile | " .
-           "pnmconvol $convkernelfile >$blurredblackshadfile");
+    system("pamdepth -quiet $sourceImageDepth $bgmaskfile | " .
+           "pnmconvol -quiet $convkernelfile >$blurredblackshadfile");
     unlink($convkernelfile) unless $keeptemp;
 
     while ($ckern < $convolve) {
@@ -251,21 +305,6 @@ my $shadowfile = "$ourtmp/shadow.ppm";
 }
 unlink("$ourtmp/blurred.ppm") unless $keeptemp;
 
-#   Make mask for foreground
-
-my $fgmaskfile = "$ourtmp/fgmask.pbm";
-open(STDIN, "<$bgmaskfile") or die();
-open(STDOUT, ">$fgmaskfile") or die();
-system("pnminvert");
-
-#   Make image which is just foreground; rest is black.
-
-my $justfgfile = "$ourtmp/justfg.ppm";
-open(STDOUT, ">$justfgfile") or die();
-system("pamarith", "-multiply", $infile, $fgmaskfile);
-
-unlink($fgmaskfile) unless $keeptemp;
-unlink($infile) unless $keeptemp;
 
 #   Paste shadow onto background.
 
@@ -276,22 +315,15 @@ system("pnmpaste", "-replace", $shadowfile, $xoffset, $yoffset,
 unlink($shadowfile) unless $keeptemp;
 unlink($backgroundfile) unless $keeptemp;
 
-#   Knock out (make black) foreground area
-
-my $allbutfgfile = "$ourtmp/allbutfg.ppm";
-open(STDOUT, ">$allbutfgfile") or die();
-system("pamarith", "-multiply", $shadbackfile, $bgmaskfile);
-
-unlink($shadbackfile) unless $keeptemp;
-unlink($bgmaskfile) unless $keeptemp;
 
-#   Place foreground in blacked out area, send to original Standard Output.
+#   Create composite file, send to original Standard Output.
 
 open(STDOUT, ">&OLDOUT");
 
-system("pamarith", "-add", $justfgfile, $allbutfgfile);
-unlink($justfgfile) unless $keeptemp;
-unlink($allbutfgfile) unless $keeptemp;
+system("pamcomp -invert -alpha $bgmaskfile $infile $shadbackfile");
+unlink($bgmaskfile) unless $keeptemp;
+unlink($infile) unless $keeptemp;
+unlink($shadbackfile) unless $keeptemp;
 
 if (!$keeptemp) {
     rmdir($ourtmp) or die ("Unable to remove temporary directory '$ourtmp'");
diff --git a/editor/ppmshadow.doc b/editor/ppmshadow.doc
index 1539c708..879faa66 100644
--- a/editor/ppmshadow.doc
+++ b/editor/ppmshadow.doc
@@ -1,3 +1,7 @@
+This file is a concatenation of two HTML files that John Walker wrote for the
+original 'pnmshadow' program.  It is not all relevant to the current
+'ppmshadow' program.
+
 <html>
 <head>
 <title>pnmshadow: How it Works</title>
diff --git a/editor/specialty/Makefile b/editor/specialty/Makefile
index 427c2c8f..8d9ca044 100644
--- a/editor/specialty/Makefile
+++ b/editor/specialty/Makefile
@@ -41,12 +41,14 @@ OBJECTS = $(BINARIES:%=%.o)
 
 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
@@ -55,3 +57,7 @@ install.bin.local: $(PKGDIR)/bin
 	cd $(PKGDIR)/bin ; \
 	rm -f pgmoil$(EXE) ; \
 	$(SYMLINK) pamoil$(EXE) pgmoil$(EXE)
+
+mergecomptrylist:
+	cat /dev/null >$@
+	echo "TRY(\"pgmoil\", main_pamoil);" >>$@
diff --git a/editor/specialty/pammixinterlace.c b/editor/specialty/pammixinterlace.c
index 7410a8f1..28dace25 100644
--- a/editor/specialty/pammixinterlace.c
+++ b/editor/specialty/pammixinterlace.c
@@ -2,7 +2,7 @@
                              pammixinterlace
 *******************************************************************************
   De-interlace an image by merging adjacent rows.
-   
+
   Copyright (C) 2007 Bruce Guenter, FutureQuest, Inc.
 
   Permission to use, copy, modify, and distribute this software and its
@@ -14,6 +14,7 @@
 
 ******************************************************************************/
 
+#define _DEFAULT_SOURCE /* New name for SVID & BSD source defines */
 #define _BSD_SOURCE    /* Make sure strcaseeq() is in nstring.h */
 
 #include <string.h>
@@ -36,9 +37,9 @@ clamp(sample const val,
 
 
 static bool
-distant(long const above,
-        long const mid,
-        long const below) {
+distant(int const above,
+        int const mid,
+        int const below) {
 
     return abs(mid - (above + below) / 2) > abs(above - below);
 }
@@ -59,9 +60,9 @@ filterLinearBlend(tuple *      const outputrow,
         unsigned int plane;
 
         for (plane = 0; plane < depth; ++plane) {
-            long const above = tuplerowWindow[0][col][plane];
-            long const mid   = tuplerowWindow[1][col][plane];
-            long const below = tuplerowWindow[2][col][plane];
+            int const above = tuplerowWindow[0][col][plane];
+            int const mid   = tuplerowWindow[1][col][plane];
+            int const below = tuplerowWindow[2][col][plane];
 
             sample out;
 
@@ -69,7 +70,7 @@ filterLinearBlend(tuple *      const outputrow,
                 out = (above + mid * 2 + below) / 4;
             else
                 out = mid;
-            
+
             outputrow[col][plane] = out;
         }
     }
@@ -86,23 +87,23 @@ filterFfmpeg(tuple *      const outputrow,
              sample       const maxval) {
 
     unsigned int col;
-    
+
     for (col = 0; col < width; ++col) {
         unsigned int plane;
-        
+
         for (plane = 0; plane < depth; ++plane) {
-            long const above = tuplerowWindow[1][col][plane];
-            long const mid   = tuplerowWindow[2][col][plane];
-            long const below = tuplerowWindow[3][col][plane];
+            int const above = tuplerowWindow[1][col][plane];
+            int const mid   = tuplerowWindow[2][col][plane];
+            int const below = tuplerowWindow[3][col][plane];
 
             sample out;
-            
+
             if (!adaptive || distant(above, mid, below)) {
-                long const a = (- (long)tuplerowWindow[0][col][plane]
+                int const a = (- (int)tuplerowWindow[0][col][plane]
                                 + above * 4
                                 + mid * 2
                                 + below * 4
-                                - (long)tuplerowWindow[4][col][plane]) / 8;
+                                - (int)tuplerowWindow[4][col][plane]) / 8;
                 out = clamp(a, maxval);
             } else
                 out = mid;
@@ -129,22 +130,22 @@ filterFIR(tuple *      const outputrow,
 
         for (plane = 0; plane < depth; ++plane) {
 
-            long const above = tuplerowWindow[1][col][plane];
-            long const mid   = tuplerowWindow[2][col][plane];
-            long const below = tuplerowWindow[3][col][plane];
+            int const above = tuplerowWindow[1][col][plane];
+            int const mid   = tuplerowWindow[2][col][plane];
+            int const below = tuplerowWindow[3][col][plane];
 
             sample out;
 
             if (!adaptive || distant(above, mid, below)) {
-                long const a = (- (long)tuplerowWindow[0][col][plane]
+                int const a = (- (int)tuplerowWindow[0][col][plane]
                                 + above * 2
                                 + mid * 6
                                 + below * 2
-                                - (long)tuplerowWindow[4][col][plane]) / 8;
+                                - (int)tuplerowWindow[4][col][plane]) / 8;
                 out = clamp(a, maxval);
             } else
                 out = mid;
-            
+
             outputrow[col][plane] = out;
         }
     }
@@ -217,7 +218,7 @@ parseCommandLine(int argc, char ** argv,
         if (!cmdlineP->filterP)
             pm_error("The filter name '%s' is not known.", filterName);
     }
-    
+
     if (argc-1 < 1)
         cmdlineP->inputFileName = "-";
     else if (argc-1 == 1)
@@ -279,12 +280,12 @@ main(int argc, char *argv[]) {
 
     FILE * ifP;
     struct cmdlineInfo cmdline;
-    struct pam inpam;  
+    struct pam inpam;
     struct pam outpam;
     tuple * tuplerowWindow[5];
     tuple * outputrow;
     unsigned int rows;
-    
+
     pnm_init(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
@@ -292,7 +293,7 @@ main(int argc, char *argv[]) {
     rows = cmdline.filterP->rows;
 
     ifP = pm_openr(cmdline.inputFileName);
-    
+
     pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
 
     outpam = inpam;    /* Initial value -- most fields should be same */
@@ -327,10 +328,10 @@ main(int argc, char *argv[]) {
                                 inpam.width, inpam.depth,
                                 cmdline.adaptive, inpam.maxval);
         pnm_writepamrow(&outpam, outputrow);
-        
+
         slideWindowDown(tuplerowWindow, rows);
     }
-    
+
     /* Pass through last rows */
     for (row = rows/2; row < rows-1; ++row)
         pnm_writepamrow(&outpam, tuplerowWindow[row]);
@@ -340,6 +341,9 @@ main(int argc, char *argv[]) {
     pnm_freepamrow(outputrow);
     pm_close(inpam.file);
     pm_close(outpam.file);
-    
+
     return 0;
 }
+
+
+
diff --git a/editor/specialty/pampaintspill.c b/editor/specialty/pampaintspill.c
index 745c9b68..eb1888f7 100644
--- a/editor/specialty/pampaintspill.c
+++ b/editor/specialty/pampaintspill.c
@@ -253,7 +253,7 @@ locatePaintSources(struct pam *            const pamP,
     if (downsample > 0 && downsample < paintSources.size) {
         unsigned int i;
 
-        srand(time(NULL));
+        srand(pm_randseed());
 
         for (i = 0; i < downsample; ++i) {
             unsigned int const swapIdx =
diff --git a/editor/specialty/pgmabel.c b/editor/specialty/pgmabel.c
index 386f0535..0f4233ac 100644
--- a/editor/specialty/pgmabel.c
+++ b/editor/specialty/pgmabel.c
@@ -170,7 +170,6 @@ int main( argc, argv )
     float pixsize=0.1;
     /* no verbose, calculating both sides                                */
     int verb = FALSE, left = TRUE, right = TRUE;
-    int nologo = FALSE;
     const char* const usage = "[-help] [-axis N] [-factor N] [-pixsize N] [-left|-right] [-verbose] [pgmfile]";
 
     pgm_init( &argc, argv );
@@ -212,10 +211,6 @@ int main( argc, argv )
                 if ( right ) left = FALSE;
                 else pm_usage( usage );
             }
-        else if ( pm_keymatch( argv[argn], "-nologo", 4 ) )
-            {
-                nologo = TRUE;
-            }
         else
             pm_usage( usage );
         ++ argn;
diff --git a/editor/specialty/pnmindex.c b/editor/specialty/pnmindex.c
index ca72633b..438fe058 100644
--- a/editor/specialty/pnmindex.c
+++ b/editor/specialty/pnmindex.c
@@ -14,6 +14,7 @@
 
 ============================================================================*/
 
+#define _DEFAULT_SOURCE /* New name for SVID & BSD source defines */
 #define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
 #define _BSD_SOURCE   /* Make sure strdup is in string.h */
 
diff --git a/editor/specialty/pnmmercator.c b/editor/specialty/pnmmercator.c
index cd9ff19b..81f7f148 100644
--- a/editor/specialty/pnmmercator.c
+++ b/editor/specialty/pnmmercator.c
@@ -16,7 +16,7 @@
 **
 */
 
-#define _XOPEN_SOURCE  /* Make sure M_PI is in <math.h> */
+#define _XOPEN_SOURCE 500  /* get M_PI in math.h */
 #include <math.h>
 #include <string.h>
 
diff --git a/editor/specialty/ppmglobe.c b/editor/specialty/ppmglobe.c
index 92e22746..bb043cc6 100644
--- a/editor/specialty/ppmglobe.c
+++ b/editor/specialty/ppmglobe.c
@@ -9,7 +9,7 @@
  */
 
 
-#define _XOPEN_SOURCE  /* get M_PI in math.h */
+#define _XOPEN_SOURCE 500  /* get M_PI in math.h */
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
diff --git a/editor/specialty/ppmntsc.c b/editor/specialty/ppmntsc.c
index a721b891..08fbc835 100644
--- a/editor/specialty/ppmntsc.c
+++ b/editor/specialty/ppmntsc.c
@@ -39,6 +39,7 @@
 
  */
 
+#define _DEFAULT_SOURCE 1  /* New name for SVID & BSD source defines */
 #define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
 #define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
 
@@ -392,14 +393,11 @@ convertOneImage(FILE *             const ifP,
         pixel * const inputRow = ppm_allocrow(cols);
         pixel * const outputRow = ppm_allocrow(cols);
 
-        pixel lastIllegalPixel;
-            /* Value of the illegal pixel we most recently processed */
         pixel black;
             /* A constant - black pixel */
 
         PPM_ASSIGN(black, 0, 0, 0);
 
-        PPM_ASSIGN(lastIllegalPixel, 0, 0, 0);  /* initial value */
         {
             unsigned int row;