about summary refs log tree commit diff
path: root/editor
diff options
context:
space:
mode:
Diffstat (limited to 'editor')
-rw-r--r--editor/Makefile33
-rw-r--r--editor/pamaddnoise.c644
-rw-r--r--editor/pamaltsat.c10
-rw-r--r--editor/pambackground.c52
-rw-r--r--editor/pamcat.c1461
-rw-r--r--editor/pamcomp.c436
-rw-r--r--editor/pamcut.c120
-rw-r--r--editor/pamdice.c158
-rw-r--r--editor/pamditherbw.c429
-rw-r--r--editor/pamenlarge.c1
-rw-r--r--editor/pamflip/pamflip.c2
-rw-r--r--editor/pamfunc.c28
-rw-r--r--editor/pamhomography.c799
-rw-r--r--editor/pamlevels.c3
-rw-r--r--editor/pammixmulti.c88
-rw-r--r--editor/pamperspective.c2
-rw-r--r--editor/pamrecolor.c50
-rw-r--r--editor/pamrestack.c472
-rw-r--r--editor/pamrubber.c638
-rw-r--r--editor/pamshuffle.c155
-rw-r--r--editor/pamsistoaglyph.c3
-rwxr-xr-xeditor/pamstretch-gen6
-rw-r--r--editor/pamthreshold.c31
-rw-r--r--editor/pamundice.c459
-rw-r--r--editor/pbmclean.c8
-rw-r--r--editor/pbmpscale.c30
-rw-r--r--editor/pbmreduce.c49
-rw-r--r--editor/pgmmedian.c311
-rw-r--r--editor/pnmcat.c872
-rw-r--r--editor/pnmconvol.c6
-rw-r--r--editor/pnmcrop.c174
-rwxr-xr-xeditor/pnmflip10
-rw-r--r--editor/pnmgamma.c2
-rwxr-xr-xeditor/pnmindex.csh189
-rwxr-xr-xeditor/pnmindex.sh214
-rwxr-xr-xeditor/pnmmargin6
-rw-r--r--editor/pnmnlfilt.c113
-rw-r--r--editor/pnmnorm.c158
-rw-r--r--editor/pnmpad.c60
-rwxr-xr-xeditor/pnmquant77
-rwxr-xr-xeditor/pnmquantall24
-rw-r--r--editor/pnmremap.c26
-rw-r--r--editor/pnmshear.c74
-rw-r--r--editor/pnmstitch.c12
-rwxr-xr-xeditor/ppmbrighten60
-rw-r--r--editor/ppmbrighten.c218
-rwxr-xr-xeditor/ppmfade146
-rwxr-xr-xeditor/ppmshadow4
-rw-r--r--editor/specialty/pampaintspill.c172
-rw-r--r--editor/specialty/pampop9.c3
-rw-r--r--editor/specialty/pgmabel.c18
-rw-r--r--editor/specialty/pnmindex.c106
-rw-r--r--editor/specialty/ppmshift.c235
-rw-r--r--editor/specialty/ppmspread.c206
54 files changed, 6021 insertions, 3642 deletions
diff --git a/editor/Makefile b/editor/Makefile
index 5b12e4ca..0027832c 100644
--- a/editor/Makefile
+++ b/editor/Makefile
@@ -16,23 +16,24 @@ SUBDIRS = pamflip specialty
 # This package is so big, it's useful even when some parts won't 
 # build.
 
-PORTBINARIES = pamaddnoise pamaltsat pambackground pambrighten pamcomp pamcut \
-	       pamdice pamditherbw pamedge \
-	       pamenlarge \
-	       pamfunc pamhue pamlevels pammasksharpen pammixmulti \
-	       pamperspective pamrecolor pamrubber \
-	       pamscale pamsistoaglyph pamstretch pamthreshold pamundice \
-	       pamwipeout \
+PORTBINARIES = pamaddnoise pamaltsat pambackground pambrighten \
+	       pamcat pamcomp pamcut \
+	       pamdice pamditherbw pamedge pamenlarge \
+	       pamfunc pamhomography pamhue pamlevels \
+	       pammasksharpen pammixmulti \
+	       pamperspective pamrecolor pamrestack pamrubber \
+	       pamscale pamshuffle pamsistoaglyph pamstretch pamthreshold \
+	       pamundice pamwipeout \
 	       pbmclean pbmmask pbmpscale pbmreduce \
 	       pgmdeshadow pgmenhance \
 	       pgmmedian \
-	       pnmalias pnmcat pnmconvol pnmcrop \
+	       pnmalias pnmconvol pnmcrop \
 	       pnmgamma \
 	       pnmhisteq pnminvert pnmmontage \
 	       pnmnlfilt pnmnorm pnmpad pnmpaste \
 	       pnmremap pnmrotate \
 	       pnmscalefixed pnmshear pnmsmooth pnmstitch pnmtile \
-	       ppmbrighten ppmchange ppmcolormask \
+	       ppmchange ppmcolormask \
 	       ppmdim ppmdist ppmdither ppmdraw \
 	       ppmflash ppmlabel ppmmix \
 
@@ -44,7 +45,7 @@ NOMERGEBINARIES =
 MERGEBINARIES = $(PORTBINARIES)
 
 BINARIES = $(MERGEBINARIES) $(NOMERGEBINARIES)
-SCRIPTS = pnmflip ppmfade ppmquant ppmshadow \
+SCRIPTS = ppmbrighten pnmflip ppmfade ppmquant ppmshadow \
 	  pamstretch-gen pnmmargin pnmquant pnmquantall 
 
 OBJECTS = $(BINARIES:%=%.o)
@@ -63,10 +64,15 @@ install.bin install.merge: install.bin.local
 .PHONY: install.bin.local
 install.bin.local: $(PKGDIR)/bin
 # Remember that $(SYMLINK) might just be a copy command.
-# backward compatibility: program used to be pnminterp
+
+# In December 2001, pamstretch replaced pnminterp and pamstretch-getn
+# replaced pnminterp-gen
 	cd $(PKGDIR)/bin ; \
 	rm -f pnminterp$(EXE); \
 	$(SYMLINK) pamstretch$(EXE) pnminterp$(EXE)
+	cd $(PKGDIR)/bin ; \
+	rm -f pnminterp-gen$(EXE); \
+	$(SYMLINK) pamstretch-gen$(EXE) pnminterp-gen$(EXE)
 # In March 2002, pnmnorm replaced ppmnorm and pgmnorm
 	cd $(PKGDIR)/bin ; \
 	rm -f ppmnorm$(EXE) ; \
@@ -100,6 +106,10 @@ install.bin.local: $(PKGDIR)/bin
 	cd $(PKGDIR)/bin ; \
 	rm -f pnmcomp$(EXE) ; \
 	$(SYMLINK) pamcomp$(EXE) pnmcomp$(EXE)
+# In August 2022, pamcat replaced pnmcat
+	cd $(PKGDIR)/bin ; \
+	rm -f pnmcat$(EXE) ; \
+	$(SYMLINK) pamcat$(EXE) pnmcat$(EXE)
 
 mergecomptrylist:
 	cat /dev/null >$@
@@ -111,4 +121,5 @@ mergecomptrylist:
 	echo "TRY(\"pnmcut\",     main_pamcut);"     >>$@
 	echo "TRY(\"pnmscale\",   main_pamscale);"   >>$@
 	echo "TRY(\"pnmcomp\",    main_pamcomp);"    >>$@
+	echo "TRY(\"pnmcat\",     main_pamcomp);"    >>$@
 
diff --git a/editor/pamaddnoise.c b/editor/pamaddnoise.c
index ccfde0b6..9ca80394 100644
--- a/editor/pamaddnoise.c
+++ b/editor/pamaddnoise.c
@@ -1,8 +1,8 @@
 /*
-** 
-** Add gaussian, multiplicative gaussian, impulse, laplacian or 
-** poisson noise to a portable anymap.
-** 
+**
+** Add gaussian, multiplicative gaussian, impulse, laplacian or
+** poisson noise to a Netpbm image
+**
 ** Version 1.0  November 1995
 **
 ** Copyright (C) 1995 by Mike Burns (burns@cac.psu.edu)
@@ -28,33 +28,198 @@
 
 #define _XOPEN_SOURCE 500  /* get M_PI in math.h */
 
+#include <assert.h>
 #include <math.h>
 
 #include "pm_c_util.h"
+#include "mallocvar.h"
+#include "rand.h"
+#include "shhopt.h"
+#include "pm_gamma.h"
 #include "pam.h"
 
-#define RANDOM_MASK 0x7FFF  /* only compare lower 15 bits.  Stupid PCs. */
-
 static double const EPSILON = 1.0e-5;
-static double const arand = 32767.0;      /* 2^15-1 in case stoopid computer */
-
-enum noiseType {
-    GAUSSIAN,
-    IMPULSE,  /* aka salt and pepper noise */
-    LAPLACIAN,
-    MULTIPLICATIVE_GAUSSIAN,
-    POISSON,
-    MAX_NOISE_TYPES
+static double const SALT_RATIO = 0.5;
+
+
+
+static double
+rand1(struct pm_randSt * const randStP) {
+
+    return (double)pm_rand(randStP)/RAND_MAX;
+}
+
+
+
+enum NoiseType {
+    NOISETYPE_GAUSSIAN,
+    NOISETYPE_IMPULSE,  /* aka salt and pepper noise */
+    NOISETYPE_LAPLACIAN,
+    NOISETYPE_MULTIPLICATIVE_GAUSSIAN,
+    NOISETYPE_POISSON
+};
+
+
+
+struct CmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;
+
+    enum NoiseType noiseType;
+
+    unsigned int seedSpec;
+    unsigned int seed;
+
+    float lambda;
+    float lsigma;
+    float mgsigma;
+    float sigma1;
+    float sigma2;
+    float tolerance;
 };
 
 
 
+static enum NoiseType
+typeFmName(const char * const name) {
+
+    enum NoiseType retval;
+
+    if (false)
+        assert(false);
+    else if (pm_keymatch(name, "gaussian", 1))
+        retval = NOISETYPE_GAUSSIAN;
+    else if (pm_keymatch(name, "impulse", 1))
+        retval = NOISETYPE_IMPULSE;
+    else if (pm_keymatch(name, "laplacian", 1))
+        retval = NOISETYPE_LAPLACIAN;
+    else if (pm_keymatch(name, "multiplicative_gaussian", 1))
+        retval = NOISETYPE_MULTIPLICATIVE_GAUSSIAN;
+    else if (pm_keymatch(name, "poisson", 1))
+        retval = NOISETYPE_POISSON;
+    else
+        pm_error("Unrecognized -type value '%s'.  "
+                 "We recognize 'gaussian', 'impulse', 'laplacian', "
+                 "'multiplicative_gaussian', and 'poisson'", name);
+
+    return retval;
+}
+
+
+
 static void
-gaussian_noise(sample   const maxval,
-               sample   const origSample,
-               sample * const newSampleP,
-               float    const sigma1,
-               float    const sigma2) {
+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.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options. */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int typeSpec, lambdaSpec, lsigmaSpec, mgsigmaSpec,
+        sigma1Spec, sigma2Spec, toleranceSpec;
+
+    const char * type;
+
+    MALLOCARRAY(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "type",            OPT_STRING,   &type,
+            &typeSpec,           0);
+    OPTENT3(0,   "seed",            OPT_UINT,     &cmdlineP->seed,
+            &cmdlineP->seedSpec, 0);
+    OPTENT3(0,   "lambda",          OPT_FLOAT,    &cmdlineP->lambda,
+            &lambdaSpec,         0);
+    OPTENT3(0,   "lsigma",          OPT_FLOAT,    &cmdlineP->lsigma,
+            &lsigmaSpec,         0);
+    OPTENT3(0,   "mgsigma",         OPT_FLOAT,    &cmdlineP->mgsigma,
+            &mgsigmaSpec,        0);
+    OPTENT3(0,   "sigma1",          OPT_FLOAT,    &cmdlineP->sigma1,
+            &sigma1Spec,         0);
+    OPTENT3(0,   "sigma2",          OPT_FLOAT,    &cmdlineP->sigma2,
+            &sigma2Spec,         0);
+    OPTENT3(0,   "tolerance",       OPT_FLOAT,    &cmdlineP->tolerance,
+            &toleranceSpec,      0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!typeSpec)
+        cmdlineP->noiseType = NOISETYPE_GAUSSIAN;
+    else
+        cmdlineP->noiseType = typeFmName(type);
+
+    if (sigma1Spec && cmdlineP->noiseType != NOISETYPE_GAUSSIAN)
+        pm_error("-sigma1 is valid only with -type=gaussian");
+
+    if (sigma2Spec && cmdlineP->noiseType != NOISETYPE_GAUSSIAN)
+        pm_error("-sigma2 is valid only with -type=gaussian");
+
+    if (mgsigmaSpec &&
+        cmdlineP->noiseType != NOISETYPE_MULTIPLICATIVE_GAUSSIAN)
+        pm_error("-mgsigma is valid only with -type=multiplicative_guassian");
+
+    if (toleranceSpec && cmdlineP->noiseType != NOISETYPE_IMPULSE)
+        pm_error("-tolerance is valid only with -type=impulse");
+
+    if (lsigmaSpec && cmdlineP->noiseType != NOISETYPE_LAPLACIAN)
+        pm_error("-lsigma is valid only with -type=laplacian");
+
+    if (lambdaSpec && cmdlineP->noiseType != NOISETYPE_POISSON)
+        pm_error("-lambda is valid only with -type=poisson");
+
+    if (!lambdaSpec)
+        cmdlineP->lambda = 12.0;
+
+    if (!lsigmaSpec)
+        cmdlineP->lsigma = 10.0;
+
+    if (!mgsigmaSpec)
+        cmdlineP->mgsigma = 0.5;
+
+    if (!sigma1Spec)
+        cmdlineP->sigma1 = 4.0;
+
+    if (!sigma2Spec)
+        cmdlineP->sigma2 = 20.0;
+
+    if (!toleranceSpec)
+        cmdlineP->tolerance = 0.10;
+
+    if (!cmdlineP->seedSpec)
+        cmdlineP->seed = pm_randseed();
+
+    if (argc-1 > 1)
+        pm_error("Too many arguments (%u).  File spec is the only argument.",
+                 argc-1);
+
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else
+        cmdlineP->inputFileName = argv[1];
+
+    free(option_def);
+}
+
+
+
+static void
+addGaussianNoise(sample             const maxval,
+                 sample             const origSample,
+                 sample *           const newSampleP,
+                 float              const sigma1,
+                 float              const sigma2,
+                 struct pm_randSt * const randStP) {
 /*----------------------------------------------------------------------------
    Add Gaussian noise.
 
@@ -64,14 +229,14 @@ gaussian_noise(sample   const maxval,
     double x1, x2, xn, yn;
     double rawNewSample;
 
-    x1 = (rand() & RANDOM_MASK) / arand; 
+    x1 = rand1(randStP);
 
     if (x1 == 0.0)
         x1 = 1.0;
-    x2 = (rand() & RANDOM_MASK) / arand;
+    x2 = rand1(randStP);
     xn = sqrt(-2.0 * log(x1)) * cos(2.0 * M_PI * x2);
     yn = sqrt(-2.0 * log(x1)) * sin(2.0 * M_PI * x2);
-    
+
     rawNewSample =
         origSample + (sqrt((double) origSample) * sigma1 * xn) + (sigma2 * yn);
 
@@ -81,41 +246,43 @@ gaussian_noise(sample   const maxval,
 
 
 static void
-impulse_noise(sample   const maxval,
-              sample   const origSample,
-              sample * const newSampleP,
-              float    const tolerance) {
+addImpulseNoise(sample             const maxval,
+                sample             const origSample,
+                sample *           const newSampleP,
+                float              const tolerance,
+                double             const saltRatio,
+                struct pm_randSt * const randStP) {
 /*----------------------------------------------------------------------------
    Add impulse (salt and pepper) noise
 -----------------------------------------------------------------------------*/
 
-    double const low_tol  = tolerance / 2.0;
-    double const high_tol = 1.0 - (tolerance / 2.0);
-    double const sap = (rand() & RANDOM_MASK) / arand; 
+    double const pepperRatio = 1.0 - saltRatio;
+    double const loTolerance = tolerance * pepperRatio;
+    double const hiTolerance = 1.0 - tolerance * saltRatio;
+    double const sap         = rand1(randStP);
 
-    if (sap < low_tol) 
-        *newSampleP = 0;
-    else if ( sap >= high_tol )
-        *newSampleP = maxval;
-    else
-        *newSampleP = origSample;
+    *newSampleP =
+        sap < loTolerance ? 0 :
+        sap >= hiTolerance? maxval :
+        origSample;
 }
 
 
 
 static void
-laplacian_noise(sample   const maxval,
-                double   const infinity,
-                sample   const origSample,
-                sample * const newSampleP,
-                float    const lsigma) {
+addLaplacianNoise(sample             const maxval,
+                  double             const infinity,
+                  sample             const origSample,
+                  sample *           const newSampleP,
+                  float              const lsigma,
+                  struct pm_randSt * const randStP) {
 /*----------------------------------------------------------------------------
    Add Laplacian noise
 
    From Pitas' book.
 -----------------------------------------------------------------------------*/
-    double const u = (rand() & RANDOM_MASK) / arand; 
-                
+    double const u = rand1(randStP);
+
     double rawNewSample;
 
     if (u <= 0.5) {
@@ -136,11 +303,12 @@ laplacian_noise(sample   const maxval,
 
 
 static void
-multiplicative_gaussian_noise(sample   const maxval,
-                              double   const infinity,
-                              sample   const origSample,
-                              sample * const newSampleP,
-                              float    const mgsigma) {
+addMultiplicativeGaussianNoise(sample             const maxval,
+                               double             const infinity,
+                               sample             const origSample,
+                               sample *           const newSampleP,
+                               float              const mgsigma,
+                               struct pm_randSt * const randStP) {
 /*----------------------------------------------------------------------------
    Add multiplicative Gaussian noise
 
@@ -150,14 +318,14 @@ multiplicative_gaussian_noise(sample   const maxval,
     double rawNewSample;
 
     {
-        double const uniform = (rand() & RANDOM_MASK) / arand; 
+        double const uniform = rand1(randStP);
         if (uniform <= EPSILON)
             rayleigh = infinity;
         else
             rayleigh = sqrt(-2.0 * log( uniform));
     }
     {
-        double const uniform = (rand() & RANDOM_MASK) / arand; 
+        double const uniform = rand1(randStP);
         gauss = rayleigh * cos(2.0 * M_PI * uniform);
     }
     rawNewSample = origSample + (origSample * mgsigma * gauss);
@@ -166,262 +334,106 @@ multiplicative_gaussian_noise(sample   const maxval,
 }
 
 
+static double
+poissonPmf(double       const lambda,
+           unsigned int const k) {
+/*----------------------------------------------------------------------------
+   This is the probability mass function (PMF) of a discrete random variable
+   with lambda 'lambda'.
+
+   I.e. it gives the probability that a value sampled from a Poisson
+   distribution with lambda 'lambda' has the value 'k'.
+
+   That means it's the probability that in a Poisson stream of events in which
+   the mean number of events in an interval of a certains size is 'lambda' that
+   'k' events happen.
+-----------------------------------------------------------------------------*/
+    double x;
+    unsigned int i;
+
+    /* We're computing the formula
+
+         (pow(lamda, k) * exp(-lambda)) / fact(k).
+
+       Note that k is ordinarily quite small.
+    */
+
+    x = exp(-lambda);
+
+    for (i = 1; i <= k; ++i) {
+        x *= lambda;
+        x /= i;
+    }
+    return x;
+}
+
+
 
 static void
-poisson_noise(sample   const maxval,
-              sample   const origSample,
-              sample * const newSampleP,
-              float    const lambda) {
+addPoissonNoise(struct pam *       const pamP,
+                sample             const origSample,
+                sample *           const newSampleP,
+                float              const lambdaOfMaxval,
+                struct pm_randSt * const randStP) {
 /*----------------------------------------------------------------------------
    Add Poisson noise
 -----------------------------------------------------------------------------*/
-    double const x  = lambda * origSample;
-    double const x1 = exp(-x);
+    samplen const origSamplen = pnm_normalized_sample(pamP, origSample);
+
+    float const origSampleIntensity = pm_ungamma709(origSamplen);
+
+    double const lambda  = origSampleIntensity * lambdaOfMaxval;
+
+    double const u = rand1(randStP);
+
+    /* We now apply the inverse CDF (cumulative distribution function) of the
+       Poisson distribution to uniform random variable 'u' to get a Poisson
+       random variable.  Unfortunately, we have no algebraic equation for the
+       inverse of the CDF, but the random variable is discrete, so we can just
+       iterate.
+    */
 
-    double rawNewSample;
-    float rr;
     unsigned int k;
+    double cumProb;
+
+    for (k = 0, cumProb = 0.0; k < lambdaOfMaxval; ++k) {
+
+        cumProb += poissonPmf(lambda, k);
 
-    rr = 1.0;  /* initial value */
-    k = 0;     /* initial value */
-    rr = rr * ((rand() & RANDOM_MASK) / arand);
-    while (rr > x1) {
-        ++k;
-        rr = rr * ((rand() & RANDOM_MASK) / arand);
+        if (cumProb >= u)
+            break;
     }
-    rawNewSample = k / lambda;
 
-    *newSampleP = MIN(MAX((int)rawNewSample, 0), maxval);
+    {
+        samplen const newSamplen = pm_gamma709(k/lambdaOfMaxval);
+
+        *newSampleP = pnm_unnormalized_sample(pamP, newSamplen);
+    }
 }
 
 
 
-int 
-main(int argc, char * argv[]) {
+int
+main(int argc, const char ** argv) {
 
     FILE * ifP;
+    struct CmdlineInfo cmdline;
     struct pam inpam;
     struct pam outpam;
     tuple * tuplerow;
     const tuple * newtuplerow;
     unsigned int row;
     double infinity;
+    struct pm_randSt randSt;
 
-    int argn;
-    const char * inputFilename;
-    int noise_type;
-    unsigned int seed;
-    int i;
-    const char * const usage = "[-type noise_type] [-lsigma x] [-mgsigma x] "
-        "[-sigma1 x] [-sigma2 x] [-lambda x] [-seed n] "
-        "[-tolerance ratio] [pgmfile]";
-
-    const char * const noise_name[] = { 
-        "gaussian",
-        "impulse",
-        "laplacian",
-        "multiplicative_gaussian",
-        "poisson"
-    };
-    int const noise_id[] = { 
-        GAUSSIAN,
-        IMPULSE,
-        LAPLACIAN,
-        MULTIPLICATIVE_GAUSSIAN,
-        POISSON
-    };
-    /* minimum number of characters to match noise name for pm_keymatch() */
-    int const noise_compare[] = {
-        1,
-        1,
-        1,
-        1,
-        1
-    };
-
-    /* define default values for configurable options */
-    float lambda = 0.05;        
-    float lsigma = 10.0;
-    float mgsigma = 0.5;
-    float sigma1 = 4.0;
-    float sigma2 = 20.0;
-    float tolerance = 0.10;
-
-    pnm_init(&argc, argv);
-
-    seed = pm_randseed();
-    noise_type = GAUSSIAN;
-
-    argn = 1;
-    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
-    {
-        if ( pm_keymatch( argv[argn], "-lambda", 3 ) )
-        {
-            ++argn;
-            if ( argn >= argc )
-            {
-                pm_message( 
-                    "incorrect number of arguments for -lambda option" );
-                pm_usage( usage );
-            }
-            else if ( argv[argn][0] == '-' )
-            {
-                pm_message( "invalid argument to -lambda option: %s", 
-                            argv[argn] );
-                pm_usage( usage );
-            }
-            lambda = atof( argv[argn] );
-        }
-        else if ( pm_keymatch( argv[argn], "-lsigma", 3 ) )
-        {
-            ++argn;
-            if ( argn >= argc )
-            {
-                pm_message( 
-                    "incorrect number of arguments for -lsigma option" );
-                pm_usage( usage );
-            }
-            else if ( argv[argn][0] == '-' )
-            {
-                pm_message( "invalid argument to -lsigma option: %s", 
-                            argv[argn] );
-                pm_usage( usage );
-            }
-            lsigma = atof( argv[argn] );
-        }
-        else if ( pm_keymatch( argv[argn], "-mgsigma", 2 ) )
-        {
-            ++argn;
-            if ( argn >= argc )
-            {
-                pm_message( 
-                    "incorrect number of arguments for -mgsigma option" );
-                pm_usage( usage );
-            }
-            else if ( argv[argn][0] == '-' )
-            {
-                pm_message( "invalid argument to -mgsigma option: %s", 
-                            argv[argn] );
-                pm_usage( usage );
-            }
-            mgsigma = atof( argv[argn] );
-        }
-        else if ( pm_keymatch( argv[argn], "-seed", 3 ) )
-        {
-            ++argn;
-            if ( argn >= argc )
-            {
-                pm_message( "incorrect number of arguments for -seed option" );
-                pm_usage( usage );
-            }
-            else if ( argv[argn][0] == '-' )
-            {
-                pm_message( "invalid argument to -seed option: %s", 
-                            argv[argn] );
-                pm_usage( usage );
-            }
-            seed = atoi(argv[argn]);
-        }
-        else if ( pm_keymatch( argv[argn], "-sigma1", 7 ) ||
-                  pm_keymatch( argv[argn], "-s1", 3 ) )
-        {
-            ++argn;
-            if ( argn >= argc )
-            {
-                pm_message( 
-                    "incorrect number of arguments for -sigma1 option" );
-                pm_usage( usage );
-            }
-            else if ( argv[argn][0] == '-' )
-            {
-                pm_message( "invalid argument to -sigma1 option: %s", 
-                            argv[argn] );
-                pm_usage( usage );
-            }
-            sigma1 = atof( argv[argn] );
-        }
-        else if ( pm_keymatch( argv[argn], "-sigma2", 7 ) ||
-                  pm_keymatch( argv[argn], "-s2", 3 ) )
-        {
-            ++argn;
-            if ( argn >= argc )
-            {
-                pm_message( 
-                    "incorrect number of arguments for -sigma2 option" );
-                pm_usage( usage );
-            }
-            else if ( argv[argn][0] == '-' )
-            {
-                pm_message( "invalid argument to -sigma2 option: %s", 
-                            argv[argn] );
-                pm_usage( usage );
-            }
-            sigma2 = atof( argv[argn] );
-        }
-        else if ( pm_keymatch( argv[argn], "-tolerance", 3 ) )
-        {
-            ++argn;
-            if ( argn >= argc )
-            {
-                pm_message( 
-                    "incorrect number of arguments for -tolerance option" );
-                pm_usage( usage );
-            }
-            else if ( argv[argn][0] == '-' )
-            {
-                pm_message( "invalid argument to -tolerance option: %s", 
-                            argv[argn] );
-                pm_usage( usage );
-            }
-            tolerance = atof( argv[argn] );
-        }
-        else if ( pm_keymatch( argv[argn], "-type", 3 ) )
-        {
-            ++argn;
-            if ( argn >= argc )
-            {
-                pm_message( "incorrect number of arguments for -type option" );
-                pm_usage( usage );
-            }
-            else if ( argv[argn][0] == '-' )
-            {
-                pm_message( "invalid argument to -type option: %s", 
-                            argv[argn] );
-                pm_usage( usage );
-            }
-            /* search through list of valid noise types and compare */
-            i = 0;
-            while ( ( i < MAX_NOISE_TYPES ) && 
-                    !pm_keymatch( argv[argn], 
-                                  noise_name[i], noise_compare[i] ) )
-                ++i;
-            if ( i >= MAX_NOISE_TYPES )
-            {
-                pm_message( "invalid argument to -type option: %s", 
-                            argv[argn] );
-                pm_usage( usage );
-            }
-            noise_type = noise_id[i];
-        }
-        else
-            pm_usage( usage );
-        ++argn;
-    }
-
-    if ( argn < argc )
-    {
-        inputFilename = argv[argn];
-        argn++;
-    }
-    else
-        inputFilename = "-";
+    pm_proginit(&argc, argv);
 
-    if ( argn != argc )
-        pm_usage( usage );
+    parseCommandLine(argc, argv, &cmdline);
 
-    srand(seed);
+    pm_randinit(&randSt);
+    pm_srand2(&randSt, cmdline.seedSpec, cmdline.seed);
 
-    ifP = pm_openr(inputFilename);
+    ifP = pm_openr(cmdline.inputFileName);
 
     pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
 
@@ -430,50 +442,56 @@ main(int argc, char * argv[]) {
 
     pnm_writepaminit(&outpam);
 
-    tuplerow = pnm_allocpamrow(&inpam);
+    tuplerow    = pnm_allocpamrow(&inpam);
     newtuplerow = pnm_allocpamrow(&inpam);
+
     infinity = (double) inpam.maxval;
-    
+
     for (row = 0; row < inpam.height; ++row) {
         unsigned int col;
         pnm_readpamrow(&inpam, tuplerow);
         for (col = 0; col < inpam.width; ++col) {
             unsigned int plane;
             for (plane = 0; plane < inpam.depth; ++plane) {
-                switch (noise_type) {
-                case GAUSSIAN:
-                    gaussian_noise(inpam.maxval,
-                                   tuplerow[col][plane],
-                                   &newtuplerow[col][plane],
-                                   sigma1, sigma2);
+                switch (cmdline.noiseType) {
+                case NOISETYPE_GAUSSIAN:
+                    addGaussianNoise(inpam.maxval,
+                                     tuplerow[col][plane],
+                                     &newtuplerow[col][plane],
+                                     cmdline.sigma1, cmdline.sigma2,
+                                     &randSt);
                     break;
-                    
-                case IMPULSE:
-                    impulse_noise(inpam.maxval,
-                                  tuplerow[col][plane],
-                                  &newtuplerow[col][plane],
-                                  tolerance);
-                   break;
-                    
-                case LAPLACIAN:
-                    laplacian_noise(inpam.maxval, infinity,
+
+                case NOISETYPE_IMPULSE:
+                    addImpulseNoise(inpam.maxval,
                                     tuplerow[col][plane],
                                     &newtuplerow[col][plane],
-                                    lsigma);
+                                    cmdline.tolerance, SALT_RATIO,
+                                    &randSt);
+                   break;
+
+                case NOISETYPE_LAPLACIAN:
+                    addLaplacianNoise(inpam.maxval, infinity,
+                                      tuplerow[col][plane],
+                                      &newtuplerow[col][plane],
+                                      cmdline.lsigma,
+                                      &randSt);
                     break;
-                    
-                case MULTIPLICATIVE_GAUSSIAN:
-                    multiplicative_gaussian_noise(inpam.maxval, infinity,
-                                                  tuplerow[col][plane],
-                                                  &newtuplerow[col][plane],
-                                                  mgsigma);
+
+                case NOISETYPE_MULTIPLICATIVE_GAUSSIAN:
+                    addMultiplicativeGaussianNoise(inpam.maxval, infinity,
+                                                   tuplerow[col][plane],
+                                                   &newtuplerow[col][plane],
+                                                   cmdline.mgsigma,
+                                                   &randSt);
                     break;
-                    
-                case POISSON:
-                    poisson_noise(inpam.maxval,
-                                  tuplerow[col][plane],
-                                  &newtuplerow[col][plane],
-                                  lambda);
+
+                case NOISETYPE_POISSON:
+                    addPoissonNoise(&inpam,
+                                    tuplerow[col][plane],
+                                    &newtuplerow[col][plane],
+                                    cmdline.lambda,
+                                    &randSt);
                     break;
 
                 }
@@ -481,8 +499,12 @@ main(int argc, char * argv[]) {
         }
         pnm_writepamrow(&outpam, newtuplerow);
     }
+    pm_randterm(&randSt);
     pnm_freepamrow(newtuplerow);
     pnm_freepamrow(tuplerow);
 
     return 0;
 }
+
+
+
diff --git a/editor/pamaltsat.c b/editor/pamaltsat.c
index 6d9b91e0..3bff55bd 100644
--- a/editor/pamaltsat.c
+++ b/editor/pamaltsat.c
@@ -160,7 +160,7 @@ typedef double (binsearchFunc)(double       const x,
 
 /* 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>. */
+   precision <prec>. <dataP> is an arbitrary parameter to <func>. */
 static double
 binsearch(binsearchFunc       func,
           const void  * const dataP,
@@ -299,7 +299,7 @@ typedef struct {
     TupleD *        tupsatP;
         /* saturated color                            */
     double *        intRatioP;
-        /* ratio of orignal and saturated intensities */
+        /* ratio of original and saturated intensities */
 } MaxLogSatInfo;
 
 
@@ -346,7 +346,7 @@ getMaxLogSat(LinSampleInfo * const siP,
 /*  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. */
+    overflow. */
     binsearch(binsearchMaxLogSat, &info, PREC, 1.0, upperLimit, 1.0 - PREC);
 }
 
@@ -399,11 +399,11 @@ saturateSpectrum(LinSampleInfo * const siP,
     else {
         double const km1 =
             (1.0 - siP->intensity)/(siP->maxval - siP->intensity);
-            /* Maximum saturation factor that keeps maximum layer intesity
+            /* Maximum saturation factor that keeps maximum layer intensity
                within range
             */
         double const km2 = siP->intensity/(siP->intensity - sample[siP->minl]);
-            /* Maximum saturation factor  that keeps minimum layer intesity
+            /* Maximum saturation factor  that keeps minimum layer intensity
                within range
             */
 
diff --git a/editor/pambackground.c b/editor/pambackground.c
index b4941042..218f5b7e 100644
--- a/editor/pambackground.c
+++ b/editor/pambackground.c
@@ -4,19 +4,19 @@
 #include "shhopt.h"
 #include "pam.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 * inputFileName;  
+    const char * inputFileName;
     unsigned int verbose;
 };
 
 
 
 static void
-parseCommandLine(int argc, char ** const 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.
@@ -37,7 +37,7 @@ parseCommandLine(int argc, char ** const argv,
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
     opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
 
-    pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
     if (argc-1 < 1)
@@ -48,7 +48,7 @@ parseCommandLine(int argc, char ** const argv,
             pm_error("There is at most one argument:  input file name.  "
                      "You specified %d", argc-1);
     }
-}        
+}
 
 
 
@@ -83,7 +83,7 @@ allocateOutputPointerRow(unsigned int const width,
 
 
 static void
-createWhiteTuple(const struct pam * const pamP, 
+createWhiteTuple(const struct pam * const pamP,
                  tuple *            const whiteTupleP) {
 /*----------------------------------------------------------------------------
    Create a "white" tuple.  By that we mean a tuple all of whose elements
@@ -134,7 +134,7 @@ selectBackground(struct pam * const pamP,
             bg = ul;
         }
     }
-    
+
     *bgColorP = pnm_allocpamtuple(pamP);
     pnm_assigntuple(pamP, *bgColorP, bg);
 }
@@ -301,8 +301,8 @@ expandBackgroundHoriz(unsigned char ** const pi,
                       unsigned int     const height,
                       bool *           const expandedP) {
 /*----------------------------------------------------------------------------
-   In every row, expand the background rightward from any known background
-   pixel through all consecutive unknown pixels.
+   In every row except top and bottom, expand the background rightward from
+   any known background pixel through all consecutive unknown pixels.
 
    Then do the same thing leftward.
 
@@ -343,8 +343,9 @@ expandBackgroundVert(unsigned char ** const pi,
                      unsigned int     const height,
                      bool *           const expandedP) {
 /*----------------------------------------------------------------------------
-   In every column, expand the background downward from any known background
-   pixel through all consecutive unknown pixels.
+   In every column except leftmost and rightmost, expand the background
+   downward from any known background pixel through all consecutive unknown
+   pixels.
 
    Then do the same thing upward.
 
@@ -400,7 +401,7 @@ findBackgroundPixels(struct pam *                   const inpamP,
 -----------------------------------------------------------------------------*/
     unsigned char ** pi;
     bool backgroundComplete;
-    unsigned int passes;
+    unsigned int passCt;
 
     pi = newPi(inpamP->width, inpamP->height);
 
@@ -409,29 +410,29 @@ findBackgroundPixels(struct pam *                   const inpamP,
     setEdges(pi, inpamP->width, inpamP->height);
 
     backgroundComplete = FALSE;
-    passes = 0;
-    
+    passCt = 0;
+
     while (!backgroundComplete) {
         bool expandedHoriz, expandedVert;
 
         expandBackgroundHoriz(pi, inpamP->width, inpamP->height,
                               &expandedHoriz);
-    
+
         expandBackgroundVert(pi, inpamP->width, inpamP->height,
                              &expandedVert);
 
         backgroundComplete = !expandedHoriz && !expandedVert;
 
-        ++passes;
+        ++passCt;
     }
 
     if (verbose)
-        pm_message("Background found in %u passes", passes);
+        pm_message("Background found in %u passes", passCt);
 
     *piP = (const unsigned char * const *)pi;
 }
 
-                     
+
 
 static void
 writeOutput(const struct pam *            const inpamP,
@@ -468,16 +469,16 @@ writeOutput(const struct pam *            const inpamP,
 
 
 int
-main(int argc, char *argv[]) {
+main(int argc, const char *argv[]) {
 
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
     struct pam inpam;
     FILE * ifP;
     pm_filepos rasterpos;
     tuple backgroundColor;
     const unsigned char * const * pi;
-    
-    pnm_init(&argc, argv);
+
+    pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
 
@@ -500,6 +501,9 @@ main(int argc, char *argv[]) {
     pm_close(ifP);
 
     pnm_freepamtuple(backgroundColor);
-    
+
     return 0;
 }
+
+
+
diff --git a/editor/pamcat.c b/editor/pamcat.c
new file mode 100644
index 00000000..7823bdfc
--- /dev/null
+++ b/editor/pamcat.c
@@ -0,0 +1,1461 @@
+/*=============================================================================
+                                   pamcat
+===============================================================================
+
+  Concatenate images.
+
+  By Bryan Henderson and Akira Urushibata.  Contributed to the public domain
+  by its authors.
+
+=============================================================================*/
+
+#include <stdio.h>
+#include <limits.h>
+#include <assert.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "shhopt.h"
+#include "bitarith.h"
+#include "nstring.h"
+#include "pam.h"
+#include "pbm.h"
+
+#define LEFTBITS pm_byteLeftBits
+#define RIGHTBITS pm_byteRightBits
+
+enum PadColorMethod {PAD_BLACK, PAD_WHITE, PAD_AUTO};
+  /* The method of determining the color of padding when images are not the
+     same height or width.  Always white (maxval samples) always black (zero
+     samples) or determined from what looks like background for the image in
+     question.
+  */
+
+
+enum Orientation {TOPBOTTOM, LEFTRIGHT};
+  /* Direction of concatenation */
+
+enum Justification {JUST_CENTER, JUST_MIN, JUST_MAX};
+  /* Justification of images in concatenation */
+
+/* FOPEN_MAX is usually defined in stdio.h, PATH_MAX in limits.h
+   Given below are typical values.  Adjust as necessary.
+ */
+
+#ifndef FOPEN_MAX
+  #define FOPEN_MAX 16
+#endif
+
+#ifndef PATH_MAX
+  #define PATH_MAX 255
+#endif
+
+
+static const char **
+copyOfStringList(const char ** const list,
+                 unsigned int  const size) {
+
+    const char ** retval;
+    unsigned int i;
+
+    MALLOCARRAY_NOFAIL(retval, size);
+
+    for (i = 0; i < size; ++i)
+        retval[i] = pm_strdup(list[i]);
+
+    return retval;
+}
+
+
+
+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        inputFileCt;
+    const char *        listfile;  /* NULL if not specified */
+    enum PadColorMethod padColorMethod;
+    enum Orientation    orientation;
+    enum Justification  justification;
+    unsigned int        verbose;
+};
+
+
+static void
+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.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to OptParseOptions3() on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int leftright, topbottom;
+    unsigned int black, white;
+    unsigned int jtop, jbottom, jleft, jright, jcenter;
+    unsigned int listfileSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "leftright",   OPT_FLAG,   NULL, &leftright,         0);
+    OPTENT3(0, "lr",          OPT_FLAG,   NULL, &leftright,         0);
+    OPTENT3(0, "topbottom",   OPT_FLAG,   NULL, &topbottom,         0);
+    OPTENT3(0, "tb",          OPT_FLAG,   NULL, &topbottom,         0);
+    OPTENT3(0, "black",       OPT_FLAG,   NULL, &black,             0);
+    OPTENT3(0, "white",       OPT_FLAG,   NULL, &white,             0);
+    OPTENT3(0, "jtop",        OPT_FLAG,   NULL, &jtop,              0);
+    OPTENT3(0, "jbottom",     OPT_FLAG,   NULL, &jbottom,           0);
+    OPTENT3(0, "jleft",       OPT_FLAG,   NULL, &jleft,             0);
+    OPTENT3(0, "jright",      OPT_FLAG,   NULL, &jright,            0);
+    OPTENT3(0, "jcenter",     OPT_FLAG,   NULL, &jcenter,           0);
+    OPTENT3(0, "listfile",    OPT_STRING, &cmdlineP->listfile,
+            &listfileSpec,      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 */
+    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. */
+
+    free(option_def);
+
+    if (leftright + topbottom > 1)
+        pm_error("You may specify only one of -topbottom (-tb) and "
+                 "-leftright (-lr)");
+    else if (leftright)
+        cmdlineP->orientation = LEFTRIGHT;
+    else if (topbottom)
+        cmdlineP->orientation = TOPBOTTOM;
+    else
+        pm_error("You must specify either -leftright or -topbottom");
+
+    if (black + white > 1)
+        pm_error("You may specify only one of -black and -white");
+    else if (black)
+        cmdlineP->padColorMethod = PAD_BLACK;
+    else if (white)
+        cmdlineP->padColorMethod = PAD_WHITE;
+    else
+        cmdlineP->padColorMethod = PAD_AUTO;
+
+    if (jtop + jbottom + jleft + jright + jcenter > 1)
+        pm_error("You may specify only one of -jtop, -jbottom, "
+                 "-jleft, and -jright");
+    else {
+        switch (cmdlineP->orientation) {
+        case LEFTRIGHT:
+            if (jleft)
+                pm_error("-jleft is invalid with -leftright");
+            if (jright)
+                pm_error("-jright is invalid with -leftright");
+            if (jtop)
+                cmdlineP->justification = JUST_MIN;
+            else if (jbottom)
+                cmdlineP->justification = JUST_MAX;
+            else if (jcenter)
+                cmdlineP->justification = JUST_CENTER;
+            else
+                cmdlineP->justification = JUST_CENTER;
+            break;
+        case TOPBOTTOM:
+            if (jtop)
+                pm_error("-jtop is invalid with -topbottom");
+            if (jbottom)
+                pm_error("-jbottom is invalid with -topbottom");
+            if (jleft)
+                cmdlineP->justification = JUST_MIN;
+            else if (jright)
+                cmdlineP->justification = JUST_MAX;
+            else if (jcenter)
+                cmdlineP->justification = JUST_CENTER;
+            else
+                cmdlineP->justification = JUST_CENTER;
+            break;
+        }
+    }
+
+    if (listfileSpec) {
+        if (argc-1 > 0)
+          pm_error ("You can not specify files on the command line and "
+                    "also -listfile.");
+    } else {
+        cmdlineP->listfile = NULL;
+
+        if (argc-1 < 1) {
+            MALLOCARRAY_NOFAIL(cmdlineP->inputFileName, 1);
+            cmdlineP->inputFileName[0] = "-";
+            cmdlineP->inputFileCt = 1;
+        } else {
+            unsigned int i;
+            unsigned int stdinCt;
+            /* Number of input files user specified as Standard Input */
+
+            MALLOCARRAY_NOFAIL(cmdlineP->inputFileName, argc-1);
+
+            for (i = 0, stdinCt = 0; i < argc-1; ++i) {
+                cmdlineP->inputFileName[i] = argv[1+i];
+                if (streq(argv[1+i], "-"))
+                    ++stdinCt;
+            }
+            cmdlineP->inputFileCt = argc-1;
+            if (stdinCt > 1)
+                pm_error("At most one input image can come from "
+                         "Standard Input.  You specified %u", stdinCt);
+        }
+    }
+}
+
+
+
+static void
+freeCmdLine(struct CmdlineInfo const cmdline) {
+
+    if (!cmdline.listfile)
+        free(cmdline.inputFileName);
+}
+
+
+
+static void
+createInFileListFmFile(const char  *        const listFileNm,
+                       bool                 const verbose,
+                       const char ***       const inputFileNmP,
+                       unsigned int *       const inputFileCtP) {
+
+    FILE * const lfP = pm_openr(listFileNm);
+
+    const char ** inputFileNm;
+    unsigned int inputFileCt;
+    unsigned int emptyLineCt;
+    unsigned int stdinCt;
+    int eof;
+
+    MALLOCARRAY_NOFAIL(inputFileNm, FOPEN_MAX);
+
+    for (inputFileCt = emptyLineCt = stdinCt = eof = 0; !eof; ) {
+
+        size_t lineLen;
+        char * buf;
+        size_t bufferSz;
+
+        buf = NULL;  /* initial value */
+        bufferSz = 0;  /* initial value */
+
+        pm_getline(lfP, &buf, &bufferSz, &eof, &lineLen);
+
+        if (!eof) {
+            if (lineLen == 0)
+                ++emptyLineCt;
+            else if (lineLen > PATH_MAX)
+                pm_error("Path/file name in list file is too long "
+                         "(%u bytes).  Maximum is %u bytes",
+                         (unsigned)lineLen, PATH_MAX);
+            else /* 0 < lineLen < PATH_MAX */ {
+                if (inputFileCt >= FOPEN_MAX)
+                    pm_error("Too many files in list file.  Maximum is %u",
+                             FOPEN_MAX);
+                else {
+                    inputFileNm[inputFileCt] = buf;
+                    ++inputFileCt;
+                    if (streq(buf, "-"))
+                        ++stdinCt;
+                }
+            }
+        }
+    }
+
+    pm_close(lfP);
+
+    if (stdinCt > 1)
+        pm_error("At most one input image can come from Standard Input.  "
+                 "You specified %u", stdinCt);
+
+    if (inputFileCt == 0)
+        pm_error("No files specified in list file.");
+
+    if (verbose) {
+        pm_message("%u files specified and %u blank lines in list file",
+                   inputFileCt, emptyLineCt);
+    }
+
+    *inputFileCtP = inputFileCt;
+    *inputFileNmP = inputFileNm;
+}
+
+
+
+static void
+createInFileList(struct CmdlineInfo const cmdline,
+                 bool               const verbose,
+                 const char ***     const inputFileNmP,
+                 unsigned int *     const inputFileCtP) {
+
+    if (cmdline.listfile)
+        createInFileListFmFile(cmdline.listfile, verbose,
+                               inputFileNmP, inputFileCtP);
+    else {
+        *inputFileCtP = cmdline.inputFileCt;
+        *inputFileNmP = copyOfStringList(cmdline.inputFileName,
+                                         cmdline.inputFileCt);
+    }
+}
+
+
+
+static void
+freeInFileList(const char ** const inputFileNm,
+               unsigned int  const inputFileCt) {
+
+    unsigned int i;
+
+    for (i = 0; i < inputFileCt; ++i)
+        pm_strfree(inputFileNm[i]);
+
+    free(inputFileNm);
+}
+
+
+
+static const char *
+tupletypeX(bool         const allVisual,
+           unsigned int const colorDepth,
+           sample       const maxMaxval,
+           bool         const haveOpacity) {
+
+    const char * retval;
+
+    if (allVisual) {
+        switch (colorDepth) {
+        case 1:
+            if (maxMaxval == 1)
+                retval = haveOpacity ? "BLACKANDWHITE_ALPHA" : "BLACKANDWHITE";
+            else
+                retval = haveOpacity ? "GRAYSCALE_ALPHA"     : "GRAYSCALE";
+            break;
+        case 3:
+            retval = haveOpacity ? "RGB_ALPHA"           : "RGB";
+            break;
+        default:
+            assert(false);
+        }
+    } else
+        retval = "";
+
+    return retval;
+}
+
+
+
+typedef struct {
+    /* This describes a transformation from one tuple type to another,
+       e.g. from BLACKANDWHITE to GRAY_ALPHA.
+
+       For transformations bewteen the defined ones for visual images,
+       only the "up" transformations are covered.
+    */
+    bool mustPromoteColor;
+        /* Plane 0, which is the black/white or grayscale plane and also
+           the red plane must be copied as the red, green, and blue planes
+           (0, 1, and 2).
+        */
+    bool mustPromoteOpacity;
+        /* Plane 1, which is the opacity plane for black and white or
+           grayscale tuples, must be copied as the RGB opacity plane (3).
+        */
+    bool mustCreateOpacity;
+        /* The opacity plane value must be set to opaque */
+
+    bool mustPadZero;
+        /* Where the target tuple type is deeper than the source tuple
+           type, all higher numbered planes must be cleared to zero.
+
+           This is mutually exclusive with the rest of the musts.
+        */
+
+} TtTransform;
+
+
+
+static TtTransform
+ttXformForImg(const struct pam * const inpamP,
+              const struct pam * const outpamP) {
+/*----------------------------------------------------------------------------
+  The transform required to transform tuples of the kind described by *inpamP
+  to tuples of the kind described by *outpamP (e.g. from grayscale to RGB,
+  which involves replicating one plane into three).
+
+  We assume *outpamP tuples are of a type that is at least as expressive as
+  *inpamP tuples.  So e.g. outpamP->tuple_type cannot be "GRAYSCALE" if
+  inpamP->tuple_type is "RGB".
+-----------------------------------------------------------------------------*/
+    TtTransform retval;
+
+    if (inpamP->visual && outpamP->visual) {
+        retval.mustPromoteColor   =
+            (outpamP->color_depth > inpamP->color_depth);
+        retval.mustPromoteOpacity =
+            (outpamP->color_depth > inpamP->color_depth &&
+             (outpamP->have_opacity && inpamP->have_opacity));
+        retval.mustCreateOpacity  =
+            (outpamP->have_opacity && !inpamP->have_opacity);
+        retval.mustPadZero = false;
+    } else {
+        retval.mustPromoteColor   = false;
+        retval.mustPromoteOpacity = false;
+        retval.mustCreateOpacity  = false;
+        retval.mustPadZero        = true;
+    }
+    return retval;
+}
+
+
+
+static void
+reportPlans(unsigned int       const fileCt,
+            const struct pam * const outpamP) {
+
+    pm_message("Concatenating %u input images", fileCt);
+
+    pm_message("Output width, height, depth: %u x %u x %u",
+               outpamP->width, outpamP->height, outpamP->depth);
+
+    if (outpamP->format == RPBM_FORMAT)
+        pm_message("Using PBM fast path and producing raw PBM output");
+    else if (outpamP->format == PBM_FORMAT)
+        pm_message("Output format: Plain PBM");
+    else {
+        pm_message("Output maxval (max of all inputs): %lu", outpamP->maxval);
+
+        switch (outpamP->format) {
+        case PGM_FORMAT:
+            pm_message("Output format: Plain PGM");
+            break;
+        case RPGM_FORMAT:
+            pm_message("Output format: Raw PGM");
+            break;
+        case PPM_FORMAT:
+            pm_message("Output format: Plain PPM");
+            break;
+        case RPPM_FORMAT:
+            pm_message("Output format: Raw PPM");
+            break;
+        case PAM_FORMAT:
+            pm_message("Output format: PAM");
+
+            if (strlen(outpamP->tuple_type) > 0)
+                pm_message("Output tuple type: '%s'", outpamP->tuple_type);
+            else
+                pm_message("Output tuple type is null string because "
+                           "input images have various non-visual tuple types");
+            break;
+        }
+    }
+}
+
+
+
+static void
+computeOutputParms(unsigned int       const fileCt,
+                   enum Orientation   const orientation,
+                   const struct pam * const inpam,  /* array */
+                   bool               const verbose,
+                   struct pam *       const outpamP) {
+
+    double newCols, newRows;
+    unsigned int maxDepth;
+    sample maxMaxval;
+    int newFormat;
+    const char * firstTupletype;
+    bool allSameTt;
+    bool allVisual;
+    unsigned int maxColorDepth;
+    bool haveOpacity;
+    unsigned int fileSeq;
+
+    for (fileSeq = 0, newCols = 0, newRows = 0, maxDepth = 0, maxMaxval = 0,
+             newFormat = 0,
+             allVisual = true, maxColorDepth = 0, haveOpacity = false,
+             firstTupletype = NULL, allSameTt = true;
+         fileSeq < fileCt;
+         ++fileSeq) {
+
+        const struct pam * const inpamP = &inpam[fileSeq];
+
+        switch (orientation) {
+        case LEFTRIGHT:
+            newCols += inpamP->width;
+            newRows = MAX(newRows, inpamP->height);
+            break;
+        case TOPBOTTOM:
+            newRows += inpamP->height;
+            newCols = MAX(newCols, inpamP->width);
+            break;
+        }
+
+        if (!firstTupletype)
+            firstTupletype = inpamP->tuple_type;
+        if (inpamP->tuple_type != firstTupletype)
+            allSameTt = false;
+
+        if (inpamP->visual) {
+            maxColorDepth = MAX(maxColorDepth, inpamP->color_depth);
+
+            if (inpamP->have_opacity)
+                haveOpacity = true;
+        } else
+            allVisual = false;
+
+        maxDepth      = MAX(maxDepth,      inpamP->depth);
+        maxMaxval     = MAX(maxMaxval,     inpamP->maxval);
+
+        if (PAM_FORMAT_TYPE(inpamP->format) > PAM_FORMAT_TYPE(newFormat))
+            newFormat = inpamP->format;
+    }
+    assert(newCols       > 0);
+    assert(newRows       > 0);
+    assert(maxMaxval     > 0);
+    assert(newFormat     > 0);
+
+    if (newCols > INT_MAX)
+       pm_error("Output width too large: %.0f.", newCols);
+    if (newRows > INT_MAX)
+       pm_error("Output height too large: %.0f.", newRows);
+
+    outpamP->size = sizeof(*outpamP);
+    outpamP->len  = PAM_STRUCT_SIZE(tuple_type);
+
+    /* Note that while 'double' is not in general a precise numerical type,
+       in the case of a sum of integers which is less than INT_MAX, it
+       is exact, because double's precision is greater than int's.
+    */
+    outpamP->height           = (unsigned int)newRows;
+    outpamP->width            = (unsigned int)newCols;
+    if (allVisual)
+        outpamP->depth        = MAX(maxDepth,
+                                    maxColorDepth + (haveOpacity ? 1 : 0));
+    else
+        outpamP->depth        = maxDepth;
+    outpamP->allocation_depth = 0;  /* This means same as depth */
+    outpamP->maxval           = maxMaxval;
+    outpamP->format           = newFormat;
+    if (allSameTt)
+        STRSCPY(outpamP->tuple_type, firstTupletype);
+    else
+        STRSCPY(outpamP->tuple_type,
+                tupletypeX(allVisual, maxColorDepth, maxMaxval, haveOpacity));
+    outpamP->comment_p        = NULL;
+    outpamP->plainformat      = false;
+
+    if (verbose)
+        reportPlans(fileCt, outpamP);
+}
+
+
+
+static void
+copyBitrow(const unsigned char * const source,
+           unsigned char *       const destBitrow,
+           unsigned int          const cols,
+           unsigned int          const offset) {
+/*----------------------------------------------------------------------------
+  Copy from source to destBitrow, without shifting.  Preserve
+  surrounding image data.
+-----------------------------------------------------------------------------*/
+    unsigned char * const dest = & destBitrow[ offset/8 ];
+        /* Copy destination, with leading full bytes ignored. */
+    unsigned int const rs = offset % 8;
+        /* The "little offset", as measured from start of dest.  Source
+           is already shifted by this value.
+        */
+    unsigned int const trs = (cols + rs) % 8;
+        /* The number of partial bits in the final char. */
+    unsigned int const colByteCnt = pbm_packed_bytes(cols + rs);
+        /* # bytes to process, including partial ones on both ends. */
+    unsigned int const last = colByteCnt - 1;
+
+    unsigned char const origHead = dest[0];
+    unsigned char const origEnd  = dest[last];
+
+    unsigned int i;
+
+    assert(colByteCnt >= 1);
+
+    for (i = 0; i < colByteCnt; ++i)
+        dest[i] = source[i];
+
+    if (rs > 0)
+        dest[0] = LEFTBITS(origHead, rs) | RIGHTBITS(dest[0], 8-rs);
+
+    if (trs > 0)
+        dest[last] = LEFTBITS(dest[last], trs) | RIGHTBITS(origEnd, 8-trs);
+}
+
+
+
+static void
+padFillBitrow(unsigned char * const destBitrow,
+              unsigned char   const padColor,
+              unsigned int    const cols,
+              unsigned int    const offset) {
+/*----------------------------------------------------------------------------
+   Fill destBitrow, starting at offset, with padColor.  padColor is a
+   byte -- 0x00 or 0xff -- not a single bit.
+-----------------------------------------------------------------------------*/
+    unsigned char * const dest = &destBitrow[offset/8];
+    unsigned int const rs = offset % 8;
+    unsigned int const trs = (cols + rs) % 8;
+    unsigned int const colByteCnt = pbm_packed_bytes(cols + rs);
+    unsigned int const last = colByteCnt - 1;
+
+    unsigned char const origHead = dest[0];
+    unsigned char const origEnd  = dest[last];
+
+    unsigned int i;
+
+    assert(colByteCnt > 0);
+
+    for (i = 0; i < colByteCnt; ++i)
+        dest[i] = padColor;
+
+    if (rs > 0)
+        dest[0] = LEFTBITS(origHead, rs) | RIGHTBITS(dest[0], 8-rs);
+
+    if (trs > 0)
+        dest[last] = LEFTBITS(dest[last], trs) | RIGHTBITS(origEnd, 8-trs);
+}
+
+
+
+/* concatenateLeftRightPbm() and concatenateLeftRightGen()
+   employ almost identical algorithms.
+   The difference is in the data types and functions.
+
+   Same for concatenateTopBottomPbm() and concatenateTopBottomGen().
+*/
+
+
+typedef struct {
+    /* Information about one image */
+    unsigned char * proberow;
+        /* Top row of image, when background color is
+           auto-determined.
+        */
+    unsigned int offset;
+        /* start position of image, in bits, counting from left
+           edge
+        */
+    unsigned char background;
+        /* Background color.  0x00 means white; 0xff means black */
+    unsigned int padtop;
+        /* Top padding amount */
+} LrImgCtlPbm;
+
+
+
+static void
+createLrImgCtlPbm(const struct pam *  const inpam,  /* array */
+                  unsigned int        const fileCt,
+                  unsigned int        const outHeight,
+                  enum Justification  const justification,
+                  enum PadColorMethod const padColorMethod,
+                  LrImgCtlPbm **      const imgCtlP) {
+/*----------------------------------------------------------------------------
+   Read the first row of each image in inpam[] and return that and additional
+   information about images as *imgCtlP.
+-----------------------------------------------------------------------------*/
+    LrImgCtlPbm * imgCtl;  /* array, size 'fileCt' */
+    unsigned int fileSeq;
+
+    MALLOCARRAY_NOFAIL(imgCtl, fileCt);
+
+    for (fileSeq = 0; fileSeq < fileCt; ++fileSeq) {
+        LrImgCtlPbm *      const imgCtlP = &imgCtl[fileSeq];
+        const struct pam * const inpamP  = &inpam[fileSeq];
+
+        switch (justification) {
+        case JUST_MIN:
+            imgCtlP->padtop = 0;
+            break;
+        case JUST_MAX:
+            imgCtlP->padtop = outHeight - inpam[fileSeq].height;
+            break;
+        case JUST_CENTER:
+            imgCtlP->padtop = (outHeight - inpamP->height) / 2;
+            break;
+        }
+
+        imgCtlP->offset =
+            (fileSeq == 0) ?
+                0 : imgCtl[fileSeq-1].offset + inpam[fileSeq-1].width;
+
+        if (inpamP->height == outHeight)  /* no padding */
+            imgCtlP->proberow = NULL;
+        else {                   /* determine pad color for image i */
+            switch (padColorMethod) {
+            case PAD_AUTO: {
+                bit bgBit;
+                imgCtlP->proberow =
+                    pbm_allocrow_packed((unsigned int)inpamP->width + 7);
+                pbm_readpbmrow_bitoffset(
+                    inpamP->file, imgCtlP->proberow,
+                    inpamP->width, inpamP->format, imgCtlP->offset % 8);
+
+                bgBit = pbm_backgroundbitrow(
+                    imgCtlP->proberow, inpamP->width,
+                    imgCtlP->offset % 8);
+
+                imgCtlP->background = bgBit == PBM_BLACK ? 0xff : 0x00;
+            } break;
+            case PAD_BLACK:
+                imgCtlP->proberow   = NULL;
+                imgCtlP->background = 0xff;
+                break;
+            case PAD_WHITE:
+                imgCtlP->proberow   = NULL;
+                imgCtlP->background = 0x00;
+                break;
+            }
+        }
+    }
+    *imgCtlP = imgCtl;
+}
+
+
+
+static void
+destroyPbmImgCtl(LrImgCtlPbm * const imgCtl,  /* array */
+                 unsigned int  const fileCt) {
+
+    unsigned int i;
+
+    for (i = 0; i < fileCt; ++i) {
+        if (imgCtl[i].proberow)
+            free(imgCtl[i].proberow);
+    }
+    free(imgCtl);
+}
+
+
+
+static void
+concatenateLeftRightPbm(struct pam *        const outpamP,
+                        const struct pam *  const inpam,  /* array */
+                        unsigned int        const fileCt,
+                        enum Justification  const justification,
+                        enum PadColorMethod const padColorMethod) {
+
+    unsigned char * const outrow = pbm_allocrow_packed(outpamP->width);
+        /* We use just one outrow.  All padding and image data (with the
+           exception of following imgCtl.proberow) goes directly into this
+           packed PBM row.
+        */
+
+    LrImgCtlPbm * imgCtl;
+        /* malloc'ed array, one element per image.  Shadows inpam[] */
+    unsigned int row;
+
+    createLrImgCtlPbm(inpam, fileCt, outpamP->height,
+                      justification, padColorMethod,
+                      &imgCtl);
+
+    outrow[pbm_packed_bytes(outpamP->width)-1] = 0x00;
+
+    for (row = 0; row < outpamP->height; ++row) {
+        unsigned int fileSeq;
+
+        for (fileSeq = 0; fileSeq < fileCt; ++fileSeq) {
+            const LrImgCtlPbm * const imgCtlP = &imgCtl[fileSeq];
+            const struct pam *  const inpamP  = &inpam[fileSeq];
+
+            if ((row == 0 && imgCtlP->padtop > 0) ||
+                row == imgCtlP->padtop + inpamP->height) {
+
+                /* This row begins a run of padding, either above or below
+                   file 'i', so set 'outrow' to padding.
+                */
+                padFillBitrow(outrow, imgCtlP->background, inpamP->width,
+                              imgCtlP->offset);
+            }
+
+            if (row == imgCtlP->padtop && imgCtlP->proberow != NULL) {
+                /* Top row has been read to proberow[] to determine
+                   background.  Copy it to outrow[].
+                */
+                copyBitrow(imgCtlP->proberow, outrow,
+                           inpamP->width, imgCtlP->offset);
+            } else if (row >= imgCtlP->padtop &&
+                       row < imgCtlP->padtop + inpamP->height) {
+                pbm_readpbmrow_bitoffset(
+                    inpamP->file, outrow, inpamP->width, inpamP->format,
+                    imgCtlP->offset);
+            } else {
+                /* It's a row of padding, so outrow[] is already set
+                   appropriately.
+                */
+            }
+        }
+        pbm_writepbmrow_packed(outpamP->file, outrow, outpamP->width, 0);
+    }
+
+    destroyPbmImgCtl(imgCtl, fileCt);
+
+    pbm_freerow_packed(outrow);
+}
+
+
+
+static void
+concatenateTopBottomPbm(const struct pam *  const outpamP,
+                        const struct pam *  const inpam,  /* array */
+                        unsigned int        const fileCt,
+                        enum Justification  const justification,
+                        enum PadColorMethod const padColorMethod) {
+
+    unsigned char * const outrow = pbm_allocrow_packed(outpamP->width);
+        /* Like the left-right PBM case, all padding and image data
+           goes directly into outrow.  There is no proberow.
+        */
+    unsigned char background, backgroundPrev;
+        /* 0x00 means white; 0xff means black */
+    unsigned int  padleft;
+    bool          backChange;
+        /* Background color is different from that of the previous
+           input image.
+        */
+
+    unsigned int fileSeq;
+    unsigned int row, startRow;
+
+    outrow[pbm_packed_bytes(outpamP->width)-1] = 0x00;
+
+    switch (padColorMethod){
+    case PAD_AUTO:   /* do nothing */    break;
+    case PAD_BLACK:  background = 0xff;  break;
+    case PAD_WHITE:  background = 0x00;  break;
+    }
+
+    for (fileSeq = 0; fileSeq < fileCt; ++fileSeq) {
+        const struct pam * const inpamP = &inpam[fileSeq];
+
+        if (inpamP->width == outpamP->width) {
+            /* No padding */
+            startRow   = 0;
+            backChange = FALSE;
+            padleft    = 0;
+            outrow[pbm_packed_bytes(outpamP->width)-1] = 0x00;
+        } else {
+            /* Determine amount of padding and color */
+            switch (justification) {
+            case JUST_MIN:
+                padleft = 0;
+                break;
+            case JUST_MAX:
+                padleft = outpamP->width - inpamP->width;
+                break;
+            case JUST_CENTER:
+                padleft = (outpamP->width - inpamP->width) / 2;
+                break;
+            }
+
+            switch (padColorMethod) {
+            case PAD_AUTO: {
+                bit bgBit;
+
+                startRow = 1;
+
+                pbm_readpbmrow_bitoffset(
+                    inpamP->file, outrow, inpamP->width, inpamP->format,
+                    padleft);
+
+                bgBit = pbm_backgroundbitrow(outrow, inpamP->width, padleft);
+                background = bgBit == PBM_BLACK ? 0xff : 0x00;
+
+                backChange = (fileSeq == 0 || background != backgroundPrev);
+            } break;
+            case PAD_WHITE:
+            case PAD_BLACK:
+                startRow = 0;
+                backChange = (fileSeq == 0);
+                break;
+            }
+
+            if (backChange ||
+                (fileSeq > 0 && inpam[fileSeq-1].width > inpamP->width)) {
+                unsigned int const padright =
+                    outpamP->width - padleft - inpamP->width;
+
+                if (padleft > 0)
+                    padFillBitrow(outrow, background, padleft, 0);
+
+                if (padright > 0)
+                    padFillBitrow(outrow, background, padright,
+                                  padleft + inpamP->width);
+
+            }
+        }
+
+        if (startRow == 1)
+            /* Top row already read for auto background color
+               determination.  Write it out.
+            */
+            pbm_writepbmrow_packed(outpamP->file, outrow, outpamP->width, 0);
+
+        for (row = startRow; row < inpamP->height; ++row) {
+            pbm_readpbmrow_bitoffset(inpamP->file, outrow, inpamP->width,
+                                     inpamP->format, padleft);
+            pbm_writepbmrow_packed(outpamP->file, outrow, outpamP->width, 0);
+        }
+
+        backgroundPrev = background;
+    }
+    pbm_freerow_packed(outrow);
+}
+
+
+
+static void
+padPlanesRow(const struct pam *  const inpamP,
+             tuple *             const outrow,
+             const struct pam *  const outpamP) {
+/*----------------------------------------------------------------------------
+  Rearrange the planes of *outrow as needed to transform them into tuples
+  as described by *outpamP from tuples as described by *inpamP.
+-----------------------------------------------------------------------------*/
+    TtTransform const ttTransform = ttXformForImg(inpamP, outpamP);
+
+    assert(inpamP->allocation_depth >= outpamP->depth);
+
+    if (ttTransform.mustPromoteOpacity) {
+        unsigned int col;
+
+        assert(outpamP->depth >= PAM_TRN_PLANE);
+
+        for (col = 0; col < inpamP->width; ++col) {
+            outrow[col][outpamP->opacity_plane] =
+                outrow[col][inpamP->opacity_plane];
+        }
+    }
+    if (ttTransform.mustPromoteColor) {
+        unsigned int col;
+
+        assert(outpamP->depth >= PAM_GRN_PLANE);
+        assert(outpamP->depth >= PAM_BLU_PLANE);
+
+        for (col = 0; col < inpamP->width; ++col) {
+            assert(PAM_RED_PLANE == 0);
+            outrow[col][PAM_GRN_PLANE] = outrow[col][0];
+            outrow[col][PAM_BLU_PLANE] = outrow[col][0];
+        }
+    }
+
+    if (ttTransform.mustCreateOpacity) {
+        unsigned int col;
+
+        for (col = 0; col < inpamP->width; ++col)
+            outrow[col][outpamP->opacity_plane] = outpamP->maxval;
+    }
+
+    if (ttTransform.mustPadZero) {
+        unsigned int plane;
+
+        for (plane = inpamP->depth; plane < outpamP->depth; ++plane) {
+            unsigned int col;
+
+            for (col = 0; col < inpamP->width; ++col)
+                outrow[col][plane] = 0;
+        }
+    }
+}
+
+
+
+typedef struct {
+/*----------------------------------------------------------------------------
+   Parameters and state for placing a row of a particular input image in
+   the output in a left-right concatenation.
+-----------------------------------------------------------------------------*/
+    tuple *      cachedRow;
+        /* Contents of the current row of the image, with depth and maxval
+           adjusted for output, in malloc'ed space belonging to this object.
+           Input file is positioned past this row.  Null if data not present
+           and input file is positioned to the current row.
+        */
+    tuple *      out;
+        /* Point in output row buffer where the row from this image goes */
+    tuple        background;
+    unsigned int padtop;
+        /* Number of rows of padding that go above this image in the output */
+} LrImgCtl;
+
+
+
+static void
+createLrImgCtlArray(const struct pam *  const inpam,  /* array */
+                    unsigned int        const fileCt,
+                    tuple *             const newTuplerow,
+                    const struct pam *  const outpamP,
+                    enum Justification  const justification,
+                    enum PadColorMethod const padColorMethod,
+                    LrImgCtl **         const imgCtlP) {
+
+    LrImgCtl * imgCtl;  /* array */
+    unsigned int fileSeq;
+
+    MALLOCARRAY_NOFAIL(imgCtl, fileCt);
+
+    for (fileSeq = 0; fileSeq < fileCt; ++fileSeq) {
+        LrImgCtl *         const thisEntryP = &imgCtl[fileSeq];
+        const struct pam * const inpamP     = &inpam[fileSeq];
+
+        switch (justification) {  /* Determine top padding */
+            case JUST_MIN:
+                thisEntryP->padtop = 0;
+                break;
+            case JUST_MAX:
+                thisEntryP->padtop = outpamP->height - inpamP->height;
+                break;
+            case JUST_CENTER:
+                thisEntryP->padtop = (outpamP->height - inpamP->height) / 2;
+                break;
+        }
+
+        thisEntryP->out =
+            (fileSeq == 0 ?
+             &newTuplerow[0] : imgCtl[fileSeq-1].out + inpam[fileSeq-1].width);
+
+        if (inpamP->height == outpamP->height) { /* no vertical padding */
+            thisEntryP->cachedRow  = NULL;
+            pnm_createBlackTuple(outpamP, &thisEntryP->background);
+                /* Meaningless because no padding */
+        } else {
+            /* Determine pad color */
+            switch (padColorMethod){
+            case PAD_AUTO:
+                thisEntryP->cachedRow = pnm_allocpamrow(inpamP);
+                pnm_readpamrow(inpamP, thisEntryP->cachedRow);
+                pnm_scaletuplerow(inpamP, thisEntryP->cachedRow,
+                                  thisEntryP->cachedRow, outpamP->maxval);
+                padPlanesRow(inpamP, thisEntryP->cachedRow, outpamP);
+                {
+                    struct pam cachedRowPam;
+                    cachedRowPam = *outpamP;
+                    cachedRowPam.width = inpamP->width;
+                    thisEntryP->background = pnm_backgroundtuplerow(
+                        &cachedRowPam, thisEntryP->cachedRow);
+                }
+                break;
+            case PAD_BLACK:
+                thisEntryP->cachedRow = NULL;
+                pnm_createBlackTuple(outpamP, &thisEntryP->background);
+                break;
+            case PAD_WHITE:
+                thisEntryP->cachedRow = NULL;
+                pnm_createWhiteTuple(outpamP, &thisEntryP->background);
+                break;
+            }
+        }
+        if (outpamP->visual) {
+            /* Any opacity sample in background color tuple is meaningless at
+               this point; make it opaque.
+            */
+            if (outpamP->have_opacity) {
+                thisEntryP->background[outpamP->opacity_plane] =
+                    outpamP->maxval;
+            }
+        }
+
+    }
+    *imgCtlP = imgCtl;
+}
+
+
+
+static void
+destroyLrImgCtlArray(LrImgCtl *   const imgCtl,  /* array */
+                     unsigned int const fileCt) {
+
+    unsigned int fileSeq;
+
+    for (fileSeq = 0; fileSeq < fileCt; ++fileSeq) {
+        LrImgCtl * const thisEntryP = &imgCtl[fileSeq];
+
+        pnm_freepamtuple(thisEntryP->background);
+        pnm_freepamrow(thisEntryP->cachedRow);
+    }
+
+    free(imgCtl);
+}
+
+
+
+static void
+concatenateLeftRightGen(const struct pam *  const outpamP,
+                        const struct pam *  const inpam,  /* array */
+                        unsigned int        const fileCt,
+                        enum Justification  const justification,
+                        enum PadColorMethod const padColorMethod) {
+
+    tuple * const outrow = pnm_allocpamrow(outpamP);
+
+    LrImgCtl *   imgCtl;
+    unsigned int row;
+
+    createLrImgCtlArray(inpam, fileCt, outrow, outpamP,
+                        justification, padColorMethod,
+                        &imgCtl);
+
+    for (row = 0; row < outpamP->height; ++row) {
+        unsigned int fileSeq;
+
+        for (fileSeq = 0; fileSeq < fileCt; ++fileSeq) {
+            LrImgCtl *   const thisEntryP   = &imgCtl[fileSeq];
+            const struct pam * const inpamP = &inpam[fileSeq];
+
+            if ((row == 0 && thisEntryP->padtop > 0) ||
+                row == thisEntryP->padtop + inpamP->height) {
+                /* This row begins a run of padding, either above or below
+                   image 'fileSeq', so set its part of outrow[] to padding.
+                */
+                unsigned int col;
+                for (col = 0; col < inpamP->width; ++col) {
+                    pnm_assigntuple(outpamP, thisEntryP->out[col],
+                                    thisEntryP->background);
+                }
+            }
+            if (row == thisEntryP->padtop && thisEntryP->cachedRow) {
+                /* We're at the top row of image 'fileSeq', and that row
+                   has already been read to cachedRow[] to determine
+                   background.  Copy it to image fileseq's part of outrow[].
+                */
+                unsigned int col;
+                for (col = 0; col < inpamP->width; ++col) {
+                    pnm_assigntuple(outpamP, thisEntryP->out[col],
+                                    thisEntryP->cachedRow[col]);
+                }
+                free(thisEntryP->cachedRow);
+                thisEntryP->cachedRow = NULL;
+            } else if (row >= thisEntryP->padtop &&
+                       row < thisEntryP->padtop + inpamP->height) {
+                pnm_readpamrow(inpamP, thisEntryP->out);
+                pnm_scaletuplerow(inpamP, thisEntryP->out,
+                                  thisEntryP->out, outpamP->maxval);
+                padPlanesRow(inpamP, thisEntryP->out, outpamP);
+            } else {
+                /* It's a row of padding, so image filesSeq's part of outrow[]
+                   is already set appropriately.
+                */
+            }
+        }
+        /* Note that imgCtl[N].out[] is an alias to part of outrow[], so
+           outrow[] has been set.
+        */
+        pnm_writepamrow(outpamP, outrow);
+    }
+    destroyLrImgCtlArray(imgCtl, fileCt);
+
+    pnm_freepamrow(outrow);
+}
+
+
+
+static tuple
+initialBackgroundColor(const struct pam *  const outpamP,
+                       enum PadColorMethod const padColorMethod) {
+
+    tuple retval;
+
+    switch (padColorMethod) {
+    case PAD_AUTO:
+        /* Background is different for each input image */
+        retval = pnm_allocpamtuple(outpamP);
+            /* Dummy value; just need something to free */
+        break;
+    case PAD_BLACK:
+        pnm_createBlackTuple(outpamP, &retval);
+        break;
+    case PAD_WHITE:
+        pnm_createWhiteTuple(outpamP, &retval);
+        break;
+    }
+
+    if (outpamP->visual) {
+        /* Any opacity sample in background color tuple is meaningless at this
+           point; make it opaque.
+        */
+        if (outpamP->have_opacity)
+            retval[outpamP->opacity_plane] = outpamP->maxval;
+    }
+
+    return retval;
+}
+
+
+
+static unsigned int
+leftPadAmount(const struct pam * const outpamP,
+              const struct pam * const inpamP,
+              enum Justification const justification) {
+
+    switch (justification) {
+    case JUST_MIN:    return 0;
+    case JUST_MAX:    return outpamP->width - inpamP->width;
+    case JUST_CENTER: return (outpamP->width - inpamP->width) / 2;
+    }
+    assert(false);
+}
+
+
+
+static void
+setHorizPadding(tuple *            const newTuplerow,
+                const struct pam * const outpamP,
+                bool               const backChanged,
+                const struct pam * const inpam,  /* array */
+                unsigned int       const imageSeq,
+                unsigned int       const padLeft,
+                tuple              const background) {
+/*----------------------------------------------------------------------------
+   Set the left and right padding for an output row in a top-bottom
+   concatenation.
+
+   'newTuplerow' is where we set the padding (and also assumed to hold the
+   contents of the previous output row).  *outpamP describes it.
+
+   'backChanged' means the background color is different for the current row
+   from that of the previous row.
+
+   inpam[] is the array of descriptors for all the input images.  'imageSeq'
+   is the index into this array for the current image.
+
+   'background' is the background color to set.
+-----------------------------------------------------------------------------*/
+    if (backChanged ||
+        (imageSeq > 0 && inpam[imageSeq-1].width > inpam[imageSeq].width)) {
+        unsigned int col;
+
+        for (col = 0; col < padLeft; ++col)
+            pnm_assigntuple(outpamP, newTuplerow[col], background);
+        for (col = padLeft + inpam[imageSeq].width;
+             col < outpamP->width;
+             ++col) {
+            pnm_assigntuple(outpamP, newTuplerow[col], background);
+        }
+    } else {
+        /* No need to pad because newTuplerow[] already contains the
+           correct padding from the previous row.
+        */
+    }
+}
+
+
+
+static void
+readFirstTBRowAndDetermineBackground(const struct pam *  const inpamP,
+                                     const struct pam *  const outpamP,
+                                     tuple *             const out,
+                                     tuple *             const backgroundP) {
+/*----------------------------------------------------------------------------
+   Read the first row of an input image into 'out', adjusting it to conform
+   to the output depth and maxval described by *outpamP.
+
+   The image is positioned to the first row at entry.
+
+   From this row, determine the background color for the input image and
+   return it as *backgroundP (a newly malloced tuple).
+-----------------------------------------------------------------------------*/
+    pnm_readpamrow(inpamP, out);
+
+    pnm_scaletuplerow(inpamP, out, out, outpamP->maxval);
+
+    padPlanesRow(inpamP, out, outpamP);
+
+    {
+        struct pam partialOutpam;
+            /* Descriptor for the input image with depth and maxval adjusted to
+               that of the output image.
+            */
+        tuple background;
+
+        partialOutpam = *outpamP;
+        partialOutpam.width = inpamP->width;
+
+        background = pnm_backgroundtuplerow(&partialOutpam, out);
+
+        if (outpamP->visual) {
+            /* Make the background opaque. */
+            if (outpamP->have_opacity)
+                background[outpamP->opacity_plane] = outpamP->maxval;
+        }
+
+        *backgroundP = background;
+    }
+}
+
+
+
+static void
+concatenateTopBottomGen(const struct pam *  const outpamP,
+                        const struct pam *  const inpam,  /* array */
+                        unsigned int        const fileCt,
+                        enum Justification  const justification,
+                        enum PadColorMethod const padColorMethod) {
+
+    tuple * const newTuplerow = pnm_allocpamrow(outpamP);
+    tuple * out;
+        /* The location in newTuplerow[] that the row from the current
+           input image goes.
+        */
+    unsigned int fileSeq;
+    tuple background;
+    tuple backgroundPrev;
+
+    background = initialBackgroundColor(outpamP, padColorMethod);
+
+    for (fileSeq = 0; fileSeq < fileCt; ++fileSeq) {
+        const struct pam * const inpamP = &inpam[fileSeq];
+
+        unsigned int row;
+        unsigned int startRow;
+        bool backChanged;
+            /* The background color is different from that of the previous
+               input image.
+            */
+
+        if (inpamP->width == outpamP->width) {
+            /* no padding */
+            startRow = 0;
+            backChanged = false;
+            out = &newTuplerow[0];
+        } else {
+            unsigned int const padLeft =
+                leftPadAmount(outpamP, inpamP, justification);
+
+            if (padColorMethod == PAD_AUTO) {
+                out = &newTuplerow[padLeft];
+                backgroundPrev = background;
+                readFirstTBRowAndDetermineBackground(
+                    inpamP, outpamP, out, &background);
+
+                backChanged =
+                    fileSeq == 0 ||
+                        pnm_tupleequal(outpamP, background, backgroundPrev);
+                pnm_freepamtuple(backgroundPrev);
+
+                startRow = 1;
+            } else {
+                /* Background color is constant: black or white */
+                startRow = 0;
+                out = &newTuplerow[padLeft];
+                backChanged = (fileSeq == 0);
+            }
+
+            setHorizPadding(newTuplerow, outpamP, backChanged, inpam, fileSeq,
+                            padLeft, background);
+        }
+
+        if (startRow == 1)
+            /* Top row was already read for auto background color
+               determination.  Write it out.
+            */
+            pnm_writepamrow(outpamP, newTuplerow);
+
+        for (row = startRow; row < inpamP->height; ++row) {
+            pnm_readpamrow(inpamP, out);
+
+            pnm_scaletuplerow(inpamP, out, out, outpamP->maxval);
+
+            padPlanesRow(inpamP, out, outpamP);
+
+            pnm_writepamrow(outpamP, newTuplerow);
+        }
+    }
+    pnm_freepamtuple(background);
+    pnm_freepamrow(newTuplerow);
+}
+
+
+
+int
+main(int           argc,
+     const char ** argv) {
+
+    struct CmdlineInfo cmdline;
+    const char ** inputFileNm;
+    unsigned int inputFileCt;
+    struct pam * inpam;  /* malloc'ed array */
+    struct pam outpam;
+    unsigned int i;
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    createInFileList(cmdline, !!cmdline.verbose, &inputFileNm, &inputFileCt);
+
+    MALLOCARRAY_NOFAIL(inpam, inputFileCt);
+
+    for (i = 0; i < inputFileCt; ++i) {
+        FILE *  ifP;
+        ifP = pm_openr(inputFileNm[i]);
+        inpam[i].comment_p = NULL;  /* Don't want to see the comments */
+        pnm_readpaminit(ifP, &inpam[i], PAM_STRUCT_SIZE(opacity_plane));
+    }
+
+    computeOutputParms(inputFileCt, cmdline.orientation, inpam,
+                       cmdline.verbose, &outpam);
+
+    outpam.file = stdout;
+
+    for (i = 0; i < inputFileCt; ++i)
+        pnm_setminallocationdepth(&inpam[i], outpam.depth);
+
+    pnm_writepaminit(&outpam);
+
+    if (outpam.format == RPBM_FORMAT) {
+        switch (cmdline.orientation) {
+        case LEFTRIGHT:
+            concatenateLeftRightPbm(&outpam, inpam, inputFileCt,
+                                    cmdline.justification,
+                                    cmdline.padColorMethod);
+            break;
+        case TOPBOTTOM:
+            concatenateTopBottomPbm(&outpam, inpam, inputFileCt,
+                                    cmdline.justification,
+                                    cmdline.padColorMethod);
+            break;
+        }
+    } else {
+        switch (cmdline.orientation) {
+        case LEFTRIGHT:
+            concatenateLeftRightGen(&outpam, inpam, inputFileCt,
+                                    cmdline.justification,
+                                    cmdline.padColorMethod);
+            break;
+        case TOPBOTTOM:
+            concatenateTopBottomGen(&outpam, inpam, inputFileCt,
+                                    cmdline.justification,
+                                    cmdline.padColorMethod);
+            break;
+        }
+    }
+    for (i = 0; i < inputFileCt; ++i)
+        pm_close(inpam[i].file);
+    free(inpam);
+    freeInFileList(inputFileNm, inputFileCt);
+    freeCmdLine(cmdline);
+    pm_close(stdout);
+
+    return 0;
+}
+
+
+
diff --git a/editor/pamcomp.c b/editor/pamcomp.c
index 6e1e7f7d..9ccd41c7 100644
--- a/editor/pamcomp.c
+++ b/editor/pamcomp.c
@@ -208,117 +208,49 @@ parseCommandLine(int                        argc,
 
 
 
-static int
-commonFormat(int const formatA,
-             int const formatB) {
-/*----------------------------------------------------------------------------
-   Return a viable format for the result of composing the two formats
-   'formatA' and 'formatB'.
------------------------------------------------------------------------------*/
-    int retval;
-
-    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)
-        retval = PPM_FORMAT;
-    else if (typeA == PGM_TYPE || typeB == PGM_TYPE)
-        retval = PGM_FORMAT;
-    else if (typeA == PBM_TYPE || typeB == PBM_TYPE)
-        retval = PBM_FORMAT;
-    else {
-        /* Results are undefined for this case, so we do a hail Mary. */
-        retval = formatA;
-    }
-    return retval;
-}
-
-
-
-typedef enum { TT_BLACKANDWHITE, TT_GRAYSCALE, TT_RGB } BaseTupletype;
-
-
-
-static BaseTupletype
-commonTupletype(const char * const tupletypeA,
-                const char * const tupletypeB) {
-
-    if (strneq(tupletypeA, "RGB", 3) ||
-        strneq(tupletypeB, "RGB", 3))
-        return TT_RGB;
-    else if (strneq(tupletypeA, "GRAYSCALE", 9) ||
-             strneq(tupletypeB, "GRAYSCALE", 9))
-        return TT_GRAYSCALE;
-    else if (strneq(tupletypeA, "BLACKANDWHITE", 13) ||
-             strneq(tupletypeB, "BLACKANDWHITE", 13))
-        return TT_BLACKANDWHITE;
-    else
-        /* Results are undefined for this case, so we do a hail Mary. */
-        return TT_RGB;
-}
-
-
-
 static void
-determineOutputTupleType(BaseTupletype const baseTupletype,
-                         bool          const underlayHaveOpacity,
-                         char *        const tupleType,
-                         size_t        const size) {
+initAlphaFile(struct CmdlineInfo const cmdline,
+              struct pam *       const overlayPamP,
+              FILE **            const filePP,
+              struct pam *       const pamP) {
 
-    char buffer[80];
+    FILE * fileP;
 
-    switch (baseTupletype) {
-    case TT_BLACKANDWHITE:
-        STRSCPY(buffer, "RGB");
-        break;
-    case TT_GRAYSCALE:
-        STRSCPY(buffer, "GRAYSCALE");
-        break;
-    case TT_RGB:
-        STRSCPY(buffer, "RGB");
-        break;
-    }
+    if (cmdline.alphaFilespec) {
+        fileP = pm_openr(cmdline.alphaFilespec);
+        pamP->comment_p = NULL;
+        pnm_readpaminit(fileP, pamP, PAM_STRUCT_SIZE(opacity_plane));
 
-    if (underlayHaveOpacity)
-        STRSCAT(buffer, "_ALPHA");
+        if (overlayPamP->width != pamP->width ||
+            overlayPamP->height != pamP->height)
+            pm_error("Opacity map and overlay image are not the same size");
+    } else
+        fileP = NULL;
 
-    strncpy(tupleType, buffer, size);
+    *filePP = fileP;
 }
 
 
 
-static void
-determineOutputType(const struct pam * const underlayPamP,
-                    const struct pam * const overlayPamP,
-                    struct pam *       const composedPamP) {
-
-    BaseTupletype const baseTupletype =
-        commonTupletype(underlayPamP->tuple_type, overlayPamP->tuple_type);
-
-    composedPamP->height = underlayPamP->height;
-    composedPamP->width  = underlayPamP->width;
+typedef enum { TT_BLACKANDWHITE, TT_GRAYSCALE, TT_RGB } BaseTupletype;
 
-    composedPamP->format = commonFormat(underlayPamP->format,
-                                        overlayPamP->format);
-    composedPamP->plainformat = FALSE;
 
-    composedPamP->maxval = pm_lcm(underlayPamP->maxval, overlayPamP->maxval,
-                                  1, PNM_OVERALLMAXVAL);
 
-    composedPamP->visual = true;
-    composedPamP->color_depth = (baseTupletype == TT_RGB ? 3 : 1);
-    composedPamP->have_opacity = underlayPamP->have_opacity;
-    composedPamP->opacity_plane = (baseTupletype == TT_RGB ? 3 : 1);
-
-    composedPamP->depth =
-        (baseTupletype == TT_RGB ? 3 : 1) +
-        (underlayPamP->have_opacity ? 1 : 0);
+static void
+validateComputableHeight(int const originTop,
+                         int const overRows) {
 
-    determineOutputTupleType(baseTupletype, underlayPamP->have_opacity,
-                             composedPamP->tuple_type,
-                             sizeof(composedPamP->tuple_type));
+    if (originTop < 0) {
+        if (originTop < -INT_MAX)
+            pm_error("Overlay starts too far above the underlay image to be "
+                     "computable.  Overlay can be at most %d rows above "
+                     "the underlay.", INT_MAX);
+    } else {
+        if (INT_MAX - originTop <= overRows)
+            pm_error("Too many total rows involved to be computable.  "
+                     "You must have a shorter overlay image or compose it "
+                     "higher on the underlay image.");
+    }
 }
 
 
@@ -362,25 +294,6 @@ warnOutOfFrame(int const originLeft,
 
 
 static void
-validateComputableHeight(int const originTop,
-                         int const overRows) {
-
-    if (originTop < 0) {
-        if (originTop < -INT_MAX)
-            pm_error("Overlay starts too far above the underlay image to be "
-                     "computable.  Overlay can be at most %d rows above "
-                     "the underlay.", INT_MAX);
-    } else {
-        if (INT_MAX - originTop <= overRows)
-            pm_error("Too many total rows involved to be computable.  "
-                     "You must have a shorter overlay image or compose it "
-                     "higher on the underlay image.");
-    }
-}
-
-
-
-static void
 computeOverlayPosition(int                const underCols,
                        int                const underRows,
                        int                const overCols,
@@ -483,6 +396,194 @@ computeOverlayPosition(int                const underCols,
 
 
 
+static BaseTupletype
+commonTupletype(const char * const tupletypeA,
+                const char * const tupletypeB) {
+
+    if (strneq(tupletypeA, "RGB", 3) ||
+        strneq(tupletypeB, "RGB", 3))
+        return TT_RGB;
+    else if (strneq(tupletypeA, "GRAYSCALE", 9) ||
+             strneq(tupletypeB, "GRAYSCALE", 9))
+        return TT_GRAYSCALE;
+    else if (strneq(tupletypeA, "BLACKANDWHITE", 13) ||
+             strneq(tupletypeB, "BLACKANDWHITE", 13))
+        return TT_BLACKANDWHITE;
+    else
+        /* Results are undefined for this case, so we do a hail Mary. */
+        return TT_RGB;
+}
+
+
+
+static int
+commonFormat(int const formatA,
+             int const formatB) {
+/*----------------------------------------------------------------------------
+   Return a viable format for the result of composing the two formats
+   'formatA' and 'formatB'.
+-----------------------------------------------------------------------------*/
+    int retval;
+
+    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)
+        retval = PPM_FORMAT;
+    else if (typeA == PGM_TYPE || typeB == PGM_TYPE)
+        retval = PGM_FORMAT;
+    else if (typeA == PBM_TYPE || typeB == PBM_TYPE)
+        retval = PBM_FORMAT;
+    else {
+        /* Results are undefined for this case, so we do a hail Mary. */
+        retval = formatA;
+    }
+    return retval;
+}
+
+
+
+static void
+determineOutputTupleType(BaseTupletype const baseTupletype,
+                         bool          const underlayHaveOpacity,
+                         char *        const tupleType,
+                         size_t        const size) {
+
+    char buffer[80];
+
+    switch (baseTupletype) {
+    case TT_BLACKANDWHITE:
+        STRSCPY(buffer, "RGB");
+        break;
+    case TT_GRAYSCALE:
+        STRSCPY(buffer, "GRAYSCALE");
+        break;
+    case TT_RGB:
+        STRSCPY(buffer, "RGB");
+        break;
+    }
+
+    if (underlayHaveOpacity)
+        STRSCAT(buffer, "_ALPHA");
+
+    strncpy(tupleType, buffer, size);
+}
+
+
+
+static void
+determineOutputType(const struct pam * const underlayPamP,
+                    const struct pam * const overlayPamP,
+                    struct pam *       const composedPamP) {
+
+    BaseTupletype const baseTupletype =
+        commonTupletype(underlayPamP->tuple_type, overlayPamP->tuple_type);
+
+    composedPamP->height = underlayPamP->height;
+    composedPamP->width  = underlayPamP->width;
+
+    composedPamP->format = commonFormat(underlayPamP->format,
+                                        overlayPamP->format);
+    composedPamP->plainformat = FALSE;
+
+    composedPamP->maxval = pm_lcm(underlayPamP->maxval, overlayPamP->maxval,
+                                  1, PNM_OVERALLMAXVAL);
+
+    composedPamP->visual = true;
+    composedPamP->color_depth = (baseTupletype == TT_RGB ? 3 : 1);
+    composedPamP->have_opacity = underlayPamP->have_opacity;
+    composedPamP->opacity_plane = (baseTupletype == TT_RGB ? 3 : 1);
+
+    composedPamP->depth =
+        (baseTupletype == TT_RGB ? 3 : 1) +
+        (underlayPamP->have_opacity ? 1 : 0);
+
+    determineOutputTupleType(baseTupletype, underlayPamP->have_opacity,
+                             composedPamP->tuple_type,
+                             sizeof(composedPamP->tuple_type));
+}
+
+
+
+static void
+determineInputAdaptations(const struct pam * const underlayPamP,
+                          const struct pam * const overlayPamP,
+                          const struct pam * const composedPamP,
+                          struct pam *       const adaptUnderlayPamP,
+                          struct pam *       const adaptOverlayPamP) {
+/*----------------------------------------------------------------------------
+   For easy of computation, this program reads a tuple row from one of the
+   input files, then transforms it something similar to the format of the
+   eventual output tuple row.  E.g. if the input is grayscale and the
+   output color, it converts the depth 1 row read from the file to a depth
+   3 row for use in computations.
+
+   This function determines what the result of that transformation should be.
+   It's not as simple as it sounds because of opacity.  The overlay may have
+   an opacity plane that has to be kept for the computations, while the output
+   has no opacity plane.
+
+   Our output PAMs are meaningless except in the fields that pertain to a
+   row of tuples.  E.g. the file descriptor and image height members are
+   meaningless.
+-----------------------------------------------------------------------------*/
+    /* We make the underlay row identical to the composed (output) row,
+       except for its width.
+    */
+
+    *adaptUnderlayPamP = *composedPamP;
+    adaptUnderlayPamP->width = underlayPamP->width;
+
+    /* Same for the overlay row, except that it retains is original
+       opacity.
+    */
+
+    adaptOverlayPamP->width = overlayPamP->width;
+    adaptOverlayPamP->tuple_type[0] = '\0';  /* a hack; this doesn't matter */
+    adaptOverlayPamP->visual = true;
+    adaptOverlayPamP->color_depth = composedPamP->color_depth;
+    adaptOverlayPamP->have_opacity = overlayPamP->have_opacity;
+    adaptOverlayPamP->opacity_plane = composedPamP->color_depth;
+    adaptOverlayPamP->depth =
+        composedPamP->color_depth +
+        (overlayPamP->have_opacity ? 1 : 0);
+    adaptOverlayPamP->maxval = composedPamP->maxval;
+    adaptOverlayPamP->bytes_per_sample = composedPamP->bytes_per_sample;
+    adaptOverlayPamP->allocation_depth = overlayPamP->allocation_depth;
+}
+
+
+
+static void
+adaptRowFormat(struct pam * const inpamP,
+               struct pam * const outpamP,
+               tuple *      const tuplerow) {
+/*----------------------------------------------------------------------------
+   Convert the row in 'tuplerow', which is in a format described by
+   *inpamP, to the format described by *outpamP.
+
+   'tuplerow' must have enough allocated depth to do this.
+-----------------------------------------------------------------------------*/
+    assert(outpamP->visual);
+    assert(inpamP->visual);
+
+    pnm_scaletuplerow(inpamP, tuplerow, tuplerow, outpamP->maxval);
+
+    if (outpamP->color_depth == 3) {
+        if (outpamP->have_opacity)
+            pnm_makerowrgba(inpamP, tuplerow);
+        else
+            pnm_makerowrgb(inpamP, tuplerow);
+    } else {
+        if (outpamP->have_opacity)
+            pnm_addopacityrow(inpamP, tuplerow);
+    }
+}
+
+
+
 static sample
 composeComponents(sample           const compA,
                   sample           const compB,
@@ -680,34 +781,6 @@ overlayPixel(tuple            const overlayTuple,
 
 
 static void
-adaptRowFormat(struct pam * const inpamP,
-               struct pam * const outpamP,
-               tuple *      const tuplerow) {
-/*----------------------------------------------------------------------------
-   Convert the row in 'tuplerow', which is in a format described by
-   *inpamP, to the format described by *outpamP.
-
-   'tuplerow' must have enough allocated depth to do this.
------------------------------------------------------------------------------*/
-    assert(outpamP->visual);
-    assert(inpamP->visual);
-
-    pnm_scaletuplerow(inpamP, tuplerow, tuplerow, outpamP->maxval);
-
-    if (outpamP->color_depth == 3) {
-        if (outpamP->have_opacity)
-            pnm_makerowrgba(inpamP, tuplerow);
-        else
-            pnm_makerowrgb(inpamP, tuplerow);
-    } else {
-        if (outpamP->have_opacity)
-            pnm_addopacityrow(inpamP, tuplerow);
-    }
-}
-
-
-
-static void
 composeRow(int              const originleft,
            struct pam *     const underlayPamP,
            struct pam *     const overlayPamP,
@@ -751,55 +824,6 @@ composeRow(int              const originleft,
 
 
 static void
-determineInputAdaptations(const struct pam * const underlayPamP,
-                          const struct pam * const overlayPamP,
-                          const struct pam * const composedPamP,
-                          struct pam *       const adaptUnderlayPamP,
-                          struct pam *       const adaptOverlayPamP) {
-/*----------------------------------------------------------------------------
-   For easy of computation, this program reads a tuple row from one of the
-   input files, then transforms it something similar to the format of the
-   eventual output tuple row.  E.g. if the input is grayscale and the
-   output color, it converts the depth 1 row read from the file to a depth
-   3 row for use in computations.
-
-   This function determines what the result of that transformation should be.
-   It's not as simple as it sounds because of opacity.  The overlay may have
-   an opacity plane that has to be kept for the computations, while the output
-   has no opacity plane.
-
-   Our output PAMs are meaningless except in the fields that pertain to a
-   row of tuples.  E.g. the file descriptor and image height members are
-   meaningless.
------------------------------------------------------------------------------*/
-    /* We make the underlay row identical to the composed (output) row,
-       except for its width.
-    */
-
-    *adaptUnderlayPamP = *composedPamP;
-    adaptUnderlayPamP->width = underlayPamP->width;
-
-    /* Same for the overlay row, except that it retains is original
-       opacity.
-    */
-
-    adaptOverlayPamP->width = overlayPamP->width;
-    adaptOverlayPamP->tuple_type[0] = '\0';  /* a hack; this doesn't matter */
-    adaptOverlayPamP->visual = true;
-    adaptOverlayPamP->color_depth = composedPamP->color_depth;
-    adaptOverlayPamP->have_opacity = overlayPamP->have_opacity;
-    adaptOverlayPamP->opacity_plane = composedPamP->color_depth;
-    adaptOverlayPamP->depth =
-        composedPamP->color_depth +
-        (overlayPamP->have_opacity ? 1 : 0);
-    adaptOverlayPamP->maxval = composedPamP->maxval;
-    adaptOverlayPamP->bytes_per_sample = composedPamP->bytes_per_sample;
-    adaptOverlayPamP->allocation_depth = overlayPamP->allocation_depth;
-}
-
-
-
-static void
 composite(int          const originleft,
           int          const origintop,
           struct pam * const underlayPamP,
@@ -899,30 +923,6 @@ composite(int          const originleft,
 
 
 
-static void
-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 ||
-            overlayPamP->height != pamP->height)
-            pm_error("Opacity map and overlay image are not the same size");
-    } else
-        fileP = NULL;
-
-    *filePP = fileP;
-}
-
-
-
 int
 main(int argc, const char *argv[]) {
 
diff --git a/editor/pamcut.c b/editor/pamcut.c
index 1c7cb4a7..7870fd70 100644
--- a/editor/pamcut.c
+++ b/editor/pamcut.c
@@ -17,6 +17,7 @@
 #include "pam.h"
 #include "shhopt.h"
 #include "mallocvar.h"
+#include "nstring.h"
 
 #define UNSPEC INT_MAX
     /* UNSPEC is the value we use for an argument that is not specified
@@ -35,10 +36,10 @@ typedef struct {
 
            If LOCTYPE_NONE: Meaningless
 
-           If LOCTYPE_FROMFAR: Number of colums from the far edge of the image
+           If LOCTYPE_FROMFAR: Number of columns 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
+           If LOCTYPE_FROMNEAR: Number of columns from the near edge of the
            image (left or top).  First column/row is 0.
         */
 } Location;
@@ -79,6 +80,73 @@ struct CmdlineInfo {
 
 
 static void
+parseLegacyLocationArgs(const char **        const argv,
+                        struct CmdlineInfo * const cmdlineP) {
+
+    int leftArg, topArg, widthArg, heightArg;
+
+    {
+        const char * error;
+        pm_string_to_int(argv[1], &leftArg,   &error);
+        if (error)
+            pm_error("Invalid number for left column argument.  %s", error);
+    }
+    {
+        const char * error;
+        pm_string_to_int(argv[2], &topArg,    &error);
+        if (error)
+            pm_error("Invalid number for top row argument.  %s",     error);
+    }
+    {
+        const char * error;
+        pm_string_to_int(argv[3], &widthArg,  &error);
+        if (error)
+            pm_error("Invalid number for width argument.  %s",       error);
+    }
+    {
+        const char * error;
+        pm_string_to_int(argv[4], &heightArg, &error);
+        if (error)
+            pm_error("Invalid number for height argument.  %s",      error);
+    }
+
+    if (leftArg < 0) {
+        cmdlineP->leftLoc.locType = LOCTYPE_FROMFAR;
+        cmdlineP->leftLoc.n       = -leftArg;
+    } else {
+        cmdlineP->leftLoc.locType = LOCTYPE_FROMNEAR;
+        cmdlineP->leftLoc.n       = leftArg;
+    }
+    if (topArg < 0) {
+        cmdlineP->topLoc.locType = LOCTYPE_FROMFAR;
+        cmdlineP->topLoc.n       = -topArg;
+    } else {
+        cmdlineP->topLoc.locType = LOCTYPE_FROMNEAR;
+        cmdlineP->topLoc.n       = topArg;
+    }
+    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);
+    }
+}
+
+
+
+static void
 parseCommandLine(int argc, const char ** const argv,
                  struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
@@ -156,51 +224,9 @@ parseCommandLine(int argc, const char ** const argv,
         break;
     }
 
-    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", &topArg) != 1)
-            pm_error("Invalid number for right column argument");
-        if (sscanf(argv[3], "%d", &widthArg) != 1)
-            pm_error("Invalid number for width argument");
-        if (sscanf(argv[4], "%d", &heightArg) != 1)
-            pm_error("Invalid number for height argument");
-
-        if (leftArg < 0) {
-            cmdlineP->leftLoc.locType = LOCTYPE_FROMFAR;
-            cmdlineP->leftLoc.n       = -leftArg;
-        } else {
-            cmdlineP->leftLoc.locType = LOCTYPE_FROMNEAR;
-            cmdlineP->leftLoc.n       = leftArg;
-        }
-        if (topArg < 0) {
-            cmdlineP->topLoc.locType = LOCTYPE_FROMFAR;
-            cmdlineP->topLoc.n       = -topArg;
-        } else {
-            cmdlineP->topLoc.locType = LOCTYPE_FROMNEAR;
-            cmdlineP->topLoc.n       = topArg;
-        }
-        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 (haveLegacyLocationArgs)
+        parseLegacyLocationArgs(argv, cmdlineP);
+    else {
         if (leftSpec && cropleftSpec)
             pm_error("You cannot specify both -left and -cropleft");
         if (leftSpec) {
diff --git a/editor/pamdice.c b/editor/pamdice.c
index b478a25a..58e24e5b 100644
--- a/editor/pamdice.c
+++ b/editor/pamdice.c
@@ -17,34 +17,30 @@
 #include "nstring.h"
 #include "mallocvar.h"
 
-#define MAXFILENAMELEN 80
-    /* Maximum number of characters we accept in filenames */
-
-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;  /* '-' if stdin */
-    const char * outstem; 
-        /* null-terminated string, max MAXFILENAMELEN-10 characters */
+    const char * outstem;
     unsigned int sliceVertically;    /* boolean */
     unsigned int sliceHorizontally;  /* boolean */
     unsigned int width;    /* Meaningless if !sliceVertically */
     unsigned int height;   /* Meaningless if !sliceHorizontally */
-    unsigned int hoverlap; 
+    unsigned int hoverlap;
         /* Meaningless if !sliceVertically.  Guaranteed < width */
-    unsigned int voverlap; 
+    unsigned int voverlap;
         /* Meaningless if !sliceHorizontally.  Guaranteed < height */
     unsigned int verbose;
 };
 
 
 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.
@@ -52,18 +48,18 @@ parseCommandLine(int argc, char ** argv,
    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;
+    optEntry * option_def;
         /* Instructions to pm_optParseOptions3 on how to parse our options.
          */
     optStruct3 opt;
-    
+
     unsigned int outstemSpec, hoverlapSpec, voverlapSpec;
     unsigned int option_def_index;
 
     MALLOCARRAY_NOFAIL(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENT3 */
-    OPTENT3(0, "width",       OPT_UINT,    &cmdlineP->width,       
+    OPTENT3(0, "width",       OPT_UINT,    &cmdlineP->width,
             &cmdlineP->sliceVertically,       0);
     OPTENT3(0, "height",      OPT_UINT,    &cmdlineP->height,
             &cmdlineP->sliceHorizontally,     0);
@@ -80,7 +76,7 @@ parseCommandLine(int argc, char ** argv,
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
     opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
 
-    pm_optParseOptions3( &argc, argv, opt, sizeof(opt), 0 );
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdline_p and others. */
 
     if (cmdlineP->sliceVertically) {
@@ -114,9 +110,11 @@ parseCommandLine(int argc, char ** argv,
         cmdlineP->inputFileName = "-";
     else if (argc-1 == 1)
         cmdlineP->inputFileName = argv[1];
-    else 
-        pm_error("Progam takes at most 1 parameter: the file specification.  "
+    else
+        pm_error("Program takes at most 1 parameter: the file specification.  "
                  "You specified %u", argc-1);
+
+    free(option_def);
 }
 
 
@@ -133,7 +131,7 @@ divup(unsigned int const dividend,
 
 
 static void
-computeSliceGeometry(struct cmdlineInfo const cmdline,
+computeSliceGeometry(struct CmdlineInfo const cmdline,
                      struct pam         const inpam,
                      bool               const verbose,
                      unsigned int *     const nHorizSliceP,
@@ -146,20 +144,30 @@ computeSliceGeometry(struct cmdlineInfo const cmdline,
 /*----------------------------------------------------------------------------
    Compute the geometry of the slices, both common slices and possibly
    smaller remainder slices at the top and right.
+
+   We return the following.
+
+   *nHorizSliceP is the number of horizontal slices.  *sliceHeightP is the
+   height of every slice except possibly the bottom one.  *bottomSliceHeightP
+   is the height of the bottom slice.
+
+   *nVertSliceP is the number of vertical slices.  *sliceWidthP is the width
+   of every slice except possibly the rightmost one.  *rightSliceWidthP is the
+   width of the rightmost slice.
 -----------------------------------------------------------------------------*/
     if (cmdline.sliceHorizontally) {
         if (cmdline.height >= inpam.height)
             *nHorizSliceP = 1;
         else
-            *nHorizSliceP = 1 + divup(inpam.height - cmdline.height, 
+            *nHorizSliceP = 1 + divup(inpam.height - cmdline.height,
                                       cmdline.height - cmdline.voverlap);
         *sliceHeightP = cmdline.height;
 
-        *bottomSliceHeightP = 
-            inpam.height - (*nHorizSliceP-1) * (cmdline.height - cmdline.voverlap);
+        *bottomSliceHeightP = inpam.height -
+            (*nHorizSliceP-1) * (cmdline.height - cmdline.voverlap);
     } else {
-        *nHorizSliceP = 1;
-        *sliceHeightP = inpam.height;
+        *nHorizSliceP       = 1;
+        *sliceHeightP       = inpam.height;
         *bottomSliceHeightP = inpam.height;
     }
 
@@ -167,14 +175,14 @@ computeSliceGeometry(struct cmdlineInfo const cmdline,
         if (cmdline.width >= inpam.width)
             *nVertSliceP = 1;
         else
-            *nVertSliceP = 1 + divup(inpam.width - cmdline.width, 
+            *nVertSliceP = 1 + divup(inpam.width - cmdline.width,
                                      cmdline.width - cmdline.hoverlap);
         *sliceWidthP = cmdline.width;
-        *rightSliceWidthP = 
-            inpam.width - (*nVertSliceP-1) * (cmdline.width - cmdline.hoverlap);
+        *rightSliceWidthP = inpam.width -
+            (*nVertSliceP-1) * (cmdline.width - cmdline.hoverlap);
     } else {
-        *nVertSliceP = 1;
-        *sliceWidthP = inpam.width;
+        *nVertSliceP      = 1;
+        *sliceWidthP      = inpam.width;
         *rightSliceWidthP = inpam.width;
     }
 
@@ -185,7 +193,7 @@ computeSliceGeometry(struct cmdlineInfo const cmdline,
                    *nVertSliceP, *nHorizSliceP,
                    *sliceWidthP, *sliceHeightP);
         if (*rightSliceWidthP != *sliceWidthP)
-            pm_message("Right vertical slice is only %u wide", 
+            pm_message("Right vertical slice is only %u wide",
                        *rightSliceWidthP);
         if (*bottomSliceHeightP != *sliceHeightP)
             pm_message("Bottom horizontal slice is only %u high",
@@ -212,7 +220,7 @@ ndigits(unsigned int const arg) {
 
 
 static void
-computeOutputFilenameFormat(int           const format, 
+computeOutputFilenameFormat(int           const format,
                             unsigned int  const nHorizSlice,
                             unsigned int  const nVertSlice,
                             const char ** const filenameFormatP) {
@@ -228,7 +236,7 @@ computeOutputFilenameFormat(int           const format,
         pm_error("INTERNAL ERROR: impossible value for libnetpbm image "
                  "fomat code: %d", format);
     }
-    
+
     pm_asprintf(filenameFormatP, "%%s_%%0%uu_%%0%uu.%s",
                 ndigits(nHorizSlice), ndigits(nVertSlice), filenameSuffix);
 
@@ -239,16 +247,16 @@ computeOutputFilenameFormat(int           const format,
 
 
 static void
-openOutStreams(struct pam   const inpam, 
-               struct pam         outpam[],
-               unsigned int const horizSlice, 
+openOutStreams(struct pam   const inpam,
+               struct pam * const outpam,
+               unsigned int const horizSlice,
                unsigned int const nHorizSlice,
                unsigned int const nVertSlice,
-               unsigned int const sliceHeight, 
+               unsigned int const sliceHeight,
                unsigned int const sliceWidth,
                unsigned int const rightSliceWidth,
                unsigned int const hOverlap,
-               char         const outstem[]) {
+               const char * const outstem) {
 /*----------------------------------------------------------------------------
    Open the output files for a single horizontal slice (there's one file
    for each vertical slice) and write the Netpbm headers to them.  Also
@@ -270,34 +278,37 @@ openOutStreams(struct pam   const inpam,
         else {
             outpam[vertSlice] = inpam;
             outpam[vertSlice].file = pm_openw(filename);
-            
-            outpam[vertSlice].width = 
+
+            outpam[vertSlice].width =
                 vertSlice < nVertSlice-1 ? sliceWidth : rightSliceWidth;
-            
+
             outpam[vertSlice].height = sliceHeight;
-            
+
             pnm_writepaminit(&outpam[vertSlice]);
 
             pm_strfree(filename);
         }
-    }        
+    }
     pm_strfree(filenameFormat);
 }
 
 
 
 static void
-closeOutFiles(struct pam pam[], unsigned int const nVertSlice) {
+closeOutFiles(struct pam * const pam,
+              unsigned int const nVertSlice) {
 
     unsigned int vertSlice;
-    
+
     for (vertSlice = 0; vertSlice < nVertSlice; ++vertSlice)
         pm_close(pam[vertSlice].file);
 }
 
+
+
 static void
-sliceRow(tuple              inputRow[], 
-         struct pam         outpam[], 
+sliceRow(tuple *      const inputRow,
+         struct pam * const outpam,
          unsigned int const nVertSlice,
          unsigned int const hOverlap) {
 /*----------------------------------------------------------------------------
@@ -308,14 +319,15 @@ sliceRow(tuple              inputRow[],
    'hOverlap', which is meaningful only when nVertSlice is greater than 1,
    is the amount by which slices overlap each other.
 -----------------------------------------------------------------------------*/
-    tuple * outputRow;
-    unsigned int vertSlice;
     unsigned int const sliceWidth = outpam[0].width;
-    unsigned int const stride = 
+    unsigned int const stride =
         nVertSlice > 1 ? sliceWidth - hOverlap : sliceWidth;
 
-    for (vertSlice = 0, outputRow = inputRow; 
-         vertSlice < nVertSlice; 
+    tuple *      outputRow;
+    unsigned int vertSlice;
+
+    for (vertSlice = 0, outputRow = inputRow;
+         vertSlice < nVertSlice;
          outputRow += stride, ++vertSlice) {
         pnm_writepamrow(&outpam[vertSlice], outputRow);
     }
@@ -333,15 +345,15 @@ sliceRow(tuple              inputRow[],
 struct inputWindow {
     unsigned int windowSize;
     unsigned int firstRowInWindow;
-    struct pam pam;
-    tuple ** rows;
+    struct pam   pam;
+    tuple **     rows;
 };
 
 static void
 initInput(struct inputWindow * const inputWindowP,
           struct pam *         const pamP,
           unsigned int         const windowSize) {
-    
+
     struct pam allocPam;  /* Just for allocating the window array */
     unsigned int i;
 
@@ -350,7 +362,7 @@ initInput(struct inputWindow * const inputWindowP,
 
     allocPam = *pamP;
     allocPam.height = windowSize;
-    
+
     inputWindowP->rows = pnm_allocpamarray(&allocPam);
 
     inputWindowP->firstRowInWindow = 0;
@@ -371,6 +383,8 @@ termInputWindow(struct inputWindow * const inputWindowP) {
     pnm_freepamarray(inputWindowP->rows, &freePam);
 }
 
+
+
 static tuple *
 getInputRow(struct inputWindow * const inputWindowP,
             unsigned int         const row) {
@@ -393,7 +407,7 @@ getInputRow(struct inputWindow * const inputWindowP,
         /* Read in the new last row in the window */
         inputWindowP->rows[i] = oldRow0;  /* Reuse the memory */
         pnm_readpamrow(&inputWindowP->pam, inputWindowP->rows[i]);
-    }        
+    }
 
     return inputWindowP->rows[row - inputWindowP->firstRowInWindow];
 }
@@ -420,10 +434,10 @@ allocOutpam(unsigned int  const nVertSlice,
 
 
 int
-main(int argc, char ** argv) {
+main(int argc, const char ** argv) {
 
-    struct cmdlineInfo cmdline;
-    FILE    *ifP;
+    struct CmdlineInfo cmdline;
+    FILE    * ifP;
     struct pam inpam;
     unsigned int horizSlice;
         /* Number of the current horizontal slice.  Slices are numbered
@@ -433,7 +447,7 @@ main(int argc, char ** argv) {
         /* Width in pam columns of each vertical slice, except
            the rightmost slice, which may be narrower.  If we aren't slicing
            vertically, that means one slice, i.e. the slice width
-           is the image width.  
+           is the image width.
         */
     unsigned int rightSliceWidth;
         /* Width in pam columns of the rightmost vertical slice. */
@@ -441,23 +455,23 @@ main(int argc, char ** argv) {
         /* Height in pam rows of each horizontal slice, except
            the bottom slice, which may be shorter.  If we aren't slicing
            horizontally, that means one slice, i.e. the slice height
-           is the image height.  
+           is the image height.
         */
     unsigned int bottomSliceHeight;
         /* Height in pam rows of the bottom horizontal slice. */
     unsigned int nHorizSlice;
     unsigned int nVertSlice;
     struct inputWindow inputWindow;
-    
+
     struct pam * outpam;
-        /* malloc'ed.  outpam[x] is the pam structure that controls
+        /* malloc'ed array.  outpam[x] is the pam structure that controls
            the current horizontal slice of vertical slice x.
         */
 
-    pnm_init(&argc, argv);
-    
+    pm_proginit(&argc, argv);
+
     parseCommandLine(argc, argv, &cmdline);
-        
+
     ifP = pm_openr(cmdline.inputFileName);
 
     pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
@@ -467,28 +481,29 @@ main(int argc, char ** argv) {
                          &nVertSlice, &sliceWidth, &rightSliceWidth);
 
     allocOutpam(nVertSlice, &outpam);
-    
-    initInput(&inputWindow, &inpam, 
+
+    initInput(&inputWindow, &inpam,
               nHorizSlice > 1 ? cmdline.voverlap + 1 : 1);
 
     for (horizSlice = 0; horizSlice < nHorizSlice; ++horizSlice) {
-        unsigned int const thisSliceFirstRow = 
+        unsigned int const thisSliceFirstRow =
             horizSlice > 0 ? horizSlice * (sliceHeight - cmdline.voverlap) : 0;
             /* Note that 'cmdline.voverlap' is not defined when there is only
                one horizontal slice
             */
-        unsigned int const thisSliceHeight = 
+        unsigned int const thisSliceHeight =
             horizSlice < nHorizSlice-1 ? sliceHeight : bottomSliceHeight;
 
         unsigned int row;
 
-        openOutStreams(inpam, outpam, horizSlice, nHorizSlice, nVertSlice, 
+        openOutStreams(inpam, outpam, horizSlice, nHorizSlice, nVertSlice,
                        thisSliceHeight, sliceWidth, rightSliceWidth,
                        cmdline.hoverlap, cmdline.outstem);
 
         for (row = 0; row < thisSliceHeight; ++row) {
             tuple * const inputRow =
                 getInputRow(&inputWindow, thisSliceFirstRow + row);
+
             sliceRow(inputRow, outpam, nVertSlice, cmdline.hoverlap);
         }
         closeOutFiles(outpam, nVertSlice);
@@ -502,3 +517,6 @@ main(int argc, char ** argv) {
 
     return 0;
 }
+
+
+
diff --git a/editor/pamditherbw.c b/editor/pamditherbw.c
index 4b192e6e..694b2c21 100644
--- a/editor/pamditherbw.c
+++ b/editor/pamditherbw.c
@@ -14,12 +14,14 @@
 #include <string.h>
 
 #include "pm_c_util.h"
-#include "pam.h"
-#include "dithers.h"
+#include "rand.h"
 #include "mallocvar.h"
 #include "shhopt.h"
+#include "pam.h"
+#include "dithers.h"
 #include "pm_gamma.h"
 
+
 enum halftone {QT_FS,
                QT_ATKINSON,
                QT_THRESH,
@@ -42,7 +44,7 @@ struct cmdlineInfo {
     enum halftone halftone;
     unsigned int  clumpSize;
         /* Defined only for halftone == QT_HILBERT */
-    unsigned int  clusterRadius;  
+    unsigned int  clusterRadius;
         /* Defined only for halftone == QT_CLUSTER */
     float         threshval;
     unsigned int  randomseed;
@@ -85,9 +87,9 @@ parseCommandLine(int argc, char ** argv,
     OPTENT3(0, "c4",        OPT_FLAG,  NULL, &cluster4Opt,  0);
     OPTENT3(0, "cluster8",  OPT_FLAG,  NULL, &cluster8Opt,  0);
     OPTENT3(0, "c8",        OPT_FLAG,  NULL, &cluster8Opt,  0);
-    OPTENT3(0, "value",     OPT_FLOAT, &cmdlineP->threshval, 
+    OPTENT3(0, "value",     OPT_FLOAT, &cmdlineP->threshval,
             &valueSpec, 0);
-    OPTENT3(0, "clump",     OPT_UINT,  &cmdlineP->clumpSize, 
+    OPTENT3(0, "clump",     OPT_UINT,  &cmdlineP->clumpSize,
             &clumpSpec, 0);
     OPTENT3(0,   "randomseed",   OPT_UINT,    &cmdlineP->randomseed,
             &cmdlineP->randomseedSpec,      0);
@@ -101,10 +103,10 @@ parseCommandLine(int argc, char ** argv,
 
     free(option_def);
 
-    if (floydOpt + atkinsonOpt + thresholdOpt + hilbertOpt + dither8Opt + 
+    if (floydOpt + atkinsonOpt + thresholdOpt + hilbertOpt + dither8Opt +
         cluster3Opt + cluster4Opt + cluster8Opt == 0)
         cmdlineP->halftone = QT_FS;
-    else if (floydOpt + atkinsonOpt + thresholdOpt + dither8Opt + 
+    else if (floydOpt + atkinsonOpt + thresholdOpt + hilbertOpt + dither8Opt +
         cluster3Opt + cluster4Opt + cluster8Opt > 1)
         pm_error("Cannot specify more than one halftoning type");
     else {
@@ -135,7 +137,7 @@ parseCommandLine(int argc, char ** argv,
         } else if (cluster8Opt) {
             cmdlineP->halftone = QT_CLUSTER;
             cmdlineP->clusterRadius = 8;
-        } else 
+        } else
             pm_error("INTERNAL ERROR.  No halftone option");
     }
 
@@ -190,152 +192,211 @@ makeOutputPam(unsigned int const width,
 
 /* Hilbert curve tracer */
 
-#define MAXORD 18
+typedef struct {
+    unsigned int x;
+    unsigned int y;
+} Point;
+
+
 
-struct Hil {
-    int order;
-    int ord;
+typedef struct {
+    bool firstPointDone;
+    unsigned int order;
+    unsigned int ord;
+        /* Meaningful only when 'firstPointDone' is true */
     int turn;
     int dx;
     int dy;
     int x;
     int y;
-    int stage[MAXORD];
-    int width;
-    int height;
-};
+    int stage[sizeof(unsigned int)*8];
+        /* One entry for every bit in the height or width, each of which
+           is an unsigned int
+        */
+    unsigned int width;
+    unsigned int height;
+} Hilbert;
 
-static void 
-initHilbert(int          const w, 
-            int          const h,
-            struct Hil * const hilP) {
+static void
+hilbert_init(Hilbert *    const hilP,
+             unsigned int const width,
+             unsigned int const height) {
 /*----------------------------------------------------------------------------
-  Initialize the Hilbert curve tracer 
+  Initialize the Hilbert curve tracer
 -----------------------------------------------------------------------------*/
-    int big, ber;
-    hilP->width = w;
-    hilP->height = h;
-    big = w > h ? w : h;
-    for (ber = 2, hilP->order = 1; ber < big; ber <<= 1, hilP->order++);
-    if (hilP->order > MAXORD)
-        pm_error("Sorry, hilbert order is too large");
-    hilP->ord = hilP->order;
-    hilP->order--;
+    unsigned int const maxDim = MAX(width, height);
+
+    unsigned int order;
+
+    hilP->width  = width;
+    hilP->height = height;
+    {
+        unsigned int ber;
+        for (ber = 2, order = 0; ber < maxDim; ber <<= 1, ++order);
+    }
+    assert(order + 1 <= ARRAY_SIZE(hilP->stage));
+    hilP->order = order;
+    hilP->firstPointDone = false;
 }
 
 
 
-static bool
-hilbert(int *        const px,
-        int *        const py,
-        struct Hil * const hilP) {
-/*----------------------------------------------------------------------------
-  Return non-zero if got another point
------------------------------------------------------------------------------*/
-    int temp;
-    if (hilP->ord > hilP->order) {
-        /* have to do first point */
-
-        hilP->ord--;
-        hilP->stage[hilP->ord] = 0;
-        hilP->turn = -1;
-        hilP->dy = 1;
-        hilP->dx = hilP->x = hilP->y = 0;
-        *px = *py = 0;
-        return true;
-    }
+static void
+hilbert_doFirstPoint(Hilbert * const hilbertP,
+                     bool *    const gotPointP,
+                     Point *   const pointP) {
+
+    hilbertP->ord = hilbertP->order;
+    hilbertP->stage[hilbertP->ord] = 0;
+    hilbertP->turn = -1;
+    hilbertP->dy = 1;
+    hilbertP->dx = hilbertP->x = hilbertP->y = 0;
+    hilbertP->firstPointDone = true;
+
+    pointP->x = 0; pointP->y = 0;
+    *gotPointP = true;
+}
+
+
+
+static void
+hilbert_advanceStateMachine(Hilbert * const hilbertP,
+                            bool *    const gotPointP,
+                            Point *   const pointP) {
 
-    /* Operate the state machine */
     for(;;)  {
-        switch (hilP->stage[hilP->ord]) {
-        case 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;
+        switch (hilbertP->stage[hilbertP->ord]) {
+        case 0: {
+            int const origDy = hilbertP->dy;
+
+            hilbertP->turn = -hilbertP->turn;
+            hilbertP->dy = -hilbertP->turn * hilbertP->dx;
+            hilbertP->dx = hilbertP->turn * origDy;
+            if (hilbertP->ord > 0) {
+                hilbertP->stage[hilbertP->ord] = 1;
+                --hilbertP->ord;
+                hilbertP->stage[hilbertP->ord]=0;
                 continue;
             }
-        case 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 1: {
+            hilbertP->x += hilbertP->dx;
+            hilbertP->y += hilbertP->dy;
+            if (hilbertP->x < hilbertP->width &&
+                hilbertP->y < hilbertP->height) {
+
+                hilbertP->stage[hilbertP->ord] = 2;
+
+                pointP->x = hilbertP->x;
+                pointP->y = hilbertP->y;
+                *gotPointP = true;
+                return;
             }
-        case 2:
-            hilP->turn = -hilP->turn;
-            temp = hilP->dy;
-            hilP->dy = -hilP->turn * hilP->dx;
-            hilP->dx = hilP->turn * temp;
-            if (hilP->ord > 0) { 
+        }
+        case 2: {
+            int const origDy = hilbertP->dy;
+
+            hilbertP->turn = -hilbertP->turn;
+            hilbertP->dy = -hilbertP->turn * hilbertP->dx;
+            hilbertP->dx = hilbertP->turn * origDy;
+            if (hilbertP->ord > 0) {
                 /* recurse */
 
-                hilP->stage[hilP->ord] = 3;
-                hilP->ord--;
-                hilP->stage[hilP->ord]=0;
+                hilbertP->stage[hilbertP->ord] = 3;
+                --hilbertP->ord;
+                hilbertP->stage[hilbertP->ord]=0;
                 continue;
             }
-        case 3:
-            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 3: {
+            hilbertP->x += hilbertP->dx;
+            hilbertP->y += hilbertP->dy;
+            if (hilbertP->x < hilbertP->width &&
+                hilbertP->y < hilbertP->height) {
+
+                hilbertP->stage[hilbertP->ord] = 4;
+
+                pointP->x = hilbertP->x;
+                pointP->y = hilbertP->y;
+                *gotPointP = true;
+                return;
             }
-        case 4:
-            if (hilP->ord > 0) {
+        }
+        case 4: {
+            if (hilbertP->ord > 0) {
                 /* recurse */
-                hilP->stage[hilP->ord] = 5;
-                hilP->ord--;
-                hilP->stage[hilP->ord]=0;
+                hilbertP->stage[hilbertP->ord] = 5;
+                --hilbertP->ord;
+                hilbertP->stage[hilbertP->ord]=0;
                 continue;
             }
-        case 5:
-            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 5: {
+            int const origDy = hilbertP->dy;
+
+            hilbertP->dy = -hilbertP->turn * hilbertP->dx;
+            hilbertP->dx = hilbertP->turn * origDy;
+            hilbertP->turn = -hilbertP->turn;
+            hilbertP->x += hilbertP->dx;
+            hilbertP->y += hilbertP->dy;
+            if (hilbertP->x < hilbertP->width &&
+                hilbertP->y < hilbertP->height) {
+
+                hilbertP->stage[hilbertP->ord] = 6;
+
+                pointP->x = hilbertP->x;
+                pointP->y = hilbertP->y;
+                *gotPointP = true;
+                return;
             }
-        case 6:
-            if (hilP->ord > 0) {
+        }
+        case 6: {
+            if (hilbertP->ord > 0) {
                 /* recurse */
-                hilP->stage[hilP->ord] = 7;
-                hilP->ord--;
-                hilP->stage[hilP->ord]=0;
+                hilbertP->stage[hilbertP->ord] = 7;
+                --hilbertP->ord;
+                hilbertP->stage[hilbertP->ord]=0;
                 continue;
             }
-        case 7:
-            temp = hilP->dy;
-            hilP->dy = -hilP->turn * hilP->dx;
-            hilP->dx = hilP->turn * temp;
-            hilP->turn = -hilP->turn;
+        }
+        case 7: {
+            int const origDy = hilbertP->dy;
+
+            hilbertP->dy = -hilbertP->turn * hilbertP->dx;
+            hilbertP->dx = hilbertP->turn * origDy;
+            hilbertP->turn = -hilbertP->turn;
             /* Return from a recursion */
-            if (hilP->ord < hilP->order)
-                hilP->ord++;
-            else
-                return false;
+            if (hilbertP->ord < hilbertP->order)
+                ++hilbertP->ord;
+            else {
+                *gotPointP = false;
+                return;
+            }
         }
+        }
+    }
+}
+
+
+
+static void
+hilbert_trace(Hilbert * const hilbertP,
+              bool *    const gotPointP,
+              Point *   const pointP) {
+/*----------------------------------------------------------------------------
+  ...
+  Return *gotPointP true iff we got another point
+-----------------------------------------------------------------------------*/
+    if (!hilbertP->firstPointDone) {
+        hilbert_doFirstPoint(hilbertP, gotPointP, pointP);
+    } else {
+        hilbert_advanceStateMachine(hilbertP, gotPointP, pointP);
     }
 }
 
 
 
-static void 
+static void
 doHilbert(FILE *       const ifP,
           unsigned int const clumpSize) {
 /*----------------------------------------------------------------------------
@@ -356,10 +417,10 @@ doHilbert(FILE *       const ifP,
     tuple ** grays;
     tuple ** bits;
 
-    struct Hil hil;
+    Hilbert hilbert;
 
     int end;
-    int *x,*y;
+    Point * point;
     int sum;
 
     grays = pnm_readpam(ifP, &graypam, PAM_STRUCT_SIZE(tuple_type));
@@ -368,11 +429,11 @@ doHilbert(FILE *       const ifP,
 
     bits = pnm_allocpamarray(&bitpam);
 
-    MALLOCARRAY(x, clumpSize);
-    MALLOCARRAY(y, clumpSize);
-    if (x == NULL  || y == NULL)
-        pm_error("out of memory");
-    initHilbert(graypam.width, graypam.height, &hil);
+    MALLOCARRAY(point, clumpSize);
+    if (!point)
+        pm_error("Unable to get memory for clump of %u points", clumpSize);
+
+    hilbert_init(&hilbert, graypam.width, graypam.height);
 
     sum = 0;
     end = clumpSize;
@@ -380,19 +441,20 @@ doHilbert(FILE *       const ifP,
     while (end == clumpSize) {
         unsigned int i;
         /* compute the next cluster co-ordinates along hilbert path */
-        for (i = 0; i < end; i++) {
+        for (i = 0; i < end; ++i) {
             bool gotPoint;
-            gotPoint = hilbert(&x[i], &y[i], &hil);
+            hilbert_trace(&hilbert, &gotPoint, &point[i]);
             if (!gotPoint)
                 end = i;    /* we reached the end */
         }
         /* sum levels */
-        for (i = 0; i < end; i++)
-            sum += grays[y[i]][x[i]][0];
+        for (i = 0; i < end; ++i)
+            sum += grays[point[i].y][point[i].x][0];
         /* dither half and half along path */
-        for (i = 0; i < end; i++) {
-            unsigned int const row = y[i];
-            unsigned int const col = x[i];
+        for (i = 0; i < end; ++i) {
+            unsigned int const row = point[i].y;
+            unsigned int const col = point[i].x;
+
             if (sum >= graypam.maxval) {
                 bits[row][col][0] = 1;
                 sum -= graypam.maxval;
@@ -400,7 +462,7 @@ doHilbert(FILE *       const ifP,
                 bits[row][col][0] = 0;
         }
     }
-    free(x);    free(y); 
+    free(point);
     pnm_writepam(&bitpam, bits);
 
     pnm_freepamarray(bits, &bitpam);
@@ -412,7 +474,7 @@ doHilbert(FILE *       const ifP,
 struct converter {
     void (*convertRow)(struct converter * const converterP,
                        unsigned int       const row,
-                       tuplen                   grayrow[], 
+                       tuplen                   grayrow[],
                        tuple                    bitrow[]);
     void (*destroy)(struct converter * const converterP);
     unsigned int cols;
@@ -423,11 +485,11 @@ struct converter {
 
 struct fsState {
     float * thiserr;
-        /* thiserr[N] is the power from previous pixels to include in 
+        /* thiserr[N] is the power from previous pixels to include in
            future column N of the current row.
         */
     float * nexterr;
-        /* nexterr[N] is the power from previous pixels to include in 
+        /* nexterr[N] is the power from previous pixels to include in
            future column N of the next row.
         */
     bool fs_forward;
@@ -455,7 +517,7 @@ fsConvertRow(struct converter * const converterP,
 
     unsigned int limitcol;
     unsigned int col;
-    
+
     for (col = 0; col < converterP->cols + 2; ++col)
         nexterr[col] = 0.0;
 
@@ -494,18 +556,18 @@ fsConvertRow(struct converter * const converterP,
             nexterr[col    ] += (accum * 3) / 16;
             nexterr[col + 1] += (accum * 5) / 16;
             nexterr[col + 2] += (accum * 1) / 16;
-            
+
             ++col;
         } else {
             thiserr[col    ] += (accum * 7) / 16;
             nexterr[col + 2] += (accum * 3) / 16;
             nexterr[col + 1] += (accum * 5) / 16;
             nexterr[col    ] += (accum * 1) / 16;
-            
+
             --col;
         }
     } while (col != limitcol);
-    
+
     stateP->thiserr = nexterr;
     stateP->nexterr = thiserr;
     stateP->fs_forward = ! stateP->fs_forward;
@@ -528,7 +590,9 @@ fsDestroy(struct converter * const converterP) {
 
 static struct converter
 createFsConverter(struct pam * const graypamP,
-                  float        const threshFraction) {
+                  float        const threshFraction,
+                  bool         const randomseedSpec,
+                  unsigned int const randomseed) {
 
     struct fsState * stateP;
     struct converter converter;
@@ -545,9 +609,17 @@ createFsConverter(struct pam * const graypamP,
 
     {
         /* (random errors in [-1/8 .. 1/8]) */
+
         unsigned int col;
+        struct pm_randSt randSt;
+
+        pm_randinit(&randSt);
+        pm_srand2(&randSt, randomseedSpec, randomseed);
+
         for (col = 0; col < graypamP->width + 2; ++col)
-            stateP->thiserr[col] = ((float)rand()/RAND_MAX - 0.5) / 4;
+            stateP->thiserr[col] = (pm_drand(&randSt) - 0.5) / 4;
+
+        pm_randterm(&randSt);
     }
 
     stateP->halfWhite = threshFraction;
@@ -584,7 +656,7 @@ struct atkinsonState {
 
 static void
 moveAtkinsonErrorWindowDown(struct converter * const converterP) {
-                            
+
     struct atkinsonState * const stateP = converterP->stateP;
 
     float * const oldError0 = stateP->error[0];
@@ -628,7 +700,7 @@ atkinsonConvertRow(struct converter * const converterP,
             accum -= stateP->white;
         } else
             bitrow[col] = blackTuple;
-        
+
         /* Forward to future output pixels 3/4 of the power from current
            input pixel and the power forwarded from previous input
            pixels to the current pixel, less any power we put into the
@@ -642,7 +714,7 @@ atkinsonConvertRow(struct converter * const converterP,
         error[1][col+1] += accum/8;
         error[2][col  ] += accum/8;
     }
-    
+
     moveAtkinsonErrorWindowDown(converterP);
 }
 
@@ -665,12 +737,14 @@ atkinsonDestroy(struct converter * const converterP) {
 
 static struct converter
 createAtkinsonConverter(struct pam * const graypamP,
-                        float        const threshFraction) {
+                        float        const threshFraction,
+                        bool         const randomseedSpec,
+                        unsigned int const randomseed) {
 
     struct atkinsonState * stateP;
     struct converter converter;
     unsigned int relRow;
-    
+
     converter.cols       = graypamP->width;
     converter.convertRow = &atkinsonConvertRow;
     converter.destroy    = &atkinsonDestroy;
@@ -683,11 +757,18 @@ createAtkinsonConverter(struct pam * const graypamP,
     {
         /* (random errors in [-1/8 .. 1/8]) */
         unsigned int col;
+        struct pm_randSt randSt;
+
+        pm_randinit(&randSt);
+        pm_srand2(&randSt, randomseedSpec, randomseed);
+
         for (col = 0; col < graypamP->width + 2; ++col) {
-            stateP->error[0][col] = ((float)rand()/RAND_MAX - 0.5) / 4;
+            stateP->error[0][col] = (pm_drand(&randSt) - 0.5) / 4;
             stateP->error[1][col] = 0.0;
             stateP->error[2][col] = 0.0;
         }
+
+        pm_randterm(&randSt);
     }
 
     stateP->halfWhite = threshFraction;
@@ -710,7 +791,7 @@ threshConvertRow(struct converter * const converterP,
                  unsigned int       const row,
                  tuplen                   grayrow[],
                  tuple                    bitrow[]) {
-    
+
     struct threshState * const stateP = converterP->stateP;
 
     unsigned int col;
@@ -740,7 +821,7 @@ createThreshConverter(struct pam * const graypamP,
     converter.cols       = graypamP->width;
     converter.convertRow = &threshConvertRow;
     converter.destroy    = &threshDestroy;
-    
+
     stateP->threshval    = threshFraction;
     converter.stateP     = stateP;
 
@@ -768,7 +849,7 @@ clusterConvertRow(struct converter * const converterP,
     unsigned int col;
 
     for (col = 0; col < converterP->cols; ++col) {
-        float const threshold = 
+        float const threshold =
             stateP->clusterMatrix[row % diameter][col % diameter];
         bitrow[col] =
             grayrow[col][0] > threshold ? whiteTuple : blackTuple;
@@ -789,7 +870,7 @@ clusterDestroy(struct converter * const converterP) {
         free(stateP->clusterMatrix[row]);
 
     free(stateP->clusterMatrix);
-    
+
     free(stateP);
 }
 
@@ -799,14 +880,14 @@ static struct converter
 createClusterConverter(struct pam *    const graypamP,
                        enum ditherType const ditherType,
                        unsigned int    const radius) {
-    
+
     /* TODO: We create a floating point normalized, gamma-adjusted
-       dither matrix from the old integer dither matrices that were 
+       dither matrix from the old integer dither matrices that were
        developed for use with integer arithmetic.  We really should
        just change the literal values in dither.h instead of computing
        the matrix from the integer literal values here.
     */
-    
+
     int const clusterNormalizer = radius * radius * 2;
     unsigned int const diameter = 2 * radius;
 
@@ -827,16 +908,16 @@ createClusterConverter(struct pam *    const graypamP,
         unsigned int col;
 
         MALLOCARRAY_NOFAIL(stateP->clusterMatrix[row], diameter);
-        
+
         for (col = 0; col < diameter; ++col) {
             switch (ditherType) {
-            case DT_REGULAR: 
+            case DT_REGULAR:
                 switch (radius) {
-                case 8: 
-                    stateP->clusterMatrix[row][col] = 
+                case 8:
+                    stateP->clusterMatrix[row][col] =
                         pm_gamma709((float)dither8[row][col] / 256);
                     break;
-                default: 
+                default:
                     pm_error("INTERNAL ERROR: invalid radius");
                 }
                 break;
@@ -849,13 +930,13 @@ createClusterConverter(struct pam *    const graypamP,
                 default:
                     pm_error("INTERNAL ERROR: invalid radius");
                 }
-                stateP->clusterMatrix[row][col] = 
+                stateP->clusterMatrix[row][col] =
                     pm_gamma709((float)val / clusterNormalizer);
             }
             break;
             }
         }
-    }            
+    }
 
     converter.stateP = stateP;
 
@@ -874,8 +955,6 @@ main(int argc, char *argv[]) {
 
     parseCommandLine(argc, argv, &cmdline);
 
-    srand(cmdline.randomseedSpec ? cmdline.randomseed : pm_randseed());
-
     ifP = pm_openr(cmdline.inputFilespec);
 
     if (cmdline.halftone == QT_HILBERT)
@@ -891,28 +970,32 @@ main(int argc, char *argv[]) {
         pnm_readpaminit(ifP, &graypam, PAM_STRUCT_SIZE(tuple_type));
 
         bitpam = makeOutputPam(graypam.width, graypam.height);
-        
+
         pnm_writepaminit(&bitpam);
 
         switch (cmdline.halftone) {
         case QT_FS:
-            converter = createFsConverter(&graypam, cmdline.threshval);
+            converter = createFsConverter(&graypam, cmdline.threshval,
+                                          cmdline.randomseedSpec,
+                                          cmdline.randomseed);
             break;
         case QT_ATKINSON:
-            converter = createAtkinsonConverter(&graypam, cmdline.threshval);
+            converter = createAtkinsonConverter(&graypam, cmdline.threshval,
+                                                cmdline.randomseedSpec,
+                                                cmdline.randomseed);
             break;
         case QT_THRESH:
             converter = createThreshConverter(&graypam, cmdline.threshval);
             break;
-        case QT_DITHER8: 
-            converter = createClusterConverter(&graypam, DT_REGULAR, 8); 
+        case QT_DITHER8:
+            converter = createClusterConverter(&graypam, DT_REGULAR, 8);
             break;
-        case QT_CLUSTER: 
-            converter = createClusterConverter(&graypam, 
-                                               DT_CLUSTER, 
+        case QT_CLUSTER:
+            converter = createClusterConverter(&graypam,
+                                               DT_CLUSTER,
                                                cmdline.clusterRadius);
             break;
-        case QT_HILBERT: 
+        case QT_HILBERT:
                 pm_error("INTERNAL ERROR: halftone is QT_HILBERT where it "
                          "shouldn't be.");
                 break;
@@ -925,7 +1008,7 @@ main(int argc, char *argv[]) {
             pnm_readpamrown(&graypam, grayrow);
 
             converter.convertRow(&converter, row, grayrow, bitrow);
-            
+
             pnm_writepamrow(&bitpam, bitrow);
         }
         free(bitrow);
diff --git a/editor/pamenlarge.c b/editor/pamenlarge.c
index 56a8c6f7..eb3b6ba2 100644
--- a/editor/pamenlarge.c
+++ b/editor/pamenlarge.c
@@ -690,6 +690,7 @@ enlargePbm(struct pam * const inpamP,
         unsigned int const rightPadding =
             scaleMethod == METHOD_GENERAL ? 0 : (xScaleFactor - 1) * 8;
 
+        assert (outcols < UINT_MAX - rightPadding);
         outrow = pbm_allocrow_packed(outcols + rightPadding);
 
         if (scaleMethod == METHOD_GENERAL)
diff --git a/editor/pamflip/pamflip.c b/editor/pamflip/pamflip.c
index bc752208..d3aa76df 100644
--- a/editor/pamflip/pamflip.c
+++ b/editor/pamflip/pamflip.c
@@ -1007,7 +1007,7 @@ stitchCellsToOutput(outputMap *  const outputMapP,
                 outCol += outCellPamP->width;
             }
 
-            assert(outCol = outpamP->width);
+            assert(outCol == outpamP->width);
 
             pnm_writepamrow(outpamP, tupleRow);
         }
diff --git a/editor/pamfunc.c b/editor/pamfunc.c
index 454e6d63..b85cfe9b 100644
--- a/editor/pamfunc.c
+++ b/editor/pamfunc.c
@@ -10,7 +10,7 @@
   ENHANCEMENT IDEAS:
 
   1) speed up by doing integer arithmetic instead of floating point for
-  multiply/divide where possible.  Especially when multiplying by an 
+  multiply/divide where possible.  Especially when multiplying by an
   integer.
 
   2) speed up by not transforming the raster in the idempotent cases
@@ -20,6 +20,7 @@
 
 #include "pm_c_util.h"
 #include "mallocvar.h"
+#include "nstring.h"
 #include "shhopt.h"
 #include "pam.h"
 
@@ -78,7 +79,7 @@ parseHex(const char * const hexString) {
     return retval;
 }
 
-         
+
 
 static void
 parseCommandLine(int argc, const char ** const argv,
@@ -103,7 +104,7 @@ parseCommandLine(int argc, const char ** const argv,
     MALLOCARRAY(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENT3 */
-    OPTENT3(0,   "multiplier", OPT_FLOAT,  &cmdlineP->u.multiplier, 
+    OPTENT3(0,   "multiplier", OPT_FLOAT,  &cmdlineP->u.multiplier,
             &multiplierSpec, 0);
     OPTENT3(0,   "divisor",    OPT_FLOAT,  &cmdlineP->u.divisor,
             &divisorSpec,    0);
@@ -150,12 +151,12 @@ parseCommandLine(int argc, const char ** const argv,
     if (multiplierSpec) {
         cmdlineP->function = FN_MULTIPLY;
         if (cmdlineP->u.multiplier < 0)
-            pm_error("Multiplier must be nonnegative.  You specified %f", 
+            pm_error("Multiplier must be nonnegative.  You specified %f",
                      cmdlineP->u.multiplier);
     } else if (divisorSpec) {
         cmdlineP->function = FN_DIVIDE;
         if (cmdlineP->u.divisor < 0)
-            pm_error("Divisor must be nonnegative.  You specified %f", 
+            pm_error("Divisor must be nonnegative.  You specified %f",
                      cmdlineP->u.divisor);
     } else if (adderSpec) {
         cmdlineP->function = FN_ADD;
@@ -180,20 +181,20 @@ parseCommandLine(int argc, const char ** const argv,
         cmdlineP->function = FN_SHIFTLEFT;
     } else if (shiftrightSpec) {
         cmdlineP->function = FN_SHIFTRIGHT;
-    } else 
+    } else
         pm_error("You must specify one of -multiplier, -divisor, "
                  "-adder, -subtractor, -min, -max, "
                  "-and, -or, -xor, -not, -shiftleft, or -shiftright");
-        
+
     if (argc-1 > 1)
         pm_error("Too many arguments (%d).  File spec is the only argument.",
                  argc-1);
 
     if (argc-1 < 1)
         cmdlineP->inputFileName = "-";
-    else 
+    else
         cmdlineP->inputFileName = argv[1];
-    
+
     free(option_def);
 }
 
@@ -336,7 +337,7 @@ applyFunction(struct CmdlineInfo const cmdline,
            1/cmdline.u.divisor instead of divide by cmdline.u.divisor,
            so we compute that here.  Note that if the function isn't
            divide, both cmdline.u.divisor and oneOverDivisor are
-           meaningless.  
+           meaningless.
         */
     unsigned int col;
 
@@ -388,7 +389,7 @@ applyFunction(struct CmdlineInfo const cmdline,
             outputRow[col][plane] = MIN(outpam.maxval, outSample);
         }
     }
-}                
+}
 
 
 
@@ -422,6 +423,9 @@ main(int argc, const char *argv[]) {
     planTransform(cmdline, inpam.maxval, outpam.format,
                   &outpam.maxval, &mustChangeRaster);
 
+    if (outpam.maxval > 1 && strneq(outpam.tuple_type, "BLACKANDWHITE", 13))
+        strcpy(outpam.tuple_type, "");
+
     pnm_writepaminit(&outpam);
 
     outputRow = pnm_allocpamrow(&outpam);
@@ -440,7 +444,7 @@ main(int argc, const char *argv[]) {
     pnm_freepamrow(inputRow);
     pm_close(inpam.file);
     pm_close(outpam.file);
-    
+
     return 0;
 }
 
diff --git a/editor/pamhomography.c b/editor/pamhomography.c
new file mode 100644
index 00000000..02c0fd72
--- /dev/null
+++ b/editor/pamhomography.c
@@ -0,0 +1,799 @@
+/* ----------------------------------------------------------------------
+ *
+ * Map one quadrilateral to another
+ * by Scott Pakin <scott+pbm@pakin.org>
+ *
+ * ----------------------------------------------------------------------
+ *
+ * Copyright (C) 2020 Scott Pakin <scott+pbm@pakin.org>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "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 AUTHOR 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.
+ *
+ * ----------------------------------------------------------------------
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <math.h>
+#include "pm_c_util.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "pam.h"
+
+#define MIN4(A, B, C, D) MIN(MIN(A, B), MIN(C, D))
+#define MAX4(A, B, C, D) MAX(MAX(A, B), MAX(C, D))
+
+typedef struct {
+/*----------------------------------------------------------------------------
+  A point on the image plane.  It may or may not lie within the
+  bounds of the image itself.
+-----------------------------------------------------------------------------*/
+    int x;
+    int y;
+} Point;
+
+
+static Point
+pointXy(int const x,
+        int const y) {
+
+    Point retval;
+
+    retval.x = x;
+    retval.y = y;
+
+    return retval;
+}
+
+
+
+typedef struct {
+/*----------------------------------------------------------------------------
+  A quadrilateral on the image plane.
+-----------------------------------------------------------------------------*/
+    Point ul;
+    Point ur;
+    Point lr;
+    Point ll;
+} Quad;
+
+typedef struct {
+/*----------------------------------------------------------------------------
+  A specification of a quadrilateral on the image plane, either explicitly
+  or just as "the whole image".
+-----------------------------------------------------------------------------*/
+    bool wholeImage;
+    Quad explicit;  /* Meaningful only if 'wholeImage' is false */
+} QuadSpec;
+
+typedef struct {
+/*----------------------------------------------------------------------------
+   Specification of a mapping from one quadrilateral to another
+-----------------------------------------------------------------------------*/
+    QuadSpec from;
+    QuadSpec to;
+} QuadMap;
+
+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 */
+    QuadMap      qmap;
+        /* Source and target quadrilaterals as specified by -to and -from;
+           Note that the file identified by 'mapfile' also supplies such
+           information.
+        */
+    QuadSpec     view;
+        /* Bounding box for the target image */
+    const char * mapfile;        /* Null if not specified */
+    const char * fill;           /* Null if not specified */
+    unsigned int verbose;
+};
+
+
+
+static void
+parseCoords(const char *   const str,
+            int *          const coords,
+            unsigned int * const nP) {
+/*----------------------------------------------------------------------------
+  Parse a list of up to 16 integers.  Return as *nP how may there are.
+-----------------------------------------------------------------------------*/
+    const char * p;
+    char * pnext;
+    unsigned int i;
+
+    for (i = 0, p = str; i < 16; ++i, p = pnext) {
+        long int val;
+
+        /* Skip punctuation, except "+" and "-", and white space. */
+        while (*p != '\0' && *p != '+' && *p != '-' &&
+               (isspace(*p) || ispunct(*p)))
+            ++p;
+
+        /* Parse the next integer. */
+        errno = 0;  /* strtol() sets errno on error. */
+        val = strtol(p, &pnext, 10);
+        if (errno == ERANGE)
+            break;  /* Integer lies out of long int range */
+        if (errno != 0 || pnext == p)
+            break;  /* Too few integers */
+        coords[i] = (int)val;
+        if ((long int)coords[i] != val)
+            break;  /* Integer lies out of int range */
+    }
+    *nP = i;
+}
+
+
+
+static Quad
+quadFmViewString(const char * const str) {
+/*----------------------------------------------------------------------------
+  Parse a list of four integers in the order {ulx, uly, lrx, lry} into a
+  quadrilateral.
+
+  Abort the program if 'str' is not valid.
+-----------------------------------------------------------------------------*/
+    Quad retval;
+    int coords[16];
+    unsigned int nCoord;
+
+    parseCoords(str, coords, &nCoord);
+
+    if (nCoord != 4)
+        pm_error("failed to parse '%s' as a list of four integers", str);
+
+    retval.ul.x = retval.ll.x = coords[0];
+    retval.ul.y = retval.ur.y = coords[1];
+    retval.lr.x = retval.ur.x = coords[2];
+    retval.lr.y = retval.ll.y = coords[3];
+
+    return retval;
+}
+
+
+
+static Quad
+quadFmIntList(const int * const coords) {
+    Quad retval;
+
+    retval.ul.x = coords[0];
+    retval.ul.y = coords[1];
+    retval.ur.x = coords[2];
+    retval.ur.y = coords[3];
+    retval.lr.x = coords[4];
+    retval.lr.y = coords[5];
+    retval.ll.x = coords[6];
+    retval.ll.y = coords[7];
+
+    return retval;
+}
+
+
+
+static Quad
+quadFmString(const char * const str) {
+/*----------------------------------------------------------------------------
+  Parse a list of eight integers in the order {ulx, uly, urx, ury,
+  lrx, lry, llx, lly} into a quadrilateral.
+
+  Abort the program if 'str' is not a valid list of eight integers.
+-----------------------------------------------------------------------------*/
+    int coords[16];
+    unsigned int nCoord;
+
+    parseCoords(str, coords, &nCoord);
+
+    if (nCoord != 8)
+        pm_error("failed to parse '%s' as a list of eight integers", str);
+
+    return quadFmIntList(coords);
+}
+
+
+
+static void
+parseCommandLine(int                        argc,
+                 const char **        const 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.
+-----------------------------------------------------------------------------*/
+
+    optEntry *option_def;
+        /* Instructions to pm_optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    unsigned int mapfileSpec, fromSpec, toSpec, viewSpec, fillSpec;
+    const char * from;
+    const char * to;
+    const char * view;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "mapfile",         OPT_STRING, &cmdlineP->mapfile,
+            &mapfileSpec,             0);
+    OPTENT3(0, "from",            OPT_STRING, &from,
+            &fromSpec,                0);
+    OPTENT3(0, "to",              OPT_STRING, &to,
+            &toSpec,                  0);
+    OPTENT3(0, "view",            OPT_STRING, &view,
+            &viewSpec,                0);
+    OPTENT3(0, "fill",            OPT_STRING, &cmdlineP->fill,
+            &fillSpec,                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 */
+    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 local variables. */
+
+    if (!fillSpec)
+        cmdlineP->fill = NULL;
+
+    if (!mapfileSpec)
+        cmdlineP->mapfile = NULL;
+
+    if (fromSpec) {
+        cmdlineP->qmap.from.wholeImage = false;
+        cmdlineP->qmap.from.explicit = quadFmString(from);
+    } else
+        cmdlineP->qmap.from.wholeImage = true;
+
+    if (toSpec) {
+        cmdlineP->qmap.to.wholeImage = false;
+        cmdlineP->qmap.to.explicit = quadFmString(to);
+    } else
+        cmdlineP->qmap.to.wholeImage = true;
+
+    if (viewSpec) {
+        cmdlineP->view.wholeImage = false;
+        cmdlineP->view.explicit   = quadFmViewString(view);
+    } else
+        cmdlineP->view.wholeImage = true;
+
+    if (argc < 2)
+        cmdlineP->inputFilespec = "-";
+    else if (argc == 2)
+        cmdlineP->inputFilespec = argv[1];
+    else
+        pm_error("Too many non-option arguments: %u.  "
+                 "Only possible argument is input file name", argc - 1);
+
+    free((void *) option_def);
+}
+
+
+
+static void
+readMapFile(const char * const fname,
+            QuadMap *    const qmapP) {
+/*----------------------------------------------------------------------------
+  Read from a file either 16 numbers in the order {ulx1, uly1, urx1, ury1,
+  lrx1, lry1, llx1, lly1, ulx2, uly2, urx2, ury2, lrx2, lry2, llx2, lly2}
+  or 8 numbers in the order {ulx2, uly2, urx2, ury2, lrx2, lry2, llx2,
+  lly2}.
+
+  Abort the program if the file does not contain data in this format.
+-----------------------------------------------------------------------------*/
+    FILE * fp;
+    char * str;      /* Entire file contents */
+    int coords[16];  /* File as a list of up to 16 coordinates */
+    unsigned int nCoord;
+    long int nread;
+
+    /* Read the entire file. */
+    fp = pm_openr(fname);
+    str = pm_read_unknown_size(fp, &nread);
+    REALLOCARRAY_NOFAIL(str, nread + 1);
+    str[nread] = '\0';
+    pm_close(fp);
+
+    {
+        unsigned int i;
+        /* Replace newlines and tabs with spaces to prettify error
+           reporting.
+        */
+        for (i = 0; str[i]; ++i)
+            if (isspace(str[i]))
+                str[i] = ' ';
+    }
+    parseCoords(str, coords, &nCoord);
+
+    /* Read either {from, to} or just a {to} quadrilateral. */
+    switch (nCoord) {
+    case 16:
+        /* 16 integers: assign both the "from" and the "to" quadrilateral. */
+        qmapP->from.wholeImage = false;
+        qmapP->from.explicit   = quadFmIntList(&coords[0]);
+        qmapP->to.wholeImage   = false;
+        qmapP->to.explicit     = quadFmIntList(&coords[8]);
+        break;
+    case 8:
+        /* 8 integers: assign only the "to" quadrilateral. */
+        qmapP->from.wholeImage = true;
+        qmapP->to.explicit     = quadFmIntList(coords);
+        break;
+    default:
+        /* Not valid input */
+        pm_error("failed to parse contents of map file '%s' ('%s') "
+                 "as a list of either 8 or 16 integers",
+                 fname, str);
+        break;
+    }
+
+    free(str);
+}
+
+
+
+static void
+reportQuads(Quad const qfrom,
+            Quad const qto) {
+
+    pm_message("Copying from ((%d,%d),(%d,%d),(%d,%d),(%d,%d)) "
+               "to ((%d,%d),(%d,%d),(%d,%d),(%d,%d))",
+               qfrom.ul.x, qfrom.ul.y,
+               qfrom.ur.x, qfrom.ur.y,
+               qfrom.lr.x, qfrom.lr.y,
+               qfrom.ll.x, qfrom.ll.y,
+               qto.ul.x,   qto.ul.y,
+               qto.ur.x,   qto.ur.y,
+               qto.lr.x,   qto.lr.y,
+               qto.ll.x,   qto.ll.y
+        );
+}
+
+
+
+static void
+reportBbox(Quad const bbox) {
+
+    pm_message("The bounding box is ((%d,%d),(%d,%d),(%d,%d),(%d,%d))",
+               bbox.ul.x, bbox.ul.y,
+               bbox.ur.x, bbox.ur.y,
+               bbox.lr.x, bbox.lr.y,
+               bbox.ll.x, bbox.ll.y
+        );
+}
+
+
+
+static tuple
+parseFillColor(const struct pam * const pamP,
+               const char *       const fillColorSpec) {
+/*----------------------------------------------------------------------------
+  Parse the fill color into the correct format for the given PAM metadata.
+-----------------------------------------------------------------------------*/
+    tuple retval;
+
+    if (!fillColorSpec)
+        pnm_createBlackTuple(pamP, &retval);
+    else {
+        tuple const rgb = pnm_parsecolor(fillColorSpec, pamP->maxval);
+
+        retval = pnm_allocpamtuple(pamP);
+
+        switch (pamP->depth) {
+        case 1:
+            /* Grayscale */
+            retval[0] = (rgb[PAM_RED_PLANE]*299 +
+                         rgb[PAM_GRN_PLANE]*587 +
+                         rgb[PAM_BLU_PLANE]*114)/1000;
+            break;
+        case 2:
+            /* Grayscale + alpha */
+            retval[0] = (rgb[PAM_RED_PLANE]*299 +
+                         rgb[PAM_GRN_PLANE]*587 +
+                         rgb[PAM_BLU_PLANE]*114)/1000;
+            retval[PAM_GRAY_TRN_PLANE] = pamP->maxval;
+            break;
+        case 3:
+            /* RGB */
+            pnm_assigntuple(pamP, retval, rgb);
+            break;
+        case 4:
+            /* RGB + alpha */
+            pnm_assigntuple(pamP, retval, rgb);
+            retval[PAM_TRN_PLANE] = pamP->maxval;
+            break;
+        default:
+            pm_error("unexpected image depth %d", pamP->depth);
+            break;
+        }
+    }
+    return retval;
+}
+
+
+
+static tuple **
+initOutputImage(const struct pam * const pamP,
+                const char *       const fillColorSpec) {
+/*----------------------------------------------------------------------------
+  Allocate and initialize the output image data structure.
+-----------------------------------------------------------------------------*/
+    tuple fillColor;    /* Fill color to use for unused coordinates */
+    tuple ** outImg;    /* Output image */
+    unsigned int row;
+
+    outImg = pnm_allocpamarray(pamP);
+
+    fillColor = parseFillColor(pamP, fillColorSpec);
+    for (row = 0; row < pamP->height; ++row) {
+        unsigned int col;
+
+        for (col = 0; col < pamP->width; ++col) {
+            pnm_assigntuple(pamP, outImg[row][col], fillColor);
+        }
+    }
+
+    free((void *) fillColor);
+    return outImg;
+}
+
+
+
+static void
+computeSteps(Quad     const qfrom,
+             Quad     const qto,
+             double * const ustepP,
+             double * const vstepP) {
+/*----------------------------------------------------------------------------
+  Compute increments for u and v as these range from 0.0 to 1.0.
+-----------------------------------------------------------------------------*/
+    double fx0, fx1, fxd;
+    double tx0, tx1, txd;
+    double fy0, fy1, fyd;
+    double ty0, ty1, tyd;
+
+    /* Compute ustep as the inverse of the maximum possible x delta across
+       either the "from" or "to" quadrilateral.
+    */
+    fx0 = MIN4((double)qfrom.ur.x,
+               (double)qfrom.ul.x,
+               (double)qfrom.lr.x,
+               (double)qfrom.ll.x);
+    fx1 = MAX4((double)qfrom.ur.x,
+               (double)qfrom.ul.x,
+               (double)qfrom.lr.x,
+               (double)qfrom.ll.x);
+    fxd = fx1 - fx0;
+
+    tx0 = MIN4((double)qto.ur.x,
+               (double)qto.ul.x,
+               (double)qto.lr.x,
+               (double)qto.ll.x);
+    tx1 = MAX4((double)qto.ur.x,
+               (double)qto.ul.x,
+               (double)qto.lr.x,
+               (double)qto.ll.x);
+    txd = tx1 - tx0;
+
+    if (fxd == 0.0 && txd == 0.0)
+        *ustepP = 1.0;  /* Arbitrary nonzero step */
+    else
+        *ustepP = 0.5/MAX(fxd, txd);
+            /* Divide into 0.5 instead of 1.0 for additional smoothing. */
+
+    /* Compute vstep as the inverse of the maximum possible y delta across
+       either the "from" or "to" quadrilateral.
+    */
+    fy0 = MIN4((double)qfrom.ur.y,
+               (double)qfrom.ul.y,
+               (double)qfrom.lr.y,
+               (double)qfrom.ll.y);
+    fy1 = MAX4((double)qfrom.ur.y,
+               (double)qfrom.ul.y,
+               (double)qfrom.lr.y,
+               (double)qfrom.ll.y);
+    fyd = fy1 - fy0;
+    ty0 = MIN4((double)qto.ur.y,
+               (double)qto.ul.y,
+               (double)qto.lr.y,
+               (double)qto.ll.y);
+    ty1 = MAX4((double)qto.ur.y,
+               (double)qto.ul.y,
+               (double)qto.lr.y,
+               (double)qto.ll.y);
+    tyd = ty1 - ty0;
+
+    if (fyd == 0.0 && tyd == 0.0)
+        *vstepP = 1.0;  /* Arbitrary nonzero step */
+    else
+        *vstepP = 0.5/MAX(fyd, tyd);
+            /* Divide into 0.5 instead of 1.0 for additional smoothing. */
+}
+
+
+
+static Quad
+quadrilateralFmSpec(const struct pam * const pamP,
+                    QuadSpec           const qdata) {
+/*----------------------------------------------------------------------------
+  The quadrilateral specified by 'qdata'.
+-----------------------------------------------------------------------------*/
+    Quad retval;
+
+    if (qdata.wholeImage) {
+        /* Set the quadrilateral to the image's bounding box. */
+        retval.ul = pointXy(0,               0               );
+        retval.ur = pointXy(pamP->width - 1, 0               );
+        retval.ll = pointXy(0,               pamP->height - 1);
+        retval.lr = pointXy(pamP->width - 1, pamP->height - 1);
+    } else {
+        /* Use the quadrilateral as specified. */
+        retval = qdata.explicit;
+    }
+
+    return retval;
+}
+
+
+
+static Point
+coordsAtPercent(Quad   const quad,
+                double const u,
+                double const v) {
+/*----------------------------------------------------------------------------
+  Return the (x, y) coordinates that lie at (u%, v%) from the upper left to
+  the lower right of a given quadrilateral.
+-----------------------------------------------------------------------------*/
+    return pointXy(
+        (int) nearbyint((1.0 - u) * (1.0 - v) * quad.ul.x +   /* x */
+                        u * (1.0 - v) * quad.ur.x +
+                        u * v * quad.lr.x +
+                        (1.0 - u) * v * quad.ll.x),
+        (int) nearbyint((1.0 - u) * (1.0 - v) * quad.ul.y +   /* y */
+                        u * (1.0 - v) * quad.ur.y +
+                        u * v * quad.lr.y +
+                        (1.0 - u) * v * quad.ll.y)
+        );
+}
+
+
+
+static Quad
+boundingBoxOfQuadrilateral(Quad const q) {
+/*----------------------------------------------------------------------------
+  The bounding box of quadrilateral 'q'.
+-----------------------------------------------------------------------------*/
+    Quad retval;
+
+    int const leftLimit = MIN4(q.ul.x, q.ur.x, q.lr.x, q.ll.x);
+    int const rghtLimit = MAX4(q.ul.x, q.ur.x, q.lr.x, q.ll.x);
+    int const topLimit  = MIN4(q.ul.y, q.ur.y, q.lr.y, q.ll.y);
+    int const botLimit  = MAX4(q.ul.y, q.ur.y, q.lr.y, q.ll.y);
+
+    retval.ul = pointXy(leftLimit, topLimit);
+    retval.ur = pointXy(rghtLimit, topLimit);
+    retval.ll = pointXy(leftLimit, botLimit);
+    retval.lr = pointXy(rghtLimit, botLimit);
+
+    return retval;
+}
+
+
+
+static void
+mapQuadrilaterals(const struct pam * const inPamP,
+                  const struct pam * const outPamP,
+                  Quad               const qfrom,
+                  Quad               const qto,
+                  tuple **           const inImg,
+                  tuple **           const outImg,
+                  int                const xofs,
+                  int                const yofs) {
+/*----------------------------------------------------------------------------
+  Map the quadrilateral in the source image to the quadrilateral in the
+  target image.  This is the function that implemens pamhomography's
+  primary functionality.
+-----------------------------------------------------------------------------*/
+    sample ** channel;
+        /* Aggregated values for a single channel */
+    unsigned long ** tally;
+        /* Number of values at each coordinate in the above */
+    double ustep, vstep;
+        /* Steps to use when iterating from 0.0 to 1.0 */
+    unsigned int plane;
+
+    MALLOCARRAY2_NOFAIL(channel, outPamP->height, outPamP->width);
+    MALLOCARRAY2_NOFAIL(tally,   outPamP->height, outPamP->width);
+
+    computeSteps(qfrom, qto, &ustep, &vstep);
+
+    for (plane = 0; plane < outPamP->depth; ++plane) {
+        /* Reset the channel colors and tally for each plane, */
+        unsigned int row;
+        double v;
+        for (row = 0; row < outPamP->height; ++row) {
+            unsigned int col;
+            for (col = 0; col < outPamP->width; ++col) {
+                channel[row][col] = 0;
+                tally  [row][col] = 0;
+            }
+        }
+        /* Iterate from 0% to 100% in the y dimension. */
+        for (v = 0.0; v <= 1.0; v += vstep) {
+            /* Iterate from 0% to 100% in the x dimension. */
+            double u;
+            for (u = 0.0; u <= 1.0; u += ustep) {
+                Point from;  /* "From" coordinate */
+                Point to;    /* "To" coordinate */
+
+                /* Map (u%, v%) of one quadrilateral to (u%, v%) of the other
+                   quadrilateral.
+                */
+                from = coordsAtPercent(qfrom, u, v);
+                to   = coordsAtPercent(qto,   u, v);
+
+                /* Copy the source image's 'from' pixel as the destination
+                   image's 'to' pixel in the current plane.
+                */
+                to.x += xofs;
+                to.y += yofs;
+                if (from.x >= 0 && from.y >= 0 &&
+                    from.x < inPamP->width && from.y < inPamP->height &&
+                    to.x >= 0 && to.y >= 0 &&
+                    to.x < outPamP->width && to.y < outPamP->height) {
+
+                    channel[to.y][to.x] += inImg[from.y][from.x][plane];
+                    ++tally[to.y][to.x];
+                }
+            }
+        }
+
+        /* Assign the current plane in the output image the average color
+           at each point. */
+        for (row = 0; row < outPamP->height; ++row) {
+            unsigned int col;
+            for (col = 0; col < outPamP->width; ++col) {
+                if (tally[row][col] != 0)
+                    outImg[row][col][plane] =
+                        (channel[row][col] + tally[row][col]/2) /
+                        tally[row][col];
+            }
+        }
+    }
+
+    pm_freearray2((void ** const)tally);
+    pm_freearray2((void ** const)channel);
+}
+
+
+
+static void
+processFile(FILE *       const ifP,
+            QuadMap      const qmap,
+            QuadSpec     const view,
+            const char * const fillColorSpec,
+            bool         const verbose) {
+/*----------------------------------------------------------------------------
+  Read the input image, create the output image, and map a quadrilateral in
+  the former to a quadrilateral in the latter.
+-----------------------------------------------------------------------------*/
+    struct pam inPam;    /* PAM metadata for the input file */
+    struct pam outPam;   /* PAM metadata for the output file */
+    tuple ** inImg;      /* Input image */
+    tuple ** outImg;     /* Output image */
+    Quad qfrom, qto;     /* Source and target quadrilaterals */
+    Quad bbox;           /* Bounding box around the transformed input image */
+
+    inImg = pnm_readpam(ifP, &inPam, PAM_STRUCT_SIZE(tuple_type));
+
+    /* Extract quadrilaterals and populate them with the image bounds
+       if necessary. */
+    qfrom = quadrilateralFmSpec(&inPam, qmap.from);
+    qto   = quadrilateralFmSpec(&inPam, qmap.to);
+
+    if (verbose)
+        reportQuads(qfrom, qto);
+
+    /* Allocate storage for the target image. */
+    if (view.wholeImage)
+        bbox = boundingBoxOfQuadrilateral(qto);
+    else
+        bbox = view.explicit;
+
+    if (verbose)
+        reportBbox(bbox);
+
+    outPam = inPam;  /* initial value */
+    outPam.file   = stdout;
+    outPam.width  = bbox.lr.x - bbox.ul.x + 1;
+    outPam.height = bbox.lr.y - bbox.ul.y + 1;
+    outImg        = initOutputImage(&outPam, fillColorSpec);
+
+    mapQuadrilaterals(&inPam, &outPam,
+                      qfrom, qto,
+                      inImg, outImg,
+                      -bbox.ul.x, -bbox.ul.y);
+
+    pnm_writepam(&outPam, outImg);
+
+    pnm_freepamarray(outImg, &outPam);
+    pnm_freepamarray(inImg, &inPam);
+}
+
+
+
+int
+main(int argc, const char *argv[]) {
+
+    struct CmdlineInfo cmdline;      /* Parsed command line */
+    FILE * ifP;
+    QuadMap qmap;
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    if (cmdline.mapfile) {
+        /* Use the from and/or to values from the map file where the user
+           didn't explicitly state them
+        */
+        QuadMap mapFileValue;
+
+        readMapFile(cmdline.mapfile, &mapFileValue);
+
+        if (cmdline.qmap.from.wholeImage)
+            qmap.from = mapFileValue.from;
+        else
+            qmap.from = cmdline.qmap.from;
+
+        if (cmdline.qmap.to.wholeImage)
+            qmap.to = mapFileValue.to;
+        else
+            qmap.to = cmdline.qmap.to;
+    } else
+        qmap = cmdline.qmap;
+
+    ifP = pm_openr(cmdline.inputFilespec);
+
+    processFile(ifP, qmap, cmdline.view, cmdline.fill, !!cmdline.verbose);
+
+    pm_close(ifP);
+
+    return 0;
+}
+
+
diff --git a/editor/pamlevels.c b/editor/pamlevels.c
index fbbb2c0b..de2afc45 100644
--- a/editor/pamlevels.c
+++ b/editor/pamlevels.c
@@ -5,7 +5,6 @@
 #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"
@@ -482,7 +481,7 @@ pamlevels(CmdlineInfo const cmdline) {
 static void
 freeCmdLineInfo(CmdlineInfo cmdline) {
 /*----------------------------------------------------------------------------
-  Free any memory that has been dynamically allcoated in <cmdline>.
+  Free any memory that has been dynamically allocated in <cmdline>.
 -----------------------------------------------------------------------------*/
     TransSet * const xxP = &cmdline.xlats;
 
diff --git a/editor/pammixmulti.c b/editor/pammixmulti.c
index d303c3de..2b45d807 100644
--- a/editor/pammixmulti.c
+++ b/editor/pammixmulti.c
@@ -13,6 +13,7 @@
 #include "shhopt.h"
 #include "mallocvar.h"
 #include "nstring.h"
+#include "rand.h"
 
 typedef enum {
     BLEND_AVERAGE,   /* Take the average color of all pixels */
@@ -36,6 +37,8 @@ struct ProgramState {
         /* Standard deviation when selecting images via a mask */
     unsigned long ** imageWeights;
         /* Per-image weights as a function of grayscale level */
+    struct pm_randSt randSt;
+        /* Random number generator parameters and internal state */
 };
 
 
@@ -134,9 +137,9 @@ parseCommandLine(int argc, const char ** argv,
 }
 
 static void
-openInputFiles(unsigned int          const inFileCt,
-               const char **         const inFileName,
-               struct ProgramState * const stateP) {
+initInput(unsigned int          const inFileCt,
+          const char **         const inFileName,
+          struct ProgramState * const stateP) {
 /*----------------------------------------------------------------------------
   Open all of the input files.
 
@@ -180,6 +183,25 @@ openInputFiles(unsigned int          const inFileCt,
 }
 
 
+
+static void
+termInput(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->inTupleRows);
+    free(stateP->inPam);
+}
+
+
+
 static void
 initMask(const char *          const maskFileName,
          struct ProgramState * const stateP) {
@@ -233,6 +255,15 @@ initOutput(FILE *                const ofP,
 }
 
 
+static void
+termOutput(struct ProgramState * const stateP) {
+
+    free(stateP->outTupleRow);
+
+    pm_close(stateP->outPam.file);
+}
+
+
 
 static void
 blendTuplesRandom(struct ProgramState * const stateP,
@@ -243,8 +274,8 @@ blendTuplesRandom(struct ProgramState * const stateP,
   from a random input image.
 -----------------------------------------------------------------------------*/
     unsigned int const depth = stateP->inPam[0].depth;
-    unsigned int const img = (unsigned int) (rand() % stateP->inFileCt);
-
+    unsigned int const img = (unsigned int) (pm_rand(&stateP->randSt) %
+                                             stateP->inFileCt);
     unsigned int samp;
 
     for (samp = 0; samp < depth; ++samp)
@@ -276,23 +307,26 @@ blendTuplesAverage(struct ProgramState * const stateP,
 
 
 
+#if 0
 static void
-randomNormal2(double * const r1P,
-              double * const r2P) {
+randomNormal2(double *           const r1P,
+              double *           const r2P,
+              struct pm_randSt * const randStP) {
 /*----------------------------------------------------------------------------
   Return two normally distributed random numbers.
 -----------------------------------------------------------------------------*/
     double u1, u2;
 
     do {
-        u1 = (double)rand() / RAND_MAX;
-        u2 = (double)rand() / RAND_MAX;
+        u1 = drand48(randStP);
+        u2 = drand48(randStP);
     }
     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);
 }
+#endif
 
 
 
@@ -332,7 +366,8 @@ precomputeImageWeights(struct ProgramState * const stateP,
             double r[2];
             unsigned int k;
 
-            randomNormal2(&r[0], &r[1]);
+            pm_gaussrand2(&stateP->randSt, &r[0], &r[1]);
+
             for (k = 0; k < 2; ++k) {
                 int const img =
                     r[k] * sigma + pctGray * stateP->inFileCt * 0.999999;
@@ -453,26 +488,6 @@ blendImages(BlendType             const blend,
 
 
 
-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[]) {
 
@@ -483,13 +498,14 @@ main(int argc, const char * argv[]) {
 
     parseCommandLine(argc, argv, &cmdline);
 
-    srand(cmdline.randomseedSpec ? cmdline.randomseed : pm_randseed());
-
-    openInputFiles(cmdline.inFileNameCt, cmdline.inFileName, &state);
+    initInput(cmdline.inFileNameCt, cmdline.inFileName, &state);
 
     if (cmdline.blend == BLEND_MASK)
         initMask(cmdline.maskfile, &state);
 
+    pm_randinit(&state.randSt);
+    pm_srand2(&state.randSt, cmdline.randomseedSpec, cmdline.randomseed);
+
     initOutput(stdout, &state);
 
     if (cmdline.blend == BLEND_MASK)
@@ -497,10 +513,14 @@ main(int argc, const char * argv[]) {
 
     blendImages(cmdline.blend, &state);
 
+    termOutput(&state);
+
+    pm_randterm(&state.randSt);
+
     if (cmdline.blend == BLEND_MASK)
         termMask(&state);
 
-    termState(&state);
+    termInput(&state);
 
     freeCmdline(&cmdline);
 
diff --git a/editor/pamperspective.c b/editor/pamperspective.c
index a206b57f..a8b1cf8f 100644
--- a/editor/pamperspective.c
+++ b/editor/pamperspective.c
@@ -804,7 +804,7 @@ static void determine_world_parallelogram (world_data *const world,
   yw_lr = world->yi_lr * zw_lr;
 
   /* Now we introduce the margin. There are several ways the margin can be
-     defined. margin_spec keeps track of wether one of them has yet been
+     defined. margin_spec keeps track of whether one of them has yet been
      used. As long as margin_spec==FALSE, the variables top_margin to
      bottom_margin are not initialized! */
 
diff --git a/editor/pamrecolor.c b/editor/pamrecolor.c
index 8c5bce12..86c1965c 100644
--- a/editor/pamrecolor.c
+++ b/editor/pamrecolor.c
@@ -1,3 +1,4 @@
+
 /* ----------------------------------------------------------------------
  *
  * Replace every pixel in an image with one of equal luminance
@@ -32,6 +33,7 @@
 
 #include "mallocvar.h"
 #include "nstring.h"
+#include "rand.h"
 #include "shhopt.h"
 #include "pam.h"
 
@@ -42,7 +44,7 @@
 #define CLAMPxy(N, A, B) MAX(MIN((float)(N), (float)(B)), (float)(A))
 
 
-struct rgbfrac {
+struct Rgbfrac {
     /* This structure represents red, green, and blue, each expressed
        as a fraction from 0.0 to 1.0.
     */
@@ -51,19 +53,19 @@ struct rgbfrac {
     float bfrac;
 };
 
-struct cmdlineInfo {
+struct CmdlineInfo {
     /* This structure represents all of the information the user
        supplied in the command line but in a form that's easy for the
        program to use.
     */
     const char *    inputFileName;  /* '-' if stdin */
     const char *    colorfile;      /* NULL if unspecified */
-    struct rgbfrac  color2gray;
+    struct Rgbfrac  color2gray;
         /* colorspace/rmult/gmult/bmult options.  Negative numbers if
            unspecified.
         */
     unsigned int    targetcolorSpec;
-    struct rgbfrac  targetcolor;
+    struct Rgbfrac  targetcolor;
     unsigned int    randomseed;
     unsigned int    randomseedSpec;
 };
@@ -71,7 +73,7 @@ struct cmdlineInfo {
 
 
 static float
-rgb2gray(struct rgbfrac * const color2grayP,
+rgb2gray(struct Rgbfrac * const color2grayP,
          float            const red,
          float            const grn,
          float            const blu) {
@@ -122,7 +124,7 @@ getColorRow(struct pam  * const pamP,
 
 static void
 convertRowToGray(struct pam     * const pamP,
-                 struct rgbfrac * const color2gray,
+                 struct Rgbfrac * const color2gray,
                  tuplen         * const tupleRow,
                  samplen        * const grayRow) {
 /*----------------------------------------------------------------------
@@ -160,7 +162,7 @@ convertRowToGray(struct pam     * const pamP,
 static void
 explicitlyColorRow(struct pam *   const pamP,
                    tuplen *       const rowData,
-                   struct rgbfrac const tint) {
+                   struct Rgbfrac const tint) {
 
     unsigned int col;
 
@@ -175,17 +177,25 @@ explicitlyColorRow(struct pam *   const pamP,
 
 static void
 randomlyColorRow(struct pam *   const pamP,
-                 tuplen *       const rowData) {
+                 tuplen *       const rowData,
+                 bool           const randomseedSpec,
+                 unsigned int   const randomseed) {
 /*----------------------------------------------------------------------
   Assign each tuple in a row a random color.
 ------------------------------------------------------------------------*/
     unsigned int col;
+    struct pm_randSt randSt;
+
+    pm_randinit(&randSt);
+    pm_srand2(&randSt, randomseedSpec, randomseed);
 
     for (col = 0; col < pamP->width; ++col) {
-        rowData[col][PAM_RED_PLANE] = rand() / (float)RAND_MAX;
-        rowData[col][PAM_GRN_PLANE] = rand() / (float)RAND_MAX;
-        rowData[col][PAM_BLU_PLANE] = rand() / (float)RAND_MAX;
+        rowData[col][PAM_RED_PLANE] = pm_drand(&randSt);
+        rowData[col][PAM_GRN_PLANE] = pm_drand(&randSt);
+        rowData[col][PAM_BLU_PLANE] = pm_drand(&randSt);
     }
+
+    pm_randterm(&randSt);
 }
 
 
@@ -193,7 +203,7 @@ randomlyColorRow(struct pam *   const pamP,
 static void
 recolorRow(struct pam     * const inPamP,
            tuplen         * const inRow,
-           struct rgbfrac * const color2grayP,
+           struct Rgbfrac * const color2grayP,
            tuplen         * const colorRow,
            struct pam     * const outPamP,
            tuplen         * const outRow) {
@@ -293,10 +303,10 @@ recolorRow(struct pam     * const inPamP,
 
 
 
-static struct rgbfrac
+static struct Rgbfrac
 color2GrayFromCsName(const char * const csName) {
 
-    struct rgbfrac retval;
+    struct Rgbfrac retval;
 
     /* Thanks to
        http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
@@ -352,7 +362,7 @@ color2GrayFromCsName(const char * const csName) {
 
 static void
 parseCommandLine(int argc, const char ** const argv,
-                 struct cmdlineInfo * const cmdlineP ) {
+                 struct CmdlineInfo * const cmdlineP ) {
 
     optEntry     * option_def;
         /* Instructions to OptParseOptions3 on how to parse our options */
@@ -445,7 +455,7 @@ parseCommandLine(int argc, const char ** const argv,
 
 int
 main(int argc, const char *argv[]) {
-    struct cmdlineInfo cmdline;          /* Command-line parameters */
+    struct CmdlineInfo cmdline;          /* Command-line parameters */
     struct pam         inPam;
     struct pam         outPam;
     struct pam         colorPam;
@@ -462,8 +472,6 @@ main(int argc, const char *argv[]) {
 
     parseCommandLine(argc, argv, &cmdline);
 
-    srand(cmdline.randomseedSpec ? cmdline.randomseed : pm_randseed());
-
     ifP = pm_openr(cmdline.inputFileName);
     inPam.comment_p = &comments;
     pnm_readpaminit(ifP, &inPam, PAM_STRUCT_SIZE(comment_p));
@@ -474,7 +482,6 @@ main(int argc, const char *argv[]) {
     outPam.depth = 4 - (inPam.depth % 2);
     outPam.allocation_depth = outPam.depth;
     strcpy(outPam.tuple_type, PAM_PPM_TUPLETYPE);
-    pnm_writepaminit(&outPam);
 
     if (cmdline.colorfile) {
         colorfP = pm_openr(cmdline.colorfile);
@@ -492,6 +499,8 @@ main(int argc, const char *argv[]) {
 
     colorRowBuffer = pnm_allocpamrown(&outPam);
 
+    pnm_writepaminit(&outPam);
+
     for (row = 0; row < inPam.height; ++row) {
         tuplen * colorRow;
 
@@ -505,7 +514,8 @@ main(int argc, const char *argv[]) {
             if (cmdline.targetcolorSpec)
                 explicitlyColorRow(&colorPam, colorRow, cmdline.targetcolor);
             else
-                randomlyColorRow(&colorPam, colorRow);
+                randomlyColorRow(&colorPam, colorRow,
+                                 cmdline.randomseedSpec, cmdline.randomseed);
         }
         recolorRow(&inPam, inRow,
                    &cmdline.color2gray, colorRow,
diff --git a/editor/pamrestack.c b/editor/pamrestack.c
new file mode 100644
index 00000000..46321774
--- /dev/null
+++ b/editor/pamrestack.c
@@ -0,0 +1,472 @@
+/*=============================================================================
+                               pamrestack
+===============================================================================
+  Part of the Netpbm package.
+
+  Rearrange pixels of a Netpbm image into different size rows.
+
+  E.g. if an image is 100 pixels wide and 50 pixels high, you can rearrange it
+  to 125 wide and 40 high.  In that case, 25 pixels from the 2nd row of the
+  input would be moved to the end of the 1st row of input, 50 pixels from the
+  3rd row would be moved to the 2nd row, etc.
+
+  If new width is less than the input image width, move the excess pixels
+  to the start (=left edge) of the next row.
+
+  If new width is larger, complete row by bringing pixels from the start
+  of the next row.
+
+  By Akira F. Urushibata
+
+  Contributed to the public domain by its author.
+=============================================================================*/
+
+#include <assert.h>
+#include <math.h>
+#include <limits.h>
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "pam.h"
+#include "shhopt.h"
+
+static unsigned int const maxSize = INT_MAX - 10;
+
+static void
+validateWidth(double       const width,
+              const char * const message) {
+/*----------------------------------------------------------------------------
+  Check width.  Ensure it is a value accepted by other Netpbm programs.
+-----------------------------------------------------------------------------*/
+    assert(maxSize < INT_MAX);
+
+    if (width > maxSize)
+        pm_error("%s %.0f is too large.", message, width);
+}
+
+
+
+static void
+validateHeight(double const height) {
+/*----------------------------------------------------------------------------
+  Fail if image height of 'height' is too great for the computations in
+  this program to work.
+-----------------------------------------------------------------------------*/
+    if (height > maxSize)
+        pm_error("Input image is large and -width value is small."
+                 "Calulated height %.0f is too large.", height);
+}
+
+
+
+enum TrimMode {TRIMMODE_NOP, TRIMMODE_FILL, TRIMMODE_CROP, TRIMMODE_ABORT};
+
+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  width;
+    unsigned int  widthSpec;
+    enum TrimMode trim;
+    unsigned int  verbose;
+};
+
+static void
+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.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options. */
+    optStruct3 opt;
+
+    const char * trimOpt;
+    unsigned int trimSpec;
+
+    unsigned int option_def_index;
+
+    MALLOCARRAY(option_def, 100);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = false;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = true;  /* We have no parms that are negative numbers */
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "width",         OPT_UINT,    &cmdlineP->width,
+            &cmdlineP->widthSpec,     0);
+    OPTENT3(0, "trim",          OPT_STRING, &trimOpt,
+            &trimSpec,                0);
+    OPTENT3(0, "verbose",       OPT_FLAG,   NULL,
+            &cmdlineP->verbose,       0);
+
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
+    /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    free(option_def);
+
+    if (cmdlineP->widthSpec) {
+        if (cmdlineP->width == 0)
+            pm_error("Width value must be positive.  You specified 0");
+        else
+            validateWidth((double) cmdlineP->width,
+                          "Specified -width value");
+    }
+
+    if (trimSpec) {
+        if (streq(trimOpt, "fill")) {
+            cmdlineP->trim = TRIMMODE_FILL;
+        } else if (streq(trimOpt, "crop")) {
+            cmdlineP->trim = TRIMMODE_CROP;
+        } else if (streq(trimOpt, "abort")) {
+            cmdlineP->trim = TRIMMODE_ABORT;
+        } else
+            /* NOP is not specified from the command line */
+            pm_error("Invalid value for -trim: '%s'", trimOpt);
+    } else
+        cmdlineP->trim = TRIMMODE_FILL;  /* default */
+
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else {
+        cmdlineP->inputFileName = argv[1];
+
+        if (argc-1 > 1)
+            pm_error("Too many arguments (%u). "
+                     "The only possible argument is the input file name.",
+                     argc-1);
+    }
+}
+
+
+
+static void
+adjustTrimMode(double          const inPixels,
+               double          const outWidth,
+               double          const outHeight,
+               bool            const verbose,
+               enum TrimMode   const originalMode,
+               enum TrimMode * const adjustedModeP) {
+/*----------------------------------------------------------------------------
+   Adjust trim mode, taking into account the number of pixels in the
+   input image and the width and height of the output image.
+
+   Check whether conditions are met for abort.
+   Set mode to NOP if all output rows will be full.
+-----------------------------------------------------------------------------*/
+    double const outPixels = outWidth * outHeight;
+
+    enum TrimMode adjustedMode;
+
+    if (inPixels == outPixels)
+        adjustedMode = TRIMMODE_NOP;
+    else {
+        if (originalMode == TRIMMODE_ABORT)
+            pm_error("Abort mode specified and input image has %.0f pixels "
+                     "which is %s specified width value %.0f",
+                     inPixels,
+                     inPixels < outWidth ? "less than" : "not a multiple of",
+                     outWidth);
+        else
+            adjustedMode = originalMode;
+    }
+
+    validateHeight(outHeight + (adjustedMode == TRIMMODE_FILL) ? 1 : 0);
+
+    switch (adjustedMode) {
+    case TRIMMODE_NOP:
+        if (verbose)
+            pm_message("Input image and output image have the same "
+                       "number of pixels.");
+        break;
+    case TRIMMODE_FILL:
+        if (verbose)
+            pm_message("Output image will have %.0f more pixels "
+                       "than input image.  Incomplete final row "
+                       "will be padded.", inPixels - outPixels);
+        break;
+    case TRIMMODE_CROP:
+        if (outHeight == 0)
+            pm_error("No row left after cropping incomplete row. "
+                     "Aborting.");
+        else if (verbose)
+            pm_message("Incomplete final row will be cropped.  %.0f "
+                       "pixels lost.", inPixels - outPixels);
+        break;
+    case TRIMMODE_ABORT:
+        pm_error("internal error");  /* Suppress compiler warning */
+        break;
+    }
+
+    *adjustedModeP = adjustedMode;
+}
+
+
+
+/*----------------------------------------------------------------------------
+  Width conversion using pointer arrays
+
+  This program reads input rows and converts to output rows of desired
+  width using a device which employs pointer arrays on both sides.
+  Conceptually similar, yet more simple, devices are used in pamcut,
+  pnmpad, pamflip and pnmcat.
+
+  inputPointers[] is an expanded version of incols[] seen in many pam
+  programs.  It reads multiple rows: as many rows as necessary to
+  complete at least one output row.
+
+  The read positions within inputPointers[] are fixed.  For example, if
+  the input row width is 100 and inputPointers has 400 elements, the read
+  positions will be: 0-99, 100-199, 200-299, 300-399.
+
+  The outPointers[] array is set up to allow selecting elements for
+  write from inputPointers[].  outPointers[] is at least as large as
+  inPointers[].  The write position migrates as necessary in a cycle.
+  If input width and output width are coprime and output has a
+  sufficient number of rows, all positions within outPointers[]
+  will be utilized.
+
+  Once set up, the conversion device is not altered until the input image
+  is completely read.
+
+  The following are special cases in which inPointers[] and outPointers[]
+  are set to the same size:
+
+  (1) Input width and output width are equal.
+  (2) Output width is an integer multiple of input width.
+  (3) Input width is an integer multiple of output width.
+
+  In cases (1) (2), the output position is fixed.
+  In case (3) the output position is mobile, but all of them will start
+  at integer multiples of output width.
+
+  Note that width, height and width * height variables are of type
+  "double" as a safeguard against overflows.
+-----------------------------------------------------------------------------*/
+
+
+
+static void
+setOutputDimensions(struct CmdlineInfo * const cmdlineP,
+                    double               const inPixelCt,
+                    int *                const outWidthP,
+                    int *                const outHeightP,
+                    enum TrimMode *      const trimModeP) {
+/*-----------------------------------------------------------------------------
+  Calculate the width and height of output from the number of pixels in
+  the input and command line arguments, most notably desired width.
+-----------------------------------------------------------------------------*/
+    double outWidth, outHeight;
+    enum TrimMode adjustedMode;
+
+    if (!cmdlineP->widthSpec) {
+        outWidth = inPixelCt;
+        outHeight = 1;
+        validateWidth(outWidth,
+                      "Input image is large and -width not specified. "
+                      "Output width");
+        adjustedMode = cmdlineP->trim;
+    } else {
+        double preAdjustedOutHeight;
+
+        outWidth  = cmdlineP->width;
+        preAdjustedOutHeight = floor(inPixelCt / outWidth);
+
+        adjustTrimMode(inPixelCt, outWidth, preAdjustedOutHeight,
+                       cmdlineP->verbose,
+                       cmdlineP->trim, &adjustedMode);
+
+        outHeight = adjustedMode == TRIMMODE_FILL ?
+            preAdjustedOutHeight + 1 : preAdjustedOutHeight;
+    }
+
+    *outWidthP  = (unsigned int)outWidth;
+    *outHeightP = (unsigned int)outHeight;
+    *trimModeP  = adjustedMode;
+}
+
+
+
+static void
+calculateInOutSize(unsigned int   const inWidth,
+                   unsigned int   const outWidth,
+                   unsigned int * const inputPointersWidthP,
+                   unsigned int * const outputPointersWidthP) {
+/*----------------------------------------------------------------------------
+  Calculate array size of inPointers[] and outPointers[] from
+  input width and output width.
+-----------------------------------------------------------------------------*/
+    double inputPointersWidth;
+    double outputPointersWidth;
+
+    if (outWidth > inWidth) {
+        if (outWidth % inWidth == 0)
+            inputPointersWidth = outputPointersWidth = outWidth;
+        else {
+            inputPointersWidth =
+              (outWidth / inWidth + 1) * inWidth * 2;
+            outputPointersWidth = inputPointersWidth + outWidth - 1;
+        }
+    }
+    else if (outWidth == inWidth)
+            inputPointersWidth = outputPointersWidth = outWidth;
+    else { /* outWidth < inWidth) */
+        if (inWidth % outWidth == 0)
+            inputPointersWidth = outputPointersWidth = inWidth;
+        else {
+            inputPointersWidth = inWidth * 2;
+            outputPointersWidth = inputPointersWidth + outWidth - 1;
+        }
+    }
+
+    if(inputPointersWidth > SIZE_MAX || outputPointersWidth > SIZE_MAX)
+        pm_error("Failed to set up conversion array.  Either input width, "
+                 "output width or their difference is too large.");
+
+    *inputPointersWidthP  = (unsigned int) inputPointersWidth;
+    *outputPointersWidthP = (unsigned int) outputPointersWidth;
+}
+
+
+
+static void
+restack(struct pam    * const inpamP,
+        struct pam    * const outpamP,
+        tuple         * const inputPointers,
+        tuple         * const outputPointers,
+        unsigned int    const inputPointersWidth,
+        unsigned int    const outputPointersWidth,
+        enum TrimMode   const trimMode) {
+/*----------------------------------------------------------------------------
+  Convert image, using inputPointers[] and outputPointers[]
+-----------------------------------------------------------------------------*/
+    unsigned int inoffset;
+    unsigned int outoffset;
+    unsigned int inPixelCt; /* Count of pixels read since last write */
+    unsigned int row;
+
+    /* Read all input and write all rows with the exception of the final
+       partial row */
+
+    for (row = 0, inoffset = 0, outoffset = 0, inPixelCt = 0;
+         row < inpamP->height; ++row) {
+
+        pnm_readpamrow(inpamP, &inputPointers[inoffset]);
+        inPixelCt += inpamP->width;
+
+        for ( ; inPixelCt >= outpamP->width; inPixelCt -= outpamP->width) {
+            pnm_writepamrow(outpamP, &outputPointers[outoffset]);
+            outoffset = (outoffset + outpamP->width ) % inputPointersWidth;
+        }
+        inoffset = (inoffset + inpamP->width) % inputPointersWidth;
+    }
+
+    /* Fill remainder of last row with black pixels and output */
+
+    if (inPixelCt > 0 && trimMode == TRIMMODE_FILL) {
+        tuple blackTuple;
+        unsigned int col;
+
+        pnm_createBlackTuple(outpamP, &blackTuple);
+
+        for (col = inPixelCt; col < outpamP->width; ++col) {
+            unsigned int const outoffset2 =
+                (outoffset + col) % outputPointersWidth;
+            outputPointers[outoffset2] = blackTuple;
+        }
+
+        /* output final row */
+        pnm_writepamrow(outpamP, &outputPointers[outoffset]);
+    }
+}
+
+
+
+static void
+restackSingleImage(FILE *               const ifP,
+                   struct CmdlineInfo * const cmdlineP) {
+
+    struct pam inpam;   /* Input PAM image */
+    struct pam mpam;    /* Adjusted PAM structure to read multiple rows */
+    struct pam outpam;  /* Output PAM image */
+
+    double        inPixelCt;
+    enum TrimMode trimMode;
+    tuple *       inputPointers;
+    tuple *       outputPointers;
+    unsigned int  inputPointersWidth;
+    unsigned int  outputPointersWidth;
+
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    inPixelCt = inpam.width * inpam.height;
+
+    outpam = inpam;
+
+    setOutputDimensions(cmdlineP, inPixelCt, &outpam.width, &outpam.height,
+                        &trimMode);
+
+    outpam.file = stdout;
+
+    pnm_writepaminit(&outpam);
+
+    calculateInOutSize(inpam.width, outpam.width,
+                       &inputPointersWidth, &outputPointersWidth);
+
+    mpam = inpam;
+    mpam.width = inputPointersWidth;
+
+    inputPointers = pnm_allocpamrow(&mpam);
+
+    if (outputPointersWidth > inputPointersWidth) {
+        unsigned int col;
+
+        MALLOCARRAY(outputPointers, outputPointersWidth);
+
+        if (!outputPointers) {
+            pm_error("Unable to allocate memory for %u output pointers",
+                     outputPointersWidth);
+        }
+
+        /* Copy pointers as far as inputPointers[] goes, then wrap around */
+        for (col = 0; col < outputPointersWidth; ++col)
+            outputPointers[col] = inputPointers[col % inputPointersWidth];
+
+    } else
+        outputPointers = inputPointers;
+
+    restack(&inpam, &outpam, inputPointers, outputPointers,
+            inputPointersWidth, outputPointersWidth, trimMode);
+
+    if (inputPointers != outputPointers)
+        free(outputPointers);
+
+    pnm_freepamrow(inputPointers);
+}
+
+
+
+int
+main(int argc, const char * argv[]) {
+
+    struct CmdlineInfo cmdline;
+    FILE * ifP;
+    int    eof;     /* no more images in input stream */
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    for (eof = false; !eof; ) {
+        restackSingleImage(ifP, &cmdline);
+        pnm_nextimage(ifP, &eof);
+    }
+
+    pm_close(ifP);
+
+    return 0;
+}
diff --git a/editor/pamrubber.c b/editor/pamrubber.c
index 7169dbcf..f68e36fe 100644
--- a/editor/pamrubber.c
+++ b/editor/pamrubber.c
@@ -1,20 +1,18 @@
-/*----------------------------------------------------------------------------*/
-
-/* pamrubber.c - transform images using Rubber Sheeting algorithm
-**               see: http://www.schaik.com/netpbm/rubber/
-**
-** Copyright (C) 2011 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.
-*/
-
-/*----------------------------------------------------------------------------*/
-
+/*=============================================================================
+                              pamrubber
+===============================================================================
+  Transform images using Rubber Sheeting algorithm
+  See: http://www.schaik.com/netpbm/rubber/
+
+  Copyright (C) 2011 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.
+=============================================================================*/
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
@@ -25,12 +23,12 @@
 
 #include "pm_c_util.h"
 #include "mallocvar.h"
+#include "rand.h"
 #include "shhopt.h"
 #include "pam.h"
 #include "pamdraw.h"
 
 
-
 typedef struct {
   double x;
   double y;
@@ -54,7 +52,7 @@ typedef struct {
     point br;  /* bottom right */
 } quadrilateral;
 
-struct cmdlineInfo {
+struct CmdlineInfo {
     unsigned int nCP;
     point        oldCP[4];
     point        newCP[4];
@@ -64,14 +62,14 @@ struct cmdlineInfo {
     unsigned int frame;
     unsigned int linear;
     unsigned int verbose;
-    unsigned int randseedSpec;
-    unsigned int randseed;
+    unsigned int randomseedSpec;
+    unsigned int randomseed;
 };
 
 
 static void
 parseCmdline(int argc, const char ** argv,
-             struct cmdlineInfo * const cmdlineP) {
+             struct CmdlineInfo * const cmdlineP) {
 
 /* parse all parameters from the command line */
 
@@ -83,7 +81,7 @@ parseCmdline(int argc, const char ** argv,
     /* instructions to optParseOptions3 on how to parse our options. */
     optEntry * option_def;
     optStruct3 opt;
-    
+
     MALLOCARRAY_NOFAIL(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENT3 */
@@ -92,10 +90,10 @@ parseCmdline(int argc, const char ** argv,
     OPTENT3(0, "frame",    OPT_FLAG, NULL, &cmdlineP->frame,    0);
     OPTENT3(0, "linear",   OPT_FLAG, NULL, &cmdlineP->linear,   0);
     OPTENT3(0, "verbose",  OPT_FLAG, NULL, &cmdlineP->verbose,  0);
-    OPTENT3(0, "randseed", OPT_UINT, &cmdlineP->randseed,
-            &cmdlineP->randseedSpec, 0);
-    OPTENT3(0, "randomseed", OPT_UINT, &cmdlineP->randseed,
-            &cmdlineP->randseedSpec, 0);
+    OPTENT3(0, "randseed", OPT_UINT, &cmdlineP->randomseed,
+            &cmdlineP->randomseedSpec, 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 */
@@ -195,38 +193,39 @@ makeline(point const p1,
 
 
 
-static bool
-intersect(line *  const l1P,
-          line *  const l2P,
-          point * const pP) {
+static void
+findIntersection(const line *  const l1P,
+                 const line *  const l2P,
+                 bool *        const theyIntersectP,
+                 point *       const intersectionP) {
 
-    bool cross;
+    bool theyIntersect;
 
     if (((l2P->p2.y - l2P->p1.y) * (l1P->p2.x - l1P->p1.x) -
          (l2P->p2.x - l2P->p1.x) * (l1P->p2.y - l1P->p1.y)) == 0) {
         /* parallel lines */
 
-        cross = false;
+        theyIntersect = false;
 
         if ((l1P->p1.x == l1P->p2.x) && (l2P->p1.x == l2P->p2.x)) {
             /* two vertical lines */
-            pP->x = (l1P->p1.x + l2P->p1.x) / 2.0;
-            pP->y = 1e10;
+            intersectionP->x = (l1P->p1.x + l2P->p1.x) / 2.0;
+            intersectionP->y = 1e10;
         } else if ((l1P->p1.y == l1P->p2.y) && (l2P->p1.y == l2P->p2.y)) {
             /* two horizontal lines */
-            pP->x = 1e10;
-            pP->y = (l1P->p1.y + l2P->p1.y) / 2.0;
+            intersectionP->x = 1e10;
+            intersectionP->y = (l1P->p1.y + l2P->p1.y) / 2.0;
         } else {
             if (fabs(l1P->p2.y - l1P->p1.y) > fabs(l1P->p2.x - l1P->p1.x)) {
                 /* steep slope */
-                pP->y = 1e10;
-                pP->x = (l1P->p2.x - l1P->p1.x) / (l1P->p2.y - l1P->p1.y)
-                    * 1e10;
+                intersectionP->y = 1e10;
+                intersectionP->x =
+                    (l1P->p2.x - l1P->p1.x) / (l1P->p2.y - l1P->p1.y) * 1e10;
             } else {
                 /* even slope */
-                pP->x = 1e10;
-                pP->y = (l1P->p2.y - l1P->p1.y) / (l1P->p2.x - l1P->p1.x)
-                    * 1e10;
+                intersectionP->x = 1e10;
+                intersectionP->y =
+                    (l1P->p2.y - l1P->p1.y) / (l1P->p2.x - l1P->p1.x) * 1e10;
             }
         }
     } else {
@@ -244,16 +243,16 @@ intersect(line *  const l1P,
                * (l1P->p2.x - l1P->p1.x) - (l2P->p2.x - l2P->p1.x)
                * (l1P->p2.y - l1P->p1.y));
 
-        pP->x = l1P->p1.x + ua * (l1P->p2.x - l1P->p1.x);
-        pP->y = l1P->p1.y + ua * (l1P->p2.y - l1P->p1.y);
+        intersectionP->x = l1P->p1.x + ua * (l1P->p2.x - l1P->p1.x);
+        intersectionP->y = l1P->p1.y + ua * (l1P->p2.y - l1P->p1.y);
 
         if ((ua >= 0.0) && (ua <= 1.0) && (ub >= 0.0) && (ub <= 1.0))
-            cross = true;
+            theyIntersect = true;
         else
-            cross = false;
+            theyIntersect = false;
     }
-
-    return cross;
+    if (theyIntersectP)
+        *theyIntersectP = theyIntersect;
 }
 
 
@@ -268,7 +267,7 @@ maketriangle(point const p1,
     retval.p1 = p1;
     retval.p2 = p2;
     retval.p3 = p3;
-    
+
     return retval;
 }
 
@@ -309,28 +308,29 @@ insidetri(triangle * const triP,
 
 
 static bool
-windtriangle(triangle * const tP,
+windtriangle(triangle * const triP,
              point      const p1,
              point      const p2,
              point      const p3) {
     point f, c;
-    line le, lv;
     bool cw;
 
     /* find cross of vertical through p3 and the edge p1-p2 */
     f.x = p3.x;
     f.y = -1.0;
-    lv = makeline(p3, f);
-    le = makeline(p1, p2);
-    intersect(&le, &lv, &c);
+    {
+        line const lv = makeline(p3, f);
+        line const le = makeline(p1, p2);
+        findIntersection(&le, &lv, NULL, &c);
+    }
 
     /* check for clockwise winding triangle */
     if (((p1.x > p2.x) && (p3.y < c.y)) ||
         ((p1.x < p2.x) && (p3.y > c.y))) {
-        *tP = maketriangle(p1, p2, p3);
+        *triP = maketriangle(p1, p2, p3);
         cw = true;
     } else { /* p1/2/3 were counter clockwise */
-        *tP = maketriangle(p1, p3, p2);
+        *triP = maketriangle(p1, p3, p2);
         cw = false;
     }
     return cw;
@@ -339,28 +339,29 @@ windtriangle(triangle * const tP,
 
 
 static double
-tiny(void) {
+tiny(struct pm_randSt * const randStP) {
 
-    if (rand() % 2)
-        return +1E-6 * (double) ((rand() % 90) + 9);
+    if (pm_rand(randStP) % 2)
+        return +1E-6 * (double) ((pm_rand(randStP) % 90) + 9);
     else
-        return -1E-6 * (double) ((rand() % 90) + 9);
+        return -1E-6 * (double) ((pm_rand(randStP) % 90) + 9);
 }
 
 
 
 static void
 angle(point * const p1P,
-      point * const p2P) {
+      point * const p2P,
+      struct pm_randSt * const randStP) {
 /*----------------------------------------------------------------------------
    Move *p2P slightly if necessary to make sure the line (*p1P, *p2P)
    is not horizontal or vertical.
 -----------------------------------------------------------------------------*/
     if (p1P->x == p2P->x) { /* vertical line */
-        p2P->x += tiny();
+        p2P->x += tiny(randStP);
     }
     if (p1P->y == p2P->y) { /* horizontal line */
-        p2P->y += tiny();
+        p2P->y += tiny(randStP);
     }
 }
 
@@ -382,11 +383,11 @@ sideTriangleVerticalEdge(unsigned int const n,
                          point        const p24,
                          point        const r21,
                          point        const r22) {
-                                   
+
     if (((n >= 4) && (r11.x < p11.x)
          && (p14.x < p13.x) && (p14.x < p12.x)
          && (p14.x < p11.x)) /* left edge */
-        || 
+        ||
         ((n >= 4) && (r11.x > p11.x)
          && (p14.x > p13.x) && (p14.x > p12.x)
          && (p14.x > p11.x))) /* right edge */ {
@@ -394,7 +395,7 @@ sideTriangleVerticalEdge(unsigned int const n,
         *trig2P = maketriangle(r21, r22, p24);
     } else if (((n >= 3) && (r11.x < p11.x) && (p13.x < p12.x)
                 && (p13.x < p11.x)) /* left edge */
-               || 
+               ||
                ((n >= 3) && (r11.x > p11.x) && (p13.x > p12.x)
                 && (p13.x > p11.x))) /* right edge */ {
         *trig1P = maketriangle(r11, r12, p13);
@@ -403,7 +404,7 @@ sideTriangleVerticalEdge(unsigned int const n,
                 && (p12.x < p11.x)) /* left edge */
                ||
                ((n >= 2) && (r11.x > p11.x)
-                && (p12.x > p11.x))) /* right edge */ { 
+                && (p12.x > p11.x))) /* right edge */ {
         *trig1P = maketriangle(r11, r12, p12);
         *trig2P = maketriangle(r21, r22, p22);
     } else if (n >= 1) {
@@ -430,24 +431,24 @@ sideTriangleHorizontalEdge(unsigned int const n,
                            point        const p24,
                            point        const r21,
                            point        const r22) {
-                                   
+
     if (((n >= 4) && (r11.y < p11.y) && (p14.y < p13.y)
          && (p14.y < p12.y) && (p14.y < p11.y)) /* top edge */
-        || 
+        ||
         ((n >= 4) && (r11.y > p11.y) && (p14.y > p13.y)
          && (p14.y > p12.y) && (p14.y > p11.y))) /* bottom edge */ {
         *trig1P = maketriangle(r11, r12, p14);
         *trig2P = maketriangle(r21, r22, p24);
     } else if (((n >= 3) && (r11.y < p11.y) && (p13.y < p12.y)
                 && (p13.y < p11.y)) /* top edge */
-               || 
+               ||
                ((n >= 3) && (r11.y > p11.y) && (p13.y > p12.y)
                 && (p13.y > p11.y))) /* bottom edge */ {
         *trig1P = maketriangle(r11, r12, p13);
         *trig2P = maketriangle(r21, r22, p23);
     } else if (((n >= 2) && (r11.y < p11.y)
                 && (p12.y < p11.y)) /* top edge */
-               || 
+               ||
                ((n >= 2) && (r11.y > p11.y)
                 && (p12.y > p11.y))) /* bottom edge */ {
         *trig1P = maketriangle(r11, r12, p12);
@@ -505,7 +506,7 @@ edgeTriangle(triangle * const trig1P,
              point      const tr2,
              point      const bl2,
              point      const br2) {
-             
+
     if ((p11.x < p12.x) && (p11.y < p12.y)) {
         /* up/left to down/right */
         *trig1P = maketriangle(tr1, p12, p11);
@@ -613,12 +614,16 @@ quadCorner(point           const p0,
 
     /* p0-p1 and p2-p3 are the diagonals */
 
+    triangle tri;
+
     if (fabs(p0.x - p1.x) + fabs(p0.y - p1.y) >=
         fabs(p2.x - p3.x) + fabs(p2.y - p3.y)) {
-        quadCornerSized(p0, p1, p2, p3, mid, quadP, triP);
+        quadCornerSized(p0, p1, p2, p3, mid, quadP, &tri);
     } else {
-        quadCornerSized(p2, p3, p0, p1, mid, quadP, triP);
+        quadCornerSized(p2, p3, p0, p1, mid, quadP, &tri);
     }
+    if (triP)
+        *triP = tri;
 }
 
 
@@ -633,7 +638,7 @@ frameDrawproc (tuple **     const tuples,
                sample       const maxval,
                pamd_point   const p,
                const void * const clientdata) {
-    
+
     int yy;
 
     for (yy = p.y - 1; yy <= p.y + 1; ++yy) {
@@ -688,14 +693,14 @@ clippedPoint(const struct pam * const pamP,
         clippedX = pamP->width - 2;
     else
         clippedX = roundedX;
-        
+
     if (roundedY <= 0)
         clippedY = 1;
     else if (roundedY > pamP->height - 1)
         clippedY = pamP->height - 2;
     else
         clippedY = roundedY;
-        
+
     return pamd_makePoint(clippedX, clippedY);
 }
 
@@ -720,8 +725,10 @@ static void drawClippedTriangle(const struct pam * const pamP,
 
 
 static void
-prepTrig(int const wd,
-         int const ht) {
+prepTrig(int          const wd,
+         int          const ht,
+         bool         const randomseedSpec,
+         unsigned int const randomseed) {
 
 /* create triangles using control points */
 
@@ -729,18 +736,21 @@ prepTrig(int const wd,
     point rtl2, rtr2, rbl2, rbr2;
     point c1p1, c1p2, c1p3, c1p4;
     point c2p1, c2p2, c2p3, c2p4;
-    line l1, l2;
     point p0;
+    struct pm_randSt randSt;
+
+    pm_randinit(&randSt);
+    pm_srand2(&randSt, randomseedSpec, randomseed);
 
-    rtl1 = makepoint(0.0 + tiny(),               0.0 + tiny());
-    rtr1 = makepoint((double) wd - 1.0 + tiny(), 0.0 + tiny());
-    rbl1 = makepoint(0.0 + tiny(),               (double) ht - 1.0 + tiny());
-    rbr1 = makepoint((double) wd - 1.0 + tiny(), (double) ht - 1.0 + tiny());
+    rtl1 = makepoint(0.0 + tiny(&randSt),               0.0 + tiny(&randSt));
+    rtr1 = makepoint((double) wd - 1.0 + tiny(&randSt), 0.0 + tiny(&randSt));
+    rbl1 = makepoint(0.0 + tiny(&randSt),               (double) ht - 1.0 + tiny(&randSt));
+    rbr1 = makepoint((double) wd - 1.0 + tiny(&randSt), (double) ht - 1.0 + tiny(&randSt));
 
-    rtl2 = makepoint(0.0 + tiny(),               0.0 + tiny());
-    rtr2 = makepoint((double) wd - 1.0 + tiny(), 0.0 + tiny());
-    rbl2 = makepoint(0.0 + tiny(),               (double) ht - 1.0 + tiny());
-    rbr2 = makepoint((double) wd - 1.0 + tiny(), (double) ht - 1.0 + tiny());
+    rtl2 = makepoint(0.0 + tiny(&randSt),               0.0 + tiny(&randSt));
+    rtr2 = makepoint((double) wd - 1.0 + tiny(&randSt), 0.0 + tiny(&randSt));
+    rbl2 = makepoint(0.0 + tiny(&randSt),               (double) ht - 1.0 + tiny(&randSt));
+    rbr2 = makepoint((double) wd - 1.0 + tiny(&randSt), (double) ht - 1.0 + tiny(&randSt));
 
     if (nCP == 1) {
         c1p1 = oldCP[0];
@@ -749,19 +759,19 @@ prepTrig(int const wd,
         /* connect control point to all corners to get 4 triangles */
         /* left side triangle */
         sideTriangle(nCP,
-                     &tri1s[0], c1p1, p0, p0, p0, rbl1, rtl1, 
+                     &tri1s[0], c1p1, p0, p0, p0, rbl1, rtl1,
                      &tri2s[0], c2p1, p0, p0, p0, rbl2, rtl2);
         /* top side triangle */
         sideTriangle(nCP,
-                     &tri1s[1], c1p1, p0, p0, p0, rtl1, rtr1, 
+                     &tri1s[1], c1p1, p0, p0, p0, rtl1, rtr1,
                      &tri2s[1], c2p1, p0, p0, p0, rtl2, rtr2);
         /* right side triangle */
         sideTriangle(nCP,
-                     &tri1s[2], c1p1, p0, p0, p0, rtr1, rbr1, 
+                     &tri1s[2], c1p1, p0, p0, p0, rtr1, rbr1,
                      &tri2s[2], c2p1, p0, p0, p0, rtr2, rbr2);
         /* bottom side triangle */
         sideTriangle(nCP,
-                     &tri1s[3], c1p1, p0, p0, p0, rbr1, rbl1, 
+                     &tri1s[3], c1p1, p0, p0, p0, rbr1, rbl1,
                      &tri2s[3], c2p1, p0, p0, p0, rbr2, rbl2);
 
         nTri = 4;
@@ -772,25 +782,25 @@ prepTrig(int const wd,
         c2p2 = newCP[1];
 
         /* check for hor/ver edges */
-        angle (&c1p1, &c1p2);
-        angle (&c2p1, &c2p2);
+        angle (&c1p1, &c1p2, &randSt);
+        angle (&c2p1, &c2p2, &randSt);
 
         /* connect two control points to corners to get 6 triangles */
         /* left side */
         sideTriangle(nCP,
-                     &tri1s[0], c1p1, c1p2, p0, p0, rbl1, rtl1, 
+                     &tri1s[0], c1p1, c1p2, p0, p0, rbl1, rtl1,
                      &tri2s[0], c2p1, c2p2, p0, p0, rbl2, rtl2);
         /* top side */
-        sideTriangle(nCP, 
-                     &tri1s[1], c1p1, c1p2, p0, p0, rtl1, rtr1, 
+        sideTriangle(nCP,
+                     &tri1s[1], c1p1, c1p2, p0, p0, rtl1, rtr1,
                      &tri2s[1], c2p1, c2p2, p0, p0, rtl2, rtr2);
         /* right side */
-        sideTriangle(nCP, 
-                     &tri1s[2], c1p1, c1p2, p0, p0, rtr1, rbr1, 
+        sideTriangle(nCP,
+                     &tri1s[2], c1p1, c1p2, p0, p0, rtr1, rbr1,
                      &tri2s[2], c2p1, c2p2, p0, p0, rtr2, rbr2);
         /* bottom side */
-        sideTriangle(nCP, 
-                     &tri1s[3], c1p1, c1p2, p0, p0, rbr1, rbl1, 
+        sideTriangle(nCP,
+                     &tri1s[3], c1p1, c1p2, p0, p0, rbr1, rbl1,
                      &tri2s[3], c2p1, c2p2, p0, p0, rbr2, rbl2);
 
         /* edge to corner triangles */
@@ -803,7 +813,7 @@ prepTrig(int const wd,
         c1p1 = oldCP[0];
         c1p2 = oldCP[1];
         c1p3 = oldCP[2];
-         
+
         c2p1 = newCP[0];
         c2p2 = newCP[1];
         c2p3 = newCP[2];
@@ -811,13 +821,13 @@ prepTrig(int const wd,
         /* Move vertices slightly if necessary to make sure no edge is
            horizontal or vertical.
         */
-        angle(&c1p1, &c1p2);
-        angle(&c1p2, &c1p3);
-        angle(&c1p3, &c1p1);
+        angle(&c1p1, &c1p2, &randSt);
+        angle(&c1p2, &c1p3, &randSt);
+        angle(&c1p3, &c1p1, &randSt);
 
-        angle(&c2p1, &c2p2);
-        angle(&c2p2, &c2p3);
-        angle(&c2p3, &c2p1);
+        angle(&c2p1, &c2p2, &randSt);
+        angle(&c2p2, &c2p3, &randSt);
+        angle(&c2p3, &c2p1, &randSt);
 
         if (windtriangle(&tri1s[0], c1p1, c1p2, c1p3)) {
             tri2s[0] = maketriangle(c2p1, c2p2, c2p3);
@@ -828,7 +838,7 @@ prepTrig(int const wd,
         c1p1 = tri1s[0].p1;
         c1p2 = tri1s[0].p2;
         c1p3 = tri1s[0].p3;
-         
+
         c2p1 = tri2s[0].p1;
         c2p2 = tri2s[0].p2;
         c2p3 = tri2s[0].p3;
@@ -836,19 +846,19 @@ prepTrig(int const wd,
         /* point to side triangles */
         /* left side */
         sideTriangle(nCP,
-                     &tri1s[1], c1p1, c1p2, c1p3, p0, rbl1, rtl1, 
+                     &tri1s[1], c1p1, c1p2, c1p3, p0, rbl1, rtl1,
                      &tri2s[1], c2p1, c2p2, c2p3, p0, rbl2, rtl2);
         /* top side */
-        sideTriangle(nCP, 
-                     &tri1s[2], c1p1, c1p2, c1p3, p0, rtl1, rtr1, 
+        sideTriangle(nCP,
+                     &tri1s[2], c1p1, c1p2, c1p3, p0, rtl1, rtr1,
                      &tri2s[2], c2p1, c2p2, c2p3, p0, rtl2, rtr2);
         /* right side */
-        sideTriangle(nCP, 
-                     &tri1s[3], c1p1, c1p2, c1p3, p0, rtr1, rbr1, 
+        sideTriangle(nCP,
+                     &tri1s[3], c1p1, c1p2, c1p3, p0, rtr1, rbr1,
                      &tri2s[3], c2p1, c2p2, c2p3, p0, rtr2, rbr2);
         /* bottom side */
-        sideTriangle(nCP, 
-                     &tri1s[4], c1p1, c1p2, c1p3, p0, rbr1, rbl1, 
+        sideTriangle(nCP,
+                     &tri1s[4], c1p1, c1p2, c1p3, p0, rbr1, rbl1,
                      &tri2s[4], c2p1, c2p2, c2p3, p0, rbr2, rbl2);
 
         /* edge to corner triangles */
@@ -864,26 +874,26 @@ prepTrig(int const wd,
         c1p2 = oldCP[1];
         c1p3 = oldCP[2];
         c1p4 = oldCP[3];
-         
+
         c2p1 = newCP[0];
         c2p2 = newCP[1];
         c2p3 = newCP[2];
         c2p4 = newCP[3];
 
         /* check for hor/ver edges */
-        angle (&c1p1, &c1p2);
-        angle (&c1p2, &c1p3);
-        angle (&c1p3, &c1p4);
-        angle (&c1p4, &c1p1);
-        angle (&c1p1, &c1p3);
-        angle (&c1p2, &c1p4);
-
-        angle (&c2p1, &c2p2);
-        angle (&c2p2, &c2p3);
-        angle (&c2p3, &c2p4);
-        angle (&c2p4, &c2p1);
-        angle (&c2p1, &c2p3);
-        angle (&c2p2, &c2p4);
+        angle (&c1p1, &c1p2, &randSt);
+        angle (&c1p2, &c1p3, &randSt);
+        angle (&c1p3, &c1p4, &randSt);
+        angle (&c1p4, &c1p1, &randSt);
+        angle (&c1p1, &c1p3, &randSt);
+        angle (&c1p2, &c1p4, &randSt);
+
+        angle (&c2p1, &c2p2, &randSt);
+        angle (&c2p2, &c2p3, &randSt);
+        angle (&c2p3, &c2p4, &randSt);
+        angle (&c2p4, &c2p1, &randSt);
+        angle (&c2p1, &c2p3, &randSt);
+        angle (&c2p2, &c2p4, &randSt);
 
         /*-------------------------------------------------------------------*/
         /*        -1-      -2-        -3-      -4-        -5-      -6-       */
@@ -892,47 +902,58 @@ prepTrig(int const wd,
         /*       3   4    2   4      4   3    2   3      4   2    3   2      */
         /*-------------------------------------------------------------------*/
 
-        /* center two triangles */
-        l1 = makeline(c1p1, c1p4);
-        l2 = makeline(c1p2, c1p3);
-        if (intersect(&l1, &l2, &p0)) {
-            if (windtriangle(&tri1s[0], c1p1, c1p2, c1p3)) {
-                tri1s[1] = maketriangle(c1p4, c1p3, c1p2);
-                tri2s[0] = maketriangle(c2p1, c2p2, c2p3);
-                tri2s[1] = maketriangle(c2p4, c2p3, c2p2);
-            } else {
-                tri1s[1] = maketriangle(c1p4, c1p2, c1p3);
-                tri2s[0] = maketriangle(c2p1, c2p3, c2p2);
-                tri2s[1] = maketriangle(c2p4, c2p2, c2p3);
+        {
+            /* center two triangles */
+            line const l1 = makeline(c1p1, c1p4);
+            line const l2 = makeline(c1p2, c1p3);
+            bool theyIntersect;
+            findIntersection(&l1, &l2, &theyIntersect, &p0);
+            if (theyIntersect) {
+                if (windtriangle(&tri1s[0], c1p1, c1p2, c1p3)) {
+                    tri1s[1] = maketriangle(c1p4, c1p3, c1p2);
+                    tri2s[0] = maketriangle(c2p1, c2p2, c2p3);
+                    tri2s[1] = maketriangle(c2p4, c2p3, c2p2);
+                } else {
+                    tri1s[1] = maketriangle(c1p4, c1p2, c1p3);
+                    tri2s[0] = maketriangle(c2p1, c2p3, c2p2);
+                    tri2s[1] = maketriangle(c2p4, c2p2, c2p3);
+                }
             }
         }
-        l1 = makeline(c1p1, c1p3);
-        l2 = makeline(c1p2, c1p4);
-        if (intersect(&l1, &l2, &p0)) {
-            if (windtriangle(&tri1s[0], c1p1, c1p2, c1p4)) {
-                tri1s[1] = maketriangle(c1p3, c1p4, c1p2);
-                tri2s[0] = maketriangle(c2p1, c2p2, c2p4);
-                tri2s[1] = maketriangle(c2p3, c2p4, c2p2);
-            } else {
-                tri1s[1] = maketriangle(c1p3, c1p2, c1p4);
-                tri2s[0] = maketriangle(c2p1, c2p4, c2p2);
-                tri2s[1] = maketriangle(c2p3, c2p2, c2p4);
+        {
+            line const l1 = makeline(c1p1, c1p3);
+            line const l2 = makeline(c1p2, c1p4);
+            bool theyIntersect;
+            findIntersection(&l1, &l2, &theyIntersect, &p0);
+            if (theyIntersect) {
+                if (windtriangle(&tri1s[0], c1p1, c1p2, c1p4)) {
+                    tri1s[1] = maketriangle(c1p3, c1p4, c1p2);
+                    tri2s[0] = maketriangle(c2p1, c2p2, c2p4);
+                    tri2s[1] = maketriangle(c2p3, c2p4, c2p2);
+                } else {
+                    tri1s[1] = maketriangle(c1p3, c1p2, c1p4);
+                    tri2s[0] = maketriangle(c2p1, c2p4, c2p2);
+                    tri2s[1] = maketriangle(c2p3, c2p2, c2p4);
+                }
             }
         }
-        l1 = makeline(c1p1, c1p2);
-        l2 = makeline(c1p3, c1p4);
-        if (intersect(&l1, &l2, &p0)) {
-            if (windtriangle(&tri1s[0], c1p1, c1p3, c1p4)) {
-                tri1s[1] = maketriangle(c1p2, c1p4, c1p3);
-                tri2s[0] = maketriangle(c2p1, c2p3, c2p4);
-                tri2s[1] = maketriangle(c2p2, c2p4, c2p3);
-            } else {
-                tri1s[1] = maketriangle(c1p2, c1p3, c1p4);
-                tri2s[0] = maketriangle(c2p1, c2p4, c2p3);
-                tri2s[1] = maketriangle(c2p2, c2p3, c2p4);
+        {
+            line const l1 = makeline(c1p1, c1p2);
+            line const l2 = makeline(c1p3, c1p4);
+            bool theyIntersect;
+            findIntersection(&l1, &l2, &theyIntersect, &p0);
+            if (theyIntersect) {
+                if (windtriangle(&tri1s[0], c1p1, c1p3, c1p4)) {
+                    tri1s[1] = maketriangle(c1p2, c1p4, c1p3);
+                    tri2s[0] = maketriangle(c2p1, c2p3, c2p4);
+                    tri2s[1] = maketriangle(c2p2, c2p4, c2p3);
+                } else {
+                    tri1s[1] = maketriangle(c1p2, c1p3, c1p4);
+                    tri2s[0] = maketriangle(c2p1, c2p4, c2p3);
+                    tri2s[1] = maketriangle(c2p2, c2p3, c2p4);
+                }
             }
         }
-
         /* control points in correct orientation */
         c1p1 = tri1s[0].p1;
         c1p2 = tri1s[0].p2;
@@ -945,20 +966,20 @@ prepTrig(int const wd,
 
         /* triangle from triangle point to side of image */
         /* left side triangle */
-        sideTriangle(nCP, 
-                     &tri1s[2], c1p1, c1p2, c1p3, c1p4, rbl1, rtl1, 
+        sideTriangle(nCP,
+                     &tri1s[2], c1p1, c1p2, c1p3, c1p4, rbl1, rtl1,
                      &tri2s[2], c2p1, c2p2, c2p3, c2p4, rbl2, rtl2);
         /* top side triangle */
-        sideTriangle(nCP, 
-                     &tri1s[3], c1p1, c1p2, c1p3, c1p4, rtl1, rtr1, 
+        sideTriangle(nCP,
+                     &tri1s[3], c1p1, c1p2, c1p3, c1p4, rtl1, rtr1,
                      &tri2s[3], c2p1, c2p2, c2p3, c2p4, rtl2, rtr2);
         /* right side triangle */
-        sideTriangle(nCP, 
-                     &tri1s[4], c1p1, c1p2, c1p3, c1p4, rtr1, rbr1, 
+        sideTriangle(nCP,
+                     &tri1s[4], c1p1, c1p2, c1p3, c1p4, rtr1, rbr1,
                      &tri2s[4], c2p1, c2p2, c2p3, c2p4, rtr2, rbr2);
         /* bottom side triangle */
-        sideTriangle(nCP, 
-                     &tri1s[5], c1p1, c1p2, c1p3, c1p4, rbr1, rbl1, 
+        sideTriangle(nCP,
+                     &tri1s[5], c1p1, c1p2, c1p3, c1p4, rbr1, rbl1,
                      &tri2s[5], c2p1, c2p2, c2p3, c2p4, rbr2, rbl2);
 
         /*-------------------------------------------------------------------*/
@@ -979,6 +1000,8 @@ prepTrig(int const wd,
                      &tri2s[9], c2p3, c2p1, rtl2, rtr2, rbl2, rbr2);
         nTri = 10;
     }
+
+    pm_randterm(&randSt);
 }
 
 
@@ -988,11 +1011,6 @@ prepQuad(void) {
 
 /* order quad control points */
 
-    double d01, d12, d20;
-    line l1, l2;
-    point mid;
-    triangle tri;
-
     if (nCP == 1) {
         /* create a rectangle from top-left corner of image and control
            point
@@ -1014,7 +1032,7 @@ prepQuad(void) {
             /* bottom-right and top-left */
             quad1 = quadRect(oldCP[1].x, oldCP[0].x, oldCP[1].y, oldCP[0].y);
         }
-        
+
         if ((newCP[0].x < newCP[1].x) && (newCP[0].y < newCP[1].y)) {
             /* top-left and bottom-right */
             quad2 = quadRect(newCP[0].x, newCP[1].x, newCP[0].y, newCP[1].y);
@@ -1034,10 +1052,10 @@ prepQuad(void) {
             /* diagonal of the parallelogram is the two control points
                furthest apart
             */
-            
-            d01 = distance(newCP[0], newCP[1]);
-            d12 = distance(newCP[1], newCP[2]);
-            d20 = distance(newCP[2], newCP[0]);
+
+            double const d01 = distance(newCP[0], newCP[1]);
+            double const d12 = distance(newCP[1], newCP[2]);
+            double const d20 = distance(newCP[2], newCP[0]);
 
             if ((d01 > d12) && (d01 > d20)) {
                 oldCP[3].x = oldCP[0].x + oldCP[1].x - oldCP[2].x;
@@ -1067,51 +1085,72 @@ prepQuad(void) {
 
         /* nCP = 3 or 4 */
 
-        /* find the intersection of the diagonals */
-        l1 = makeline(oldCP[0], oldCP[1]);
-        l2 = makeline(oldCP[2], oldCP[3]);
-        if (intersect(&l1, &l2, &mid)) {
-            quadCorner(oldCP[0], oldCP[1], oldCP[2], oldCP[3],
-                       mid, &quad1, &tri);
-        } else {
-            l1 = makeline(oldCP[0], oldCP[2]);
-            l2 = makeline(oldCP[1], oldCP[3]);
-            if (intersect(&l1, &l2, &mid))
-                quadCorner(oldCP[0], oldCP[2], oldCP[1], oldCP[3],
-                           mid, &quad1, &tri);
+        {
+            /* find the intersection of the diagonals */
+            line const l1 = makeline(oldCP[0], oldCP[1]);
+            line const l2 = makeline(oldCP[2], oldCP[3]);
+            bool theyIntersect;
+            point mid;
+            findIntersection(&l1, &l2, &theyIntersect, &mid);
+            if (theyIntersect)
+                quadCorner(oldCP[0], oldCP[1], oldCP[2], oldCP[3],
+                           mid, &quad1, NULL);
             else {
-                l1 = makeline(oldCP[0], oldCP[3]);
-                l2 = makeline(oldCP[1], oldCP[2]);
-                if (intersect(&l1, &l2, &mid))
-                    quadCorner(oldCP[0], oldCP[3],
-                               oldCP[1], oldCP[2], mid, &quad1, &tri);
-                else
-                    pm_error("The four old control points don't seem "
-                             "to be corners.");
+                line const l1 = makeline(oldCP[0], oldCP[2]);
+                line const l2 = makeline(oldCP[1], oldCP[3]);
+                bool theyIntersect;
+                point mid;
+                findIntersection(&l1, &l2, &theyIntersect, &mid);
+                if (theyIntersect)
+                    quadCorner(oldCP[0], oldCP[2], oldCP[1], oldCP[3],
+                               mid, &quad1, NULL);
+                else {
+                    line const l1 = makeline(oldCP[0], oldCP[3]);
+                    line const l2 = makeline(oldCP[1], oldCP[2]);
+                    bool theyIntersect;
+                    point mid;
+                    findIntersection(&l1, &l2, &theyIntersect, &mid);
+                    if (theyIntersect)
+                        quadCorner(oldCP[0], oldCP[3],
+                                   oldCP[1], oldCP[2], mid, &quad1, NULL);
+                    else
+                        pm_error("The four old control points don't seem "
+                                 "to be corners.");
+                }
             }
         }
-
-        /* repeat for the "to-be" control points */
-        l1 = makeline(newCP[0], newCP[1]);
-        l2 = makeline(newCP[2], newCP[3]);
-        if (intersect(&l1, &l2, &mid))
-            quadCorner(newCP[0], newCP[1], newCP[2], newCP[3],
-                       mid, &quad2, &tri);
-        else {
-            l1 = makeline(newCP[0], newCP[2]);
-            l2 = makeline(newCP[1], newCP[3]);
-            if (intersect(&l1, &l2, &mid))
-                quadCorner(newCP[0], newCP[2], newCP[1], newCP[3],
-                           mid, &quad2, &tri);
+        {
+            /* repeat for the "to-be" control points */
+            line const l1 = makeline(newCP[0], newCP[1]);
+            line const l2 = makeline(newCP[2], newCP[3]);
+            bool theyIntersect;
+            point mid;
+            findIntersection(&l1, &l2, &theyIntersect, &mid);
+            if (theyIntersect)
+                quadCorner(newCP[0], newCP[1], newCP[2], newCP[3],
+                           mid, &quad2, NULL);
             else {
-                l1 = makeline(newCP[0], newCP[3]);
-                l2 = makeline(newCP[1], newCP[2]);
-                if (intersect(&l1, &l2, &mid))
-                    quadCorner(newCP[0], newCP[3],
-                               newCP[1], newCP[2], mid, &quad2, &tri);
-                else
-                    pm_error("The four new control points don't seem "
-                             "to be corners.");
+                line const l1 = makeline(newCP[0], newCP[2]);
+                line const l2 = makeline(newCP[1], newCP[3]);
+                bool theyIntersect;
+                point mid;
+                findIntersection(&l1, &l2, &theyIntersect, &mid);
+                if (theyIntersect)
+                    quadCorner(newCP[0], newCP[2], newCP[1], newCP[3],
+                               mid, &quad2, NULL);
+                else {
+                    line const l1 = makeline(newCP[0], newCP[3]);
+                    line const l2 = makeline(newCP[1], newCP[2]);
+                    bool theyIntersect;
+                    point mid;
+                    findIntersection(&l1, &l2, &theyIntersect, &mid);
+                    if (theyIntersect)
+                        quadCorner(newCP[0], newCP[3],
+                                   newCP[1], newCP[2], mid, &quad2, NULL);
+                    else
+                        pm_error("The four new control points don't seem "
+                                 "to be corners.");
+                }
             }
         }
     }
@@ -1127,14 +1166,11 @@ warpTrig(point   const p2,
 
     point e1p1, e1p2, e1p3;
     point e2p1, e2p2, e2p3;
-    line lp, le;
-    line l1, l2, l3;
-    double d1, d2, d3;
-    int i;
+    unsigned int i;
 
     /* find in which triangle p2 lies */
-    for (i = 0; i < nTri; i++) {
-        if (insidetri (&tri2s[i], p2))
+    for (i = 0; i < nTri; ++i) {
+        if (insidetri(&tri2s[i], p2))
             break;
     }
 
@@ -1142,28 +1178,36 @@ warpTrig(point   const p2,
         *p1P = makepoint(0.0, 0.0);
     else {
         /* where in triangle is point */
-        d1 = fabs (p2.x - tri2s[i].p1.x) + fabs (p2.y - tri2s[i].p1.y);
-        d2 = fabs (p2.x - tri2s[i].p2.x) + fabs (p2.y - tri2s[i].p2.y);
-        d3 = fabs (p2.x - tri2s[i].p3.x) + fabs (p2.y - tri2s[i].p3.y);
-
-        /* line through p1 and p intersecting with edge p2-p3 */
-        lp = makeline(tri2s[i].p1, p2);
-        le = makeline(tri2s[i].p2, tri2s[i].p3);
-        intersect (&lp, &le, &e2p1);
-
-        /* line through p2 and p intersecting with edge p3-p1 */
-        lp = makeline(tri2s[i].p2, p2);
-        le = makeline(tri2s[i].p3, tri2s[i].p1);
-        intersect (&lp, &le, &e2p2);
+        double const d1 =
+            fabs (p2.x - tri2s[i].p1.x) + fabs (p2.y - tri2s[i].p1.y);
+        double const d2 =
+            fabs (p2.x - tri2s[i].p2.x) + fabs (p2.y - tri2s[i].p2.y);
+        double const d3 =
+            fabs (p2.x - tri2s[i].p3.x) + fabs (p2.y - tri2s[i].p3.y);
+
+        {
+            /* line through p1 and p intersecting with edge p2-p3 */
+            line const lp = makeline(tri2s[i].p1, p2);
+            line const le = makeline(tri2s[i].p2, tri2s[i].p3);
+            findIntersection(&lp, &le, NULL, &e2p1);
+        }
 
-        /* line through p3 and p intersecting with edge p1-p2 */
-        lp = makeline(tri2s[i].p3, p2);
-        le = makeline(tri2s[i].p1, tri2s[i].p2);
-        intersect (&lp, &le, &e2p3);
+        {
+            /* line through p2 and p intersecting with edge p3-p1 */
+            line const lp = makeline(tri2s[i].p2, p2);
+            line const le = makeline(tri2s[i].p3, tri2s[i].p1);
+            findIntersection(&lp, &le, NULL, &e2p2);
+        }
 
+        {
+            /* line through p3 and p intersecting with edge p1-p2 */
+            line const lp = makeline(tri2s[i].p3, p2);
+            line const le = makeline(tri2s[i].p1, tri2s[i].p2);
+            findIntersection(&lp, &le, NULL, &e2p3);
+        }
         /* map target control points to source control points */
         e1p1.x = tri1s[i].p2.x
-            + (e2p1.x - tri2s[i].p2.x)/(tri2s[i].p3.x - tri2s[i].p2.x) 
+            + (e2p1.x - tri2s[i].p2.x)/(tri2s[i].p3.x - tri2s[i].p2.x)
             * (tri1s[i].p3.x - tri1s[i].p2.x);
         e1p1.y = tri1s[i].p2.y
             + (e2p1.y - tri2s[i].p2.y)/(tri2s[i].p3.y - tri2s[i].p2.y)
@@ -1181,17 +1225,19 @@ warpTrig(point   const p2,
             + (e2p3.y - tri2s[i].p1.y)/(tri2s[i].p2.y - tri2s[i].p1.y)
             * (tri1s[i].p2.y - tri1s[i].p1.y);
 
-        /* intersect grid lines in source */
-        l1 = makeline(tri1s[i].p1, e1p1);
-        l2 = makeline(tri1s[i].p2, e1p2);
-        l3 = makeline(tri1s[i].p3, e1p3);
-
-        if ((d1 < d2) && (d1 < d3))
-            intersect (&l2, &l3, p1P);
-        else if (d2 < d3)
-            intersect (&l1, &l3, p1P);
-        else
-            intersect (&l1, &l2, p1P);
+        {
+            /* intersect grid lines in source */
+            line const l1 = makeline(tri1s[i].p1, e1p1);
+            line const l2 = makeline(tri1s[i].p2, e1p2);
+            line const l3 = makeline(tri1s[i].p3, e1p3);
+
+            if ((d1 < d2) && (d1 < d3))
+                findIntersection(&l2, &l3, NULL, p1P);
+            else if (d2 < d3)
+                findIntersection(&l1, &l3, NULL, p1P);
+            else
+                findIntersection(&l1, &l2, NULL, p1P);
+        }
     }
 }
 
@@ -1208,37 +1254,38 @@ warpQuad(point   const p2,
     point c2tl, c2tr, c2bl, c2br;
     point e1t, e1b, e1l, e1r;
     point e2t, e2b, e2l, e2r;
-    line l2t, l2b, l2l, l2r;
-    line lh, lv;
 
     c1tl = quad1.tl;
     c1tr = quad1.tr;
     c1bl = quad1.bl;
     c1br = quad1.br;
-       
+
     c2tl = quad2.tl;
     c2tr = quad2.tr;
     c2bl = quad2.bl;
     c2br = quad2.br;
 
-    l2t = makeline(c2tl, c2tr);
-    l2b = makeline(c2bl, c2br);
-    l2l = makeline(c2tl, c2bl);
-    l2r = makeline(c2tr, c2br);
-
-    /* find intersections of lines through control points */
-    intersect (&l2t, &l2b, &h2);
-    intersect (&l2l, &l2r, &v2);
-
-    /* find intersections of axes through P with control point box */
-    lv = makeline(p2, v2);
-    intersect (&l2t, &lv, &e2t);
-    intersect (&l2b, &lv, &e2b);
-
-    lh = makeline(p2, h2);
-    intersect (&l2l, &lh, &e2l);
-    intersect (&l2r, &lh, &e2r);
-
+    {
+        line const l2t = makeline(c2tl, c2tr);
+        line const l2b = makeline(c2bl, c2br);
+        line const l2l = makeline(c2tl, c2bl);
+        line const l2r = makeline(c2tr, c2br);
+
+        /* find intersections of lines through control points */
+        findIntersection(&l2t, &l2b, NULL, &h2);
+        findIntersection(&l2l, &l2r, NULL, &v2);
+
+        {
+            /* find intersections of axes through P with control point box */
+            line const lv = makeline(p2, v2);
+            line const lh = makeline(p2, h2);
+
+            findIntersection(&l2t, &lv, NULL, &e2t);
+            findIntersection(&l2b, &lv, NULL, &e2b);
+            findIntersection(&l2l, &lh, NULL, &e2l);
+            findIntersection(&l2r, &lh, NULL, &e2r);
+        }
+    }
     /* map target control points to source control points */
     e1t.x = c1tl.x + (e2t.x - c2tl.x)/(c2tr.x - c2tl.x) * (c1tr.x - c1tl.x);
     if (c1tl.y == c1tr.y)
@@ -1268,16 +1315,18 @@ warpQuad(point   const p2,
             = c1tr.x + (e2r.y - c2tr.y)/(c2br.y - c2tr.y) * (c1br.x - c1tr.x);
     e1r.y = c1tr.y + (e2r.y - c2tr.y)/(c2br.y - c2tr.y) * (c1br.y - c1tr.y);
 
-    /* intersect grid lines in source */
-    lv = makeline(e1t, e1b);
-    lh = makeline(e1l, e1r);
-    intersect (&lh, &lv, p1P);
+    {
+        /* intersect grid lines in source */
+        line const lv = makeline(e1t, e1b);
+        line const lh = makeline(e1l, e1r);
+        findIntersection(&lh, &lv, NULL, p1P);
+    }
 }
 
 
 
 static void
-setGlobalCP(struct cmdlineInfo const cmdline) {
+setGlobalCP(struct CmdlineInfo const cmdline) {
 
     unsigned int i;
 
@@ -1364,22 +1413,22 @@ pix(tuple **     const tuples,
         pix = 0.0;
         if (((int) floor(p.x) >= 0) && ((int) floor(p.x) < width) &&
             ((int) floor(p.y) >= 0) && ((int) floor(p.y) < height)) {
-            pix += (1.0 - rx) * (1.0 - ry) 
+            pix += (1.0 - rx) * (1.0 - ry)
                 * tuples[(int) floor(p.y)][(int) floor(p.x)][plane];
         }
         if (((int) floor(p.x) + 1 >= 0) && ((int) floor(p.x) + 1 < width) &&
             ((int) floor(p.y) >= 0) && ((int) floor(p.y) < height)) {
-            pix += rx * (1.0 - ry) 
+            pix += rx * (1.0 - ry)
                 * tuples[(int) floor(p.y)][(int) floor(p.x) + 1][plane];
         }
         if (((int) floor(p.x) >= 0) && ((int) floor(p.x) < width) &&
             ((int) floor(p.y) + 1 >= 0) && ((int) floor(p.y) + 1 < height)) {
-            pix += (1.0 - rx) * ry 
+            pix += (1.0 - rx) * ry
                 * tuples[(int) floor(p.y) + 1][(int) floor(p.x)][plane];
         }
         if (((int) floor(p.x) + 1 >= 0) && ((int) floor(p.x) + 1 < width) &&
             ((int) floor(p.y) + 1 >= 0) && ((int) floor(p.y) + 1 < height)) {
-            pix += rx * ry 
+            pix += rx * ry
                 * tuples[(int) floor(p.y) + 1][(int) floor(p.x) + 1][plane];
         }
     }
@@ -1392,21 +1441,19 @@ pix(tuple **     const tuples,
 int
 main(int argc, const char ** const argv) {
 
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
     FILE * ifP;
     struct pam inpam, outpam;
     tuple ** inTuples;
     tuple ** outTuples;
     unsigned int p2y;
-  
+
     pm_proginit(&argc, argv);
 
     parseCmdline(argc, argv, &cmdline);
 
     setGlobalCP(cmdline);
 
-    srand(cmdline.randseedSpec ? cmdline.randseed : pm_randseed());
-
     ifP = pm_openr(cmdline.fileName);
 
     inTuples = pnm_readpam(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
@@ -1421,7 +1468,8 @@ main(int argc, const char ** const argv) {
     makeAllWhite(&outpam, outTuples);
 
     if (cmdline.tri)
-        prepTrig(inpam.width, inpam.height);
+        prepTrig(inpam.width, inpam.height,
+                 cmdline.randomseedSpec, cmdline.randomseed);
     if (cmdline.quad)
         prepQuad();
 
diff --git a/editor/pamshuffle.c b/editor/pamshuffle.c
new file mode 100644
index 00000000..bffb79c5
--- /dev/null
+++ b/editor/pamshuffle.c
@@ -0,0 +1,155 @@
+/*=============================================================================
+                               pamshuffle
+===============================================================================
+  Part of the Netpbm package.
+
+  Relocate pixels in row, randomly, using Fisher-Yates shuffling.
+
+  By Akira F. Urushibata
+
+  Contributed to the public domain by its author.
+=============================================================================*/
+
+#include <assert.h>
+#include "pm_c_util.h"
+#include "pam.h"
+#include "rand.h"
+#include "shhopt.h"
+
+struct CmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;
+    unsigned int column;
+    unsigned int randomseedSpec;
+    unsigned int randomseed;
+};
+
+static void
+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.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options. */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    MALLOCARRAY(option_def, 100);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = false;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = true;  /* We have no parms that are negative numbers */
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "column",     OPT_FLAG,   NULL,
+                               &cmdlineP->column,                    0);
+    OPTENT3(0,   "randomseed", OPT_UINT,   &cmdlineP->randomseed,
+                               &cmdlineP->randomseedSpec,            0);
+
+    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)
+        pm_error("Too many arguments (%u). "
+                 "The only possible argument is the input file name.", argc-1);
+    else if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else
+        cmdlineP->inputFileName = argv[1];
+
+}
+
+
+
+static void
+shuffleRow(tuple *            const tuplerow,
+           unsigned int       const cols,
+           struct pm_randSt * const randStP) {
+
+    unsigned int col;
+
+    for (col = 0; col + 1 < cols; ++col) {
+        tuple        const temp    = tuplerow[col];
+        unsigned int const randcol = col + pm_rand(randStP) % (cols - col);
+
+        assert(randcol >= col );
+        assert(randcol < cols);
+
+        /* swap */
+        tuplerow[col]     = tuplerow[randcol];
+        tuplerow[randcol] = temp;
+    }
+}
+
+
+
+int
+main(int argc, const char * argv[]) {
+
+    FILE * ifP;
+    int    eof;     /* no more images in input stream */
+
+    struct CmdlineInfo cmdline;
+    struct pam inpam;   /* Input PAM image */
+    struct pam outpam;  /* Output PAM image */
+    struct pm_randSt randSt;
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    pm_randinit(&randSt);
+    pm_srand2(&randSt, cmdline.randomseedSpec, cmdline.randomseed);
+
+    for (eof = FALSE; !eof;) {
+        tuple * inrow;   /* Input row buffer */
+        tuple * outrow;  /* Pointers into the input row buffer to reorder it */
+        unsigned int row, col;
+
+        pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+        outpam = inpam;
+        outpam.file = stdout;
+
+        pnm_writepaminit(&outpam);
+
+        inrow = pnm_allocpamrow(&inpam);
+
+        MALLOCARRAY(outrow, inpam.width);
+
+        if (!outrow)
+            pm_error("Unable to allocate memory for %u-column output buffer",
+                     inpam.width);
+
+        for (col = 0; col < inpam.width; ++col)
+            outrow[col] = inrow[col];
+
+        for (row = 0; row < inpam.height; ++row) {
+            pnm_readpamrow(&inpam, inrow);
+
+            if (cmdline.column && row > 0) {
+                /* Use the same shuffle ('outrow') as the previous row */
+            } else
+                shuffleRow(outrow, inpam.width, &randSt);
+
+            pnm_writepamrow(&outpam, outrow);
+        }
+
+        pnm_freepamrow(inrow);
+        free(outrow);
+        pnm_nextimage(ifP, &eof);
+    }
+
+    pm_randterm(&randSt);
+
+    return 0;
+}
diff --git a/editor/pamsistoaglyph.c b/editor/pamsistoaglyph.c
index f9e25518..cac1f99f 100644
--- a/editor/pamsistoaglyph.c
+++ b/editor/pamsistoaglyph.c
@@ -151,7 +151,7 @@ bestEyeSepWeEncountered(int const bestSeparation[3],
     for (i = 2; i >= 0; --i) {
         if (bestSeparation[i] != 0)
             return bestSeparation[i];
-    }    
+    }
     return altBestSeparation;
 }
 
@@ -429,3 +429,4 @@ main(int argc, const char *argv[]) {
     return 0;
 }
 
+
diff --git a/editor/pamstretch-gen b/editor/pamstretch-gen
index fec4469c..ee04821a 100755
--- a/editor/pamstretch-gen
+++ b/editor/pamstretch-gen
@@ -24,10 +24,6 @@ while true ; do
         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
         ;;
@@ -80,7 +76,7 @@ esac
 # in some exceptional cases adjustment is necessary because of
 # "-dropedge".
 
-report=$(pamscale -reportonly $1 $tempfile)
+report=$(pamscale $quietopt -reportonly $1 $tempfile)
 if [ $? -ne 0 ]; then
   echo "pamstretch-gen: pamscale -reportonly $1 (file) failed" 1>&2
   exit 1
diff --git a/editor/pamthreshold.c b/editor/pamthreshold.c
index 8d28bc4a..809d76d9 100644
--- a/editor/pamthreshold.c
+++ b/editor/pamthreshold.c
@@ -73,7 +73,7 @@ initRange(struct range * const rangeP) {
     rangeP->max = 0.0;
 }
 
-          
+
 
 static void
 addToRange(struct range * const rangeP,
@@ -89,7 +89,7 @@ static float
 spread(struct range const range) {
 
     assert(range.max >= range.min);
-    
+
     return range.max - range.min;
 }
 
@@ -120,12 +120,12 @@ parseGeometry(const char *   const wxl,
 
 
 static void
-parseCommandLine(int                 argc, 
+parseCommandLine(int                 argc,
                  char **             argv,
                  struct cmdlineInfo *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.
@@ -145,7 +145,7 @@ parseCommandLine(int                 argc,
     MALLOCARRAY_NOFAIL(option_def, 100);
 
     /* define the options */
-    OPTENT3(0, "simple",    OPT_FLAG,   NULL,               
+    OPTENT3(0, "simple",    OPT_FLAG,   NULL,
             &cmdlineP->simple,      0);
     OPTENT3(0, "local",     OPT_STRING, &localOpt,
             &localSpec,             0);
@@ -155,7 +155,7 @@ parseCommandLine(int                 argc,
             &thresholdSpec,         0);
     OPTENT3(0, "contrast",  OPT_FLOAT,  &cmdlineP->contrast,
             &contrastSpec,          0);
-    OPTENT3(0, "verbose",    OPT_FLAG,   NULL,               
+    OPTENT3(0, "verbose",    OPT_FLAG,   NULL,
             &cmdlineP->verbose,     0);
 
     /* set the defaults */
@@ -216,8 +216,8 @@ parseCommandLine(int                 argc,
         cmdlineP->inputFileName = "-";
     else if (argc-1 == 1)
         cmdlineP->inputFileName = argv[1];
-    else 
-        pm_error("Progam takes at most 1 parameter: the file name.  "
+    else
+        pm_error("Program takes at most 1 parameter: the file name.  "
                  "You specified %d", argc-1);
 }
 
@@ -389,7 +389,7 @@ computeGlobalThreshold(struct pam *         const inpamP,
        of the "k-means clustering algorithm."
 
        The article claims it's proven to converge, by the way.
-       We have an interation limit just as a safety net.
+       We have an iteration limit just as a safety net.
 
        This code originally implemented a rather different algorithm,
        while nonetheless carrying the comment that it implemented the
@@ -524,7 +524,7 @@ thresholdLocalRow(struct pam *       const inpamP,
         getLocalThreshold(inrows, inpamP->width, col, localWidth, windowHeight,
                           cmdline.threshold, minSpread, globalThreshold,
                           &threshold);
-        
+
         thresholdPixel(outpamP, inrow[col], outrow[col], threshold);
     }
 }
@@ -569,12 +569,12 @@ thresholdLocal(struct pam *       const inpamP,
     unsigned int oddLocalWidth;
     unsigned int oddLocalHeight;
     unsigned int i;
-    
+
     /* use a subimage with odd width and height to have a middle pixel */
 
     if (cmdline.width % 2 == 0)
         oddLocalWidth = cmdline.width + 1;
-    else 
+    else
         oddLocalWidth = cmdline.width;
     if (cmdline.height % 2 == 0)
         oddLocalHeight = cmdline.height + 1;
@@ -616,7 +616,7 @@ thresholdLocal(struct pam *       const inpamP,
                           outpamP, outrow);
 
         pnm_writepamrow(outpamP, outrow);
-        
+
         /* read next image line if available and necessary */
         if (row + windowHeight / 2 >= nextRowToRead &&
             nextRowToRead < inpamP->height)
@@ -655,7 +655,7 @@ thresholdIterative(struct pam * const inpamP,
 int
 main(int argc, char **argv) {
 
-    FILE * ifP; 
+    FILE * ifP;
     struct cmdlineInfo cmdline;
     struct pam inpam, outpam;
     int eof;  /* No more images in input stream */
@@ -711,3 +711,6 @@ main(int argc, char **argv) {
 
     return 0;
 }
+
+
+
diff --git a/editor/pamundice.c b/editor/pamundice.c
index dbe0a8df..60360f17 100644
--- a/editor/pamundice.c
+++ b/editor/pamundice.c
@@ -18,30 +18,28 @@
 #include "nstring.h"
 #include "mallocvar.h"
 
-#define MAXFILENAMELEN 80
-    /* Maximum number of characters we accept in filenames */
-
-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 * inputFilePattern;
-        /* null-terminated string, max MAXFILENAMELEN-10 characters */
     unsigned int across;
     unsigned int down;
-    unsigned int hoverlap; 
-    unsigned int voverlap; 
+    unsigned int hoverlap;
+    unsigned int voverlap;
+    const char * listfile;
+    unsigned int listfileSpec;
     unsigned int verbose;
 };
 
 
 
 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.
@@ -53,7 +51,7 @@ parseCommandLine(int argc, char ** argv,
         /* Instructions to pm_optParseOptions3 on how to parse our options.
          */
     optStruct3 opt;
-    
+
     unsigned int acrossSpec, downSpec;
     unsigned int hoverlapSpec, voverlapSpec;
     unsigned int option_def_index;
@@ -69,6 +67,8 @@ parseCommandLine(int argc, char ** argv,
             &hoverlapSpec,                    0);
     OPTENT3(0, "voverlap",    OPT_UINT,    &cmdlineP->voverlap,
             &voverlapSpec,                    0);
+    OPTENT3(0, "listfile",    OPT_STRING,  &cmdlineP->listfile,
+            &cmdlineP->listfileSpec,          0);
     OPTENT3(0, "verbose",     OPT_FLAG,    NULL,
             &cmdlineP->verbose,               0);
 
@@ -76,13 +76,19 @@ parseCommandLine(int argc, char ** argv,
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
     opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
 
-    pm_optParseOptions3( &argc, argv, opt, sizeof(opt), 0 );
+    pm_optParseOptions3( &argc, (char **)argv, opt, sizeof(opt), 0 );
         /* Uses and sets argc, argv, and some of *cmdline_p and others. */
 
-    if (!acrossSpec)
+    if (acrossSpec) {
+        if (cmdlineP->across == 0)
+            pm_error ("-across value must be positive");
+    } else
         cmdlineP->across = 1;
-    
-    if (!downSpec)
+
+    if (downSpec) {
+        if (cmdlineP->down == 0)
+            pm_error ("-down value must be positive");
+    } else
         cmdlineP->down = 1;
 
     if (!hoverlapSpec)
@@ -91,16 +97,23 @@ parseCommandLine(int argc, char ** argv,
     if (!voverlapSpec)
         cmdlineP->voverlap = 0;
 
-    if (argc-1 < 1)
-        pm_error("You must specify one argument: the input file name "
-                 "pattern (e.g. 'myimage%%2a%%2d.pnm')");
-    else {
-        cmdlineP->inputFilePattern = argv[1];
-
-        if (argc-1 > 1)
-            pm_error("Progam takes at most one parameter: input file name.  "
-                     "You specified %u", argc-1);
+    if (cmdlineP->listfileSpec) {
+        if (argc-1 > 0)
+            pm_error("Program takes no parameters when -listfile is "
+                     "specified.  You specified %u", argc-1);
+        else
+            cmdlineP->inputFilePattern = NULL;
+    } else {
+        if (argc-1 < 1)
+            pm_error("You must specify one argument, the input file name "
+                     "pattern (e.g. 'myimage%%2a%%2d.pnm'), or -listfile");
+        else if (argc-1 > 1)
+            pm_error("Program takes at most one parameter: "
+                     "the input file name pattern.  You specified %u", argc-1);
+        else
+            cmdlineP->inputFilePattern = argv[1];
     }
+    free(option_def);
 }
 
 
@@ -128,7 +141,7 @@ buffer_init(struct buffer * const bufferP) {
 
 static void
 buffer_term(struct buffer * const bufferP) {
-    
+
     free(bufferP->string);
 }
 
@@ -184,7 +197,7 @@ getPrecision(const char *   const pattern,
     inCursor = startInCursor;  /* Start right after the '%' */
 
     precision = 0;
-                
+
     while (isdigit(pattern[inCursor])) {
         precision = 10 * precision + digitValue(pattern[inCursor]);
         ++inCursor;
@@ -202,13 +215,24 @@ getPrecision(const char *   const pattern,
 
 
 
+typedef struct {
+    /* Context of % substitutions as we progress through a file name pattern */
+    bool downSub;
+        /* There has been a %d (down) substitution */
+    bool acrossSub;
+        /* There has been a %a (across) substitution */
+} SubstContext;
+
+
+
 static void
 doSubstitution(const char *    const pattern,
                unsigned int    const startInCursor,
                unsigned int    const rank,
                unsigned int    const file,
                struct buffer * const bufferP,
-               unsigned int *  const newInCursorP) {
+               unsigned int *  const newInCursorP,
+               SubstContext *  const substContextP) {
 
     unsigned int inCursor;
 
@@ -219,7 +243,7 @@ doSubstitution(const char *    const pattern,
         ++inCursor;
     } else {
         unsigned int precision;
-        
+
         getPrecision(pattern, inCursor, &precision, &inCursor);
 
         if (pattern[inCursor] == '\0')
@@ -232,16 +256,28 @@ doSubstitution(const char *    const pattern,
 
             switch (pattern[inCursor]) {
             case 'a':
-                pm_asprintf(&substString, "%0*u", precision, file);
-                pm_asprintf(&desc, "file (across)");
+                if (substContextP->acrossSub)
+                    pm_error("Format specifier 'a' appears more than "
+                             "once in input file pattern '%s'", pattern);
+                else {
+                    pm_asprintf(&substString, "%0*u", precision, file);
+                    pm_asprintf(&desc, "file (across)");
+                    substContextP->acrossSub = true;
+                }
                 break;
             case 'd':
-                pm_asprintf(&substString, "%0*u", precision, rank);
-                pm_asprintf(&desc, "rank (down)");
+                if (substContextP->downSub)
+                    pm_error("Format specifier 'd' appears more than "
+                             "once in input file pattern '%s'", pattern);
+                else {
+                    pm_asprintf(&substString, "%0*u", precision, rank);
+                    pm_asprintf(&desc, "rank (down)");
+                    substContextP->downSub = true;
+                }
                 break;
             default:
                 pm_error("Unknown format specifier '%c' in input file "
-                         "pattern '%s'.  Recognized format specifier s are "
+                         "pattern '%s'.  Recognized format specifiers are "
                          "'%%a' (across) and '%%d (down)'",
                          pattern[inCursor], pattern);
             }
@@ -252,7 +288,7 @@ doSubstitution(const char *    const pattern,
                          desc, (unsigned)strlen(substString), precision);
             else
                 buffer_addString(bufferP, substString);
-            
+
             pm_strfree(desc);
             pm_strfree(substString);
 
@@ -268,25 +304,32 @@ static void
 computeInputFileName(const char *  const pattern,
                      unsigned int  const rank,
                      unsigned int  const file,
-                     const char ** const fileNameP) {
+                     const char ** const fileNameP,
+                     bool *        const rankFileIndependentP) {
 
     struct buffer buffer;
     unsigned int inCursor;
+    SubstContext substContext;
 
     buffer_init(&buffer);
 
     inCursor = 0;
+    substContext.downSub   = 0;
+    substContext.acrossSub = 0;
 
     while (pattern[inCursor] != '\0') {
         if (pattern[inCursor] == '%') {
             ++inCursor;
 
-            doSubstitution(pattern, inCursor, rank, file, &buffer, &inCursor);
+            doSubstitution(pattern, inCursor, rank, file, &buffer, &inCursor,
+                           &substContext);
 
         } else
             buffer_addChar(&buffer, pattern[inCursor++]);
     }
 
+    *rankFileIndependentP = !substContext.downSub && !substContext.acrossSub;
+
     pm_asprintf(fileNameP, "%s", buffer.string);
 
     buffer_term(&buffer);
@@ -295,8 +338,127 @@ computeInputFileName(const char *  const pattern,
 
 
 
+
+static void
+createInFileListFmFile(const char  *  const listFile,
+                       unsigned int   const nRank,
+                       unsigned int   const nFile,
+                       const char *** const inputFileListP) {
+
+    FILE * const lfP = pm_openr(listFile);
+    unsigned int const fileCt = nRank * nFile;
+
+    const char ** inputFileList;
+    unsigned int fileSeq;
+
+    MALLOCARRAY_NOFAIL(inputFileList, nRank * nFile);
+
+    for (fileSeq = 0; fileSeq < fileCt; ) {
+        int eof;
+        size_t lineLen;
+        char * buf = NULL;   /* initial value */
+        size_t bufferSz = 0; /* initial value */
+
+        pm_getline(lfP, &buf, &bufferSz, &eof, &lineLen);
+
+        if (eof)
+            pm_error("Premature EOF reading list file.  "
+                     "Read %u files.  Should be %u.", fileSeq, fileCt);
+        else if (lineLen > 0) {
+            inputFileList[fileSeq] = buf;
+            ++fileSeq;
+        }
+    }
+    pm_close(lfP);
+
+    *inputFileListP = inputFileList;
+
+}
+
+
+
+static void
+createInFileListFmPattern(const char  *  const pattern,
+                          unsigned int   const nRank,
+                          unsigned int   const nFile,
+                          const char *** const inputFileListP) {
+
+    const char ** inputFileList;
+    unsigned int rank, file;
+    bool warnedSingleFile;
+
+    MALLOCARRAY_NOFAIL(inputFileList, nRank * nFile);
+
+    for (rank = 0, warnedSingleFile = false; rank < nRank ; ++rank) {
+         for (file = 0; file < nFile ; ++file) {
+             const unsigned int idx = rank * nFile + file;
+
+             bool fileNmIsRankFileIndependent;
+
+             computeInputFileName(pattern, rank, file, &inputFileList[idx],
+                                  &fileNmIsRankFileIndependent);
+
+             if (fileNmIsRankFileIndependent && !warnedSingleFile) {
+                 pm_message("Warning: No grid location (%%a/%%d) specified "
+                            "in input file pattern '%s'.  "
+                            "Input is single file", pattern);
+                 warnedSingleFile = true;
+             }
+         }
+    }
+    *inputFileListP = inputFileList;
+}
+
+
+
+static void
+destroyInFileList(const char ** const inputFileList,
+                  unsigned int  const nRank,
+                  unsigned int  const nFile) {
+
+    unsigned int const fileCt = nRank * nFile;
+
+    unsigned int fileSeq;
+
+    for (fileSeq = 0; fileSeq < fileCt; ++fileSeq)
+        pm_strfree(inputFileList[fileSeq]);
+
+    free(inputFileList);
+}
+
+
+
+typedef struct {
+    unsigned int nRank;  /* Number of images in the vertical direction */
+    unsigned int nFile;  /* Number of images in the horizontal direction */
+    unsigned int hoverlap;    /* horizontal overlap */
+    unsigned int voverlap;    /* vertical overlap */
+    const char ** list;  /* List (1-dimensional array) of filenames */
+                         /* Row-major, top to bottom, left to right */
+} InputFiles;
+
+
+
+static const char *
+inputFileName(InputFiles     const inputFiles,
+              unsigned int   const rank,
+              unsigned int   const file) {
+/*----------------------------------------------------------------------------
+    A selected entry from "inputFiles.list" based on "rank" and "file".
+
+    Currently we assume that the list is a one-dimensional represetation
+    of an array, row-major, top to bottom and left to right in each row.
+----------------------------------------------------------------------------*/
+    assert(rank < inputFiles.nRank);
+    assert(file < inputFiles.nFile);
+
+    return inputFiles.list[rank * inputFiles.nFile + file];
+}
+
+
+
 static void
-getCommonInfo(const char *   const inputFilePattern,
+getCommonInfo(InputFiles     const inputFiles,
               int *          const formatP,
               unsigned int * const depthP,
               sample *       const maxvalP,
@@ -306,16 +468,12 @@ getCommonInfo(const char *   const inputFilePattern,
    among all input images and the output image.  I.e. everything except
    width and height.
 -----------------------------------------------------------------------------*/
-    const char * fileName;
-        /* Name of top left input image */
     FILE * ifP;
         /* Top left input image stream */
     struct pam inpam00;
         /* Description of top left input image */
 
-    computeInputFileName(inputFilePattern, 0, 0, &fileName);
-
-    ifP = pm_openr(fileName);
+    ifP = pm_openr(inputFileName(inputFiles, 0, 0));
 
     pnm_readpaminit(ifP, &inpam00, PAM_STRUCT_SIZE(tuple_type));
 
@@ -325,40 +483,19 @@ getCommonInfo(const char *   const inputFilePattern,
     strcpy(tupleType, inpam00.tuple_type);
 
     pm_close(ifP);
-
-    pm_strfree(fileName);
 }
 
 
 
-static FILE *
-openInputImage(const char * const inputFilePattern,
-               unsigned int const rank,
-               unsigned int const file) {
-
-    FILE * retval;
-    const char * fileName;
-        
-    computeInputFileName(inputFilePattern, rank, file, &fileName);
-
-    retval = pm_openr(fileName);
-    
-    pm_strfree(fileName);
-
-    return retval;
-}
-
-               
-
 static void
-getImageInfo(const char * const inputFilePattern,
+getImageInfo(InputFiles   const inputFiles,
              unsigned int const rank,
              unsigned int const file,
              struct pam * const pamP) {
 
     FILE * ifP;
 
-    ifP = openInputImage(inputFilePattern, rank, file);
+    ifP = pm_openr(inputFileName(inputFiles, rank, file));
 
     pnm_readpaminit(ifP, pamP, PAM_STRUCT_SIZE(tuple_type));
 
@@ -369,76 +506,70 @@ getImageInfo(const char * const inputFilePattern,
 
 
 static void
-getOutputWidth(const char * const inputFilePattern,
-               unsigned int const nFile,
-               unsigned int const hoverlap,
-               int *        const widthP) {
+getOutputWidth(InputFiles const inputFiles,
+               int *      const widthP) {
 /*----------------------------------------------------------------------------
-   Get the output width by adding up the widths of all 'nFile' images of
-   the top rank, and allowing for overlap of 'hoverlap' pixels.
+   Get the output width by adding up the widths of all 'inputFiles.nFile'
+   images of the top rank, and allowing for overlap of 'inputFiles.hoverlap'
+   pixels.
 -----------------------------------------------------------------------------*/
-    unsigned int totalWidth;
+    double       totalWidth;
     unsigned int file;
 
-    for (file = 0, totalWidth = 0; file < nFile; ++file) {
+    for (file = 0, totalWidth = 0; file < inputFiles.nFile; ++file) {
         struct pam inpam;
 
-        getImageInfo(inputFilePattern, 0, file, &inpam);
+        getImageInfo(inputFiles, 0, file, &inpam);
 
-        if (inpam.width < hoverlap)
+        if (inpam.width < inputFiles.hoverlap)
             pm_error("Rank 0, file %u image has width %u, "
                      "which is less than the horizontal overlap of %u pixels",
-                     file, inpam.width, hoverlap);
+                     file, inpam.width, inputFiles.hoverlap);
         else {
             totalWidth += inpam.width;
 
-            if (file < nFile-1)
-                totalWidth -= hoverlap;
+            if (file < inputFiles.nFile-1)
+                totalWidth -= inputFiles.hoverlap;
         }
     }
-    *widthP = totalWidth;
+    *widthP = (int) totalWidth;
 }
 
 
 
 static void
-getOutputHeight(const char *  const inputFilePattern,
-                unsigned int  const nRank,
-                unsigned int  const voverlap,
-                int *         const heightP) {
+getOutputHeight(InputFiles const inputFiles,
+                int *      const heightP) {
 /*----------------------------------------------------------------------------
-   Get the output height by adding up the widths of all 'nRank' images of
-   the left file, and allowing for overlap of 'voverlap' pixels.
+   Get the output height by adding up the widths of all 'inputFiles.nRank'
+   images of the left file, and allowing for overlap of 'inputFiles.voverlap'
+   pixels.
 -----------------------------------------------------------------------------*/
-    unsigned int totalHeight;
+    double       totalHeight;
     unsigned int rank;
 
-    for (rank = 0, totalHeight = 0; rank < nRank; ++rank) {
+    for (rank = 0, totalHeight = 0; rank < inputFiles.nRank; ++rank) {
         struct pam inpam;
 
-        getImageInfo(inputFilePattern, rank, 0, &inpam);
+        getImageInfo(inputFiles, rank, 0, &inpam);
 
-        if (inpam.height < voverlap)
+        if (inpam.height < inputFiles.voverlap)
             pm_error("Rank %u, file 0 image has height %u, "
                      "which is less than the vertical overlap of %u pixels",
-                     rank, inpam.height, voverlap);
-        
+                     rank, inpam.height, inputFiles.voverlap);
+
         totalHeight += inpam.height;
-        
-        if (rank < nRank-1)
-            totalHeight -= voverlap;
+
+        if (rank < inputFiles.nRank-1)
+            totalHeight -= inputFiles.voverlap;
     }
-    *heightP = totalHeight;
+    *heightP = (int) totalHeight;
 }
 
 
 
 static void
-initOutpam(const char * const inputFilePattern,
-           unsigned int const nFile,
-           unsigned int const nRank,
-           unsigned int const hoverlap,
-           unsigned int const voverlap,
+initOutpam(InputFiles   const inputFiles,
            FILE *       const ofP,
            bool         const verbose,
            struct pam * const outpamP) {
@@ -447,10 +578,10 @@ initOutpam(const char * const inputFilePattern,
    *outpamP.
 
    Do this by examining the top rank and left file of the input images,
-   which are in files named by 'inputFilePattern', 'nFile', and 'nRank'.
+   which are in 'inputFiles.list'.
 
-   In computing dimensions, assume 'hoverlap' pixels of horizontal
-   overlap and 'voverlap' pixels of vertical overlap.
+   In computing dimensions, assume 'inputFiles.hoverlap' pixels of horizontal
+   overlap and 'inputFiles.voverlap' pixels of vertical overlap.
 
    We overlook any inconsistencies among the images.  E.g. if two images
    have different depths, we just return one of them.  If two images in
@@ -459,23 +590,23 @@ initOutpam(const char * const inputFilePattern,
    Therefore, Caller must check all the input images to make sure they are
    consistent with the information we return.
 -----------------------------------------------------------------------------*/
-    assert(nFile >= 1);
-    assert(nRank >= 1);
+    assert(inputFiles.nFile >= 1);
+    assert(inputFiles.nRank >= 1);
 
     outpamP->size        = sizeof(*outpamP);
     outpamP->len         = PAM_STRUCT_SIZE(tuple_type);
     outpamP->file        = ofP;
     outpamP->plainformat = 0;
-    
-    getCommonInfo(inputFilePattern, &outpamP->format, &outpamP->depth,
+
+    getCommonInfo(inputFiles, &outpamP->format, &outpamP->depth,
                   &outpamP->maxval, outpamP->tuple_type);
 
-    getOutputWidth(inputFilePattern, nFile, hoverlap, &outpamP->width);
+    getOutputWidth(inputFiles,  &outpamP->width);
 
-    getOutputHeight(inputFilePattern, nRank, voverlap, &outpamP->height);
+    getOutputHeight(inputFiles, &outpamP->height);
 
     if (verbose) {
-        pm_message("Output width = %u pixels", outpamP->width);
+        pm_message("Output width = %u pixels",  outpamP->width);
         pm_message("Output height = %u pixels", outpamP->height);
     }
 }
@@ -485,53 +616,50 @@ initOutpam(const char * const inputFilePattern,
 static void
 openInStreams(struct pam         inpam[],
               unsigned int const rank,
-              unsigned int const fileCount,
-              char         const inputFilePattern[]) {
+              InputFiles   const inputFiles) {
 /*----------------------------------------------------------------------------
    Open the input files for a single horizontal slice (there's one file
    for each vertical slice) and read the Netpbm headers from them.  Return
    the pam structures to describe each as inpam[].
 
-   Open the files for horizontal slice number 'rank', assuming there are
-   'fileCount' vertical slices (so open 'fileCount' files).  Use
-   inputFilePattern[] with each rank and file number to compute the name of
-   each file.
+   Open the files for horizontal slice number 'rank', as described by
+   'inputFiles'.
 -----------------------------------------------------------------------------*/
     unsigned int file;
 
-    for (file = 0; file < fileCount; ++file) {
-        FILE * const ifP = openInputImage(inputFilePattern, rank, file);
+    for (file = 0; file < inputFiles.nFile; ++file) {
+        FILE * const ifP = pm_openr(inputFileName(inputFiles, rank, file));
 
         pnm_readpaminit(ifP, &inpam[file], PAM_STRUCT_SIZE(tuple_type));
-    }        
+    }
 }
 
 
 
 static void
 closeInFiles(struct pam         pam[],
-             unsigned int const fileCount) {
+             unsigned int const fileCt) {
 /*----------------------------------------------------------------------------
-   Close the 'fileCount' input file streams represented by pam[].
+   Close the 'fileCt' input file streams represented by pam[].
 -----------------------------------------------------------------------------*/
-    unsigned int file;
-    
-    for (file = 0; file < fileCount; ++file)
-        pm_close(pam[file].file);
+    unsigned int fileSeq;
+
+    for (fileSeq = 0; fileSeq < fileCt; ++fileSeq)
+        pm_close(pam[fileSeq].file);
 }
 
 
 
 static void
-assembleRow(tuple              outputRow[], 
-            struct pam         inpam[], 
-            unsigned int const fileCount,
+assembleRow(tuple              outputRow[],
+            struct pam         inpam[],
+            unsigned int const fileCt,
             unsigned int const hOverlap) {
 /*----------------------------------------------------------------------------
-   Assemble the row outputRow[] from the 'fileCount' input files
+   Assemble the row outputRow[] from the 'fileCt' input files
    described out inpam[].
 
-   'hOverlap', which is meaningful only when fileCount is greater than 1,
+   'hOverlap', which is meaningful only when 'fileCt' is greater than 1,
    is the amount by which files overlap each other.  We assume every
    input image is at least that wide.
 
@@ -539,19 +667,17 @@ assembleRow(tuple              outputRow[],
    entire assembly.
 -----------------------------------------------------------------------------*/
     tuple * inputRow;
-    unsigned int file;
+    unsigned int fileSeq;
 
-    for (file = 0, inputRow = &outputRow[0]; 
-         file < fileCount; 
-         ++file) {
+    for (fileSeq = 0, inputRow = &outputRow[0]; fileSeq < fileCt; ++fileSeq) {
 
-        unsigned int const overlap = file == fileCount - 1 ? 0 : hOverlap;
+        unsigned int const overlap = fileSeq == fileCt - 1 ? 0 : hOverlap;
 
-        assert(hOverlap <= inpam[file].width);
+        assert(hOverlap <= inpam[fileSeq].width);
 
-        pnm_readpamrow(&inpam[file], inputRow);
+        pnm_readpamrow(&inpam[fileSeq], inputRow);
 
-        inputRow += inpam[file].width - overlap;
+        inputRow += inpam[fileSeq].width - overlap;
     }
 }
 
@@ -574,6 +700,7 @@ allocInpam(unsigned int  const rankCount,
 
 
 
+
 static void
 verifyRankFileAttributes(struct pam *       const inpam,
                          unsigned int       const nFile,
@@ -622,7 +749,7 @@ verifyRankFileAttributes(struct pam *       const inpam,
                      rank, file, inpamP->height, inpam[0].height);
         else {
             totalWidth += inpamP->width;
-        
+
             if (file < nFile-1)
                 totalWidth -= hoverlap;
         }
@@ -637,11 +764,7 @@ verifyRankFileAttributes(struct pam *       const inpam,
 
 static void
 assembleTiles(struct pam * const outpamP,
-              const char * const inputFilePattern,
-              unsigned int const nFile,
-              unsigned int const nRank,
-              unsigned int const hoverlap,
-              unsigned int const voverlap,
+              InputFiles   const inputFiles,
               struct pam         inpam[],
               tuple *      const tuplerow) {
 
@@ -649,12 +772,17 @@ assembleTiles(struct pam * const outpamP,
         /* Number of the current rank (horizontal slice).  Ranks are numbered
            sequentially starting at 0.
         */
-    
+
+    unsigned int const nRank    = inputFiles.nRank;
+    unsigned int const nFile    = inputFiles.nFile;
+    unsigned int const hoverlap = inputFiles.hoverlap;
+    unsigned int const voverlap = inputFiles.voverlap;
+
     for (rank = 0; rank < nRank; ++rank) {
         unsigned int row;
         unsigned int rankHeight;
 
-        openInStreams(inpam, rank, nFile, inputFilePattern);
+        openInStreams(inpam, rank, inputFiles);
 
         verifyRankFileAttributes(inpam, nFile, outpamP, hoverlap, rank);
 
@@ -672,36 +800,47 @@ assembleTiles(struct pam * const outpamP,
 
 
 int
-main(int argc, char ** argv) {
+main(int argc, const char ** argv) {
 
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
+    InputFiles inputFiles;
     struct pam outpam;
     struct pam * inpam;
         /* malloc'ed.  inpam[x] is the pam structure that controls the
-           current rank of file x. 
+           current rank of file x.
         */
     tuple * tuplerow;
 
-    pnm_init(&argc, argv);
-    
+    pm_proginit(&argc, argv);
+
     parseCommandLine(argc, argv, &cmdline);
-        
+
     allocInpam(cmdline.across, &inpam);
 
-    initOutpam(cmdline.inputFilePattern, cmdline.across, cmdline.down,
-               cmdline.hoverlap, cmdline.voverlap, stdout, cmdline.verbose,
-               &outpam);
-    
+    if (cmdline.listfileSpec)
+        createInFileListFmFile(cmdline.listfile,
+                               cmdline.down, cmdline.across,
+                               &inputFiles.list);
+    else
+        createInFileListFmPattern(cmdline.inputFilePattern,
+                                  cmdline.down, cmdline.across,
+                                  &inputFiles.list);
+
+    inputFiles.nFile    = cmdline.across;
+    inputFiles.nRank    = cmdline.down;
+    inputFiles.hoverlap = cmdline.hoverlap;
+    inputFiles.voverlap = cmdline.voverlap;
+
+    initOutpam(inputFiles, stdout, cmdline.verbose, &outpam);
+
     tuplerow = pnm_allocpamrow(&outpam);
 
     pnm_writepaminit(&outpam);
 
-    assembleTiles(&outpam,
-                  cmdline.inputFilePattern, cmdline.across, cmdline.down,
-                  cmdline.hoverlap, cmdline.voverlap, inpam, tuplerow);
+    assembleTiles(&outpam, inputFiles, inpam, tuplerow);
 
     pnm_freepamrow(tuplerow);
-
+    destroyInFileList(inputFiles.list, inputFiles.nRank, inputFiles.nFile);
     free(inpam);
 
     return 0;
diff --git a/editor/pbmclean.c b/editor/pbmclean.c
index 08f410c0..47c775e5 100644
--- a/editor/pbmclean.c
+++ b/editor/pbmclean.c
@@ -381,7 +381,7 @@ cleanSimple(FILE *             const ifP,
    Do the traditional clean where you look only at the immediate neighboring
    pixels of a subject pixel to determine whether to erase that pixel.
 -----------------------------------------------------------------------------*/
-    bit ** buffer;
+    bit ** buffer;  /* one bit per pixel */
         /* The rows of the input relevant to our current processing:
            the current row and the one above and below it.
         */
@@ -399,7 +399,7 @@ cleanSimple(FILE *             const ifP,
     setupInputBuffers(ifP, cols, format, &buffer, &edgeRow,
                       &thisRow, &nextRow);
 
-    outRow = pbm_allocrow(cols);
+    outRow = pbm_allocrow_packed(cols);
 
     pbm_writepbminit(ofP, cols, rows, 0) ;
 
@@ -756,12 +756,12 @@ cleanExtended(FILE *             const ifP,
    We erase (flip) every pixel in every trivial blob.  A trivial blob is
    one with 'trivialSize' pixels or fewer.
 -----------------------------------------------------------------------------*/
-    bit ** pixels;
+    bit ** pixels;    /* one byte per pixel */
     int cols, rows;
 
     pixels = pbm_readpbm(ifP, &cols, &rows);
 
-	cleanPixels(pixels, cols, rows, foregroundColor, trivialSize, nFlippedP);
+        cleanPixels(pixels, cols, rows, foregroundColor, trivialSize, nFlippedP);
 
     pbm_writepbm(ofP, pixels, cols, rows, 0);
 
diff --git a/editor/pbmpscale.c b/editor/pbmpscale.c
index 3b6935b2..434c7965 100644
--- a/editor/pbmpscale.c
+++ b/editor/pbmpscale.c
@@ -12,7 +12,7 @@
 #define LEFTBITS pm_byteLeftBits
 #define RIGHTBITS pm_byteRightBits
 
-/* Table for translating bit pattern into "corners" flag element */ 
+/* Table for translating bit pattern into "corners" flag element */
 
 unsigned char const
 transTable[512] = {
@@ -191,8 +191,8 @@ setFlags(const bit *     const prevrow,
          unsigned char * const flags,
          unsigned int    const cols ) {
 /*----------------------------------------------------------------------------
-   Scan one row, examining the row above and row below, and determine 
-   whether there are "corners" for each pixel.  Feed a 9 bit sample into 
+   Scan one row, examining the row above and row below, and determine
+   whether there are "corners" for each pixel.  Feed a 9 bit sample into
    pre-calculated array transTable[512] to calculate all four corner statuses
    at once.
 
@@ -220,7 +220,7 @@ setFlags(const bit *     const prevrow,
        The high byte of the patterns is a mask, which determines which bits are
        not ignored.
     */
-    uint16_t const patterns[] 
+    uint16_t const patterns[]
         = { 0x0000,   0xd555,            /* no corner */
             0x0001,   0xffc1, 0xd514,    /* normal corner */
             0x0002,   0xd554, 0xd515, 0xbea2, 0xdfc0, 0xfd81, 0xfd80, 0xdf80,
@@ -230,7 +230,7 @@ setFlags(const bit *     const prevrow,
     /*
       For example, the NE corner is examined with the following 8 bit sample:
       Current : W : NW : N : NE : E : SE : S
-      (SW is the "square behind") 
+      (SW is the "square behind")
       */
 #endif
 
@@ -257,7 +257,7 @@ setFlags(const bit *     const prevrow,
         sample = ( ( prevrow24 >> ( 8 -offset) ) & 0x01c0 )
             | ( ( thisrow24 >> (11 -offset) ) & 0x0038 )
             | ( ( nextrow24 >> (14 -offset) ) & 0x0007 );
-        
+
         flags[col] =  transTable[sample];
     }
 }
@@ -275,14 +275,14 @@ expandRow(const bit *     const thisrow,
           int             const ucutoff) {
 /*----------------------------------------------------------------------------
   Process one row, using flags array as reference.  If pixel has no corners
-  output a NxN square of the given color, otherwise output with the 
+  output a NxN square of the given color, otherwise output with the
   specified corner area(s) clipped off.
 -----------------------------------------------------------------------------*/
     unsigned int const outcols = cols * scale;
 
     unsigned int i;
     unsigned int col;
-    
+
     for (i = 0; i < scale; ++i) {
         int const zone = (i > ucutoff) - (i < cutoff);
         int const cut1 =
@@ -297,7 +297,7 @@ expandRow(const bit *     const thisrow,
         cut[1] = cut1;
         cut[2] = cut1 ? cut1 - 1 : 0;
         cut[3] = (cut1 && cutoff > 1) ? cut1 - 1 : cut1;
-      
+
         for (col = 0; col < cols; ++col) {
             unsigned int const col8 = col / 8;
             unsigned int const offset = col % 8;
@@ -309,11 +309,11 @@ expandRow(const bit *     const thisrow,
             if (flag == 0x00) {
                 /* There are no corners, no color change */
                 outcol += scale;
-            } else { 
+            } else {
                 switch (zone) {
                 case -1:
                     if (i==0 && flag == 0xff) {
-                        /* No corners, color changed */ 
+                        /* No corners, color changed */
                         cutl = cutr = 0;
                         flags[col] = 0x00;
                             /* Use above skip procedure next cycle */
@@ -330,7 +330,7 @@ expandRow(const bit *     const thisrow,
                     cutr = cut[SE(flag)];
                     break;
                 }
-                
+
                 if (cutl > 0) {
                     writeBitSpan(outrow, cutl, outcol, !pix);
                     outcol += cutl;
@@ -384,10 +384,10 @@ main(int argc, const char ** argv) {
 
     pbm_readpbminit(ifP, &cols, &rows, &format) ;
 
-    validateComputableDimensions(cols, rows, cmdline.scale); 
+    validateComputableDimensions(cols, rows, cmdline.scale);
 
     outcols = cols * cmdline.scale;
-    outrows = rows * cmdline.scale; 
+    outrows = rows * cmdline.scale;
 
     /* Initialize input buffers.
        We add a margin of 8 bits on the right of the three rows.
@@ -402,7 +402,7 @@ main(int argc, const char ** argv) {
     for (i = 0; i < pbm_packed_bytes(cols + 8); ++i)
         edgerow[i] = 0x00;
 
-    /* Add blank bytes at right edges */ 
+    /* Add blank bytes at right edges */
     for (i = 0; i < 3; ++i)
         buffer[i][pbm_packed_bytes(cols + 8) - 1] = 0x00;
 
diff --git a/editor/pbmreduce.c b/editor/pbmreduce.c
index 3a0968fe..70caa581 100644
--- a/editor/pbmreduce.c
+++ b/editor/pbmreduce.c
@@ -11,9 +11,11 @@
 */
 
 #include "pm_c_util.h"
-#include "pbm.h"
 #include "mallocvar.h"
+#include "rand.h"
 #include "shhopt.h"
+#include "pbm.h"
+
 #include <assert.h>
 
 #define SCALE 1024
@@ -105,7 +107,7 @@ parseCommandLine(int argc, const char ** argv,
         unsigned int scale;
 
         scale = strtol(argv[1], &endptr, 10);
-        if (*argv[1] == '\0') 
+        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'.",
@@ -115,7 +117,7 @@ parseCommandLine(int argc, const char ** argv,
                      "You specified %d", scale);
         else if (scale > INT_MAX / scale)
             pm_error("Scale argument too large.  You specified %d", scale);
-        else 
+        else
             cmdlineP->scale = scale;
 
         if (argc-1 > 1) {
@@ -145,10 +147,11 @@ struct FS {
 static void
 initializeFloydSteinberg(struct FS  * const fsP,
                          int          const newcols,
-                         unsigned int const seed,
-                         bool         const seedSpec) {
+                         bool         const seedSpec,
+                         unsigned int const seed) {
 
     unsigned int col;
+    struct pm_randSt randSt;
 
     MALLOCARRAY(fsP->thiserr, newcols + 2);
     MALLOCARRAY(fsP->nexterr, newcols + 2);
@@ -156,33 +159,36 @@ initializeFloydSteinberg(struct FS  * const fsP,
     if (fsP->thiserr == NULL || fsP->nexterr == NULL)
         pm_error("out of memory");
 
-    srand(seedSpec ? seed : pm_randseed());
+    pm_randinit(&randSt);
+    pm_srand2(&randSt, seedSpec, seed);
 
     for (col = 0; col < newcols + 2; ++col)
-        fsP->thiserr[col] = (rand() % SCALE - HALFSCALE) / 4;
+        fsP->thiserr[col] = ((int) (pm_rand(&randSt) % SCALE) - HALFSCALE) / 4;
         /* (random errors in [-SCALE/8 .. SCALE/8]) */
+
+    pm_randterm(&randSt);
 }
 
 
 
 /*
     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    are ignored; their values do not influence output.
     111222333444xx
     888777666555xx
     888777666555xx
     888777666555xx
     xxxxxxxxxxxxxx
-    
+
     Output (4 x 2):
-    
+
     1234
     8765
 
@@ -196,11 +202,14 @@ enum Direction { RIGHT_TO_LEFT, LEFT_TO_RIGHT };
 static enum Direction
 oppositeDir(enum Direction const arg) {
 
+    enum Direction retval;
+
     switch (arg) {
-    case LEFT_TO_RIGHT: return RIGHT_TO_LEFT;
-    case RIGHT_TO_LEFT: return LEFT_TO_RIGHT;
+    case LEFT_TO_RIGHT: retval = RIGHT_TO_LEFT; break;
+    case RIGHT_TO_LEFT: retval = LEFT_TO_RIGHT; break;
     }
-    assert(false);  /* All cases handled above */
+
+    return retval;
 }
 
 
@@ -240,7 +249,7 @@ main(int argc, const char * argv[]) {
 
     if (cmdline.halftone == QT_FS)
         initializeFloydSteinberg(&fs, newcols,
-                                 cmdline.randomseed, cmdline.randomseedSpec);
+                                 cmdline.randomseedSpec, cmdline.randomseed);
     else {
         /* These variables are meaningless in this case, and the values
            should never be used.
@@ -258,10 +267,10 @@ main(int argc, const char * argv[]) {
         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);
 
@@ -274,7 +283,7 @@ main(int argc, const char * argv[]) {
         case LEFT_TO_RIGHT: {
             startCol = 0;
             limitCol = newcols;
-            step = +1;  
+            step = +1;
         } break;
         case RIGHT_TO_LEFT: {
             startCol = newcols - 1;
diff --git a/editor/pgmmedian.c b/editor/pgmmedian.c
index 4648af68..9cebeac8 100644
--- a/editor/pgmmedian.c
+++ b/editor/pgmmedian.c
@@ -1,4 +1,4 @@
-/* 
+/*
 ** Version 1.0  September 28, 1996
 **
 ** Copyright (C) 1996 by Mike Burns <burns@cac.psu.edu>
@@ -15,16 +15,17 @@
 
 /* References
 ** ----------
-** The select k'th value implementation is based on Algorithm 489 by 
+** The select k'th value implementation is based on Algorithm 489 by
 ** Robert W. Floyd from the "Collected Algorithms from ACM" Volume II.
 **
 ** The histogram sort is based is described in the paper "A Fast Two-
-** Dimensional Median Filtering Algorithm" in "IEEE Transactions on 
+** Dimensional Median Filtering Algorithm" in "IEEE Transactions on
 ** Acoustics, Speech, and Signal Processing" Vol. ASSP-27, No. 1, February
 ** 1979.  The algorithm I more closely followed is found in "Digital
 ** Image Processing Algorithms" by Ioannis Pitas.
 */
 
+#include <assert.h>
 
 #include "pm_c_util.h"
 #include "pgm.h"
@@ -32,10 +33,10 @@
 #include "mallocvar.h"
 #include "nstring.h"
 
-enum medianMethod {MEDIAN_UNSPECIFIED, SELECT_MEDIAN, HISTOGRAM_SORT_MEDIAN};
+enum MedianMethod {MEDIAN_UNSPECIFIED, SELECT_MEDIAN, HISTOGRAM_SORT_MEDIAN};
 #define MAX_MEDIAN_TYPES      2
 
-struct cmdlineInfo {
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
@@ -43,24 +44,28 @@ struct cmdlineInfo {
     unsigned int width;
     unsigned int height;
     unsigned int cutoff;
-    enum medianMethod type;
+    enum MedianMethod type;
 };
 
 
-/* Global variables common to each median sort routine. */
 static int const forceplain = 0;
-static int format;
-static gray maxval;
-static gray **grays;
-static gray *grayrow;
-static int ccolso2, crowso2;
-static int row;
+
+
+
+/* Global variables common to each median sort routine. */
+static gray ** grays;
+    /* The convolution buffer.  This is a circular buffer that contains the
+       rows of the input image that are being convolved into the current 
+       output row.
+    */
+static gray * grayrow;
+    /* A buffer for building the current output row */
 
 
 
 static void
-parseCommandLine(int argc, char ** argv,
-                 struct cmdlineInfo * const cmdlineP) {
+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.
@@ -91,13 +96,21 @@ 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 (!widthSpec)
+    if (widthSpec) {
+        if (cmdlineP->width < 1)
+            pm_error("-width must be at least 1");
+    } else
         cmdlineP->width = 3;
-    if (!heightSpec)
+
+    if (heightSpec) {
+        if (cmdlineP->height < 1)
+            pm_error("-height must be at least 1");
+    } else
         cmdlineP->height = 3;
+
     if (!cutoffSpec)
         cmdlineP->cutoff = 250;
 
@@ -125,6 +138,32 @@ parseCommandLine(int argc, char ** argv,
 
 
 static void
+setWindow(gray **      const convBuffer,
+          unsigned int const crows,
+          gray **      const cgrayrow,
+          unsigned int const lastRow) {
+/*----------------------------------------------------------------------------
+   Set 'cgrayrow' so it points into the circular buffer 'convBuffer' such
+   that cgrayrow[0] is the topmost row in the buffer, given that the
+   bottommost row in the buffer is row number 'lastRow'.
+-----------------------------------------------------------------------------*/
+    unsigned int const windowTopRow = (lastRow + 1) % crows;
+
+    unsigned int bufferRow;
+    unsigned int wrow;
+
+    wrow = 0;
+
+    for (bufferRow = windowTopRow; bufferRow < crows; ++wrow, ++bufferRow)
+        cgrayrow[wrow] = grays[bufferRow];
+
+    for (bufferRow = 0; bufferRow < windowTopRow; ++wrow, ++bufferRow)
+        cgrayrow[wrow] = grays[bufferRow];
+}
+
+
+
+static void
 select489(gray * const a,
           int *  const parray,
           int    const n,
@@ -184,54 +223,53 @@ selectMedian(FILE *       const ifP,
              unsigned int const crows,
              unsigned int const cols,
              unsigned int const rows,
-             unsigned int const median) {
+             int          const format,
+             gray         const maxval,
+             unsigned int const median,
+             unsigned int const firstRow) {
+
+    unsigned int const ccolso2 = ccols / 2;
+    unsigned int const crowso2 = crows / 2;
 
     unsigned int const numValues = crows * ccols;
 
     unsigned int col;
-    gray * garray;
-        /* Array of the currenty gray values */
+    gray * garray;  /* Array of the currently gray values */
     int * parray;
     int * subcol;
-    gray ** rowptr;
-    
+    gray ** cgrayrow;
+    unsigned int row;
+
     garray = pgm_allocrow(numValues);
 
-    MALLOCARRAY(rowptr, crows);
+    MALLOCARRAY(cgrayrow, crows);
     MALLOCARRAY(parray, numValues);
     MALLOCARRAY(subcol, cols);
 
-    if (rowptr == NULL || parray == NULL || subcol == NULL)
+    if (cgrayrow == NULL || parray == NULL || subcol == NULL)
         pm_error("Unable to allocate memory");
 
     for (col = 0; col < cols; ++col)
         subcol[col] = (col - (ccolso2 + 1)) % ccols;
 
     /* Apply median to main part of image. */
-    for ( ; row < rows; ++row) {
-        int crow;
-        int rownum, irow, temprow;
+    for (row = firstRow; row < rows; ++row) {
+        unsigned int crow;
         unsigned int col;
-    
+
         pgm_readpgmrow(ifP, grays[row % crows], cols, maxval, format);
 
-        /* Rotate pointers to rows, so rows can be accessed in order. */
-        temprow = (row + 1) % crows;
-        rownum = 0;
-        for (irow = temprow; irow < crows; ++rownum, ++irow)
-            rowptr[rownum] = grays[irow];
-        for (irow = 0; irow < temprow; ++rownum, ++irow)
-            rowptr[rownum] = grays[irow];
+        setWindow(grays, crows, cgrayrow, row);
 
         for (col = 0; col < cols; ++col) {
             if (col < ccolso2 || col >= cols - ccolso2) {
-                grayrow[col] = rowptr[crowso2][col];
+                grayrow[col] = cgrayrow[crowso2][col];
             } else if (col == ccolso2) {
                 unsigned int const leftcol = col - ccolso2;
                 unsigned int i;
                 i = 0;
                 for (crow = 0; crow < crows; ++crow) {
-                    gray * const temprptr = rowptr[crow] + leftcol;
+                    gray * const temprptr = cgrayrow[crow] + leftcol;
                     unsigned int ccol;
                     for (ccol = 0; ccol < ccols; ++ccol) {
                         garray[i] = *(temprptr + ccol);
@@ -246,23 +284,16 @@ selectMedian(FILE *       const ifP,
                 unsigned int crow;
                 unsigned int tsum;
                 for (crow = 0, tsum = 0; crow < crows; ++crow, tsum += ccols)
-                    garray[tsum + subcol[col]] = *(rowptr[crow] + addcol );
+                    garray[tsum + subcol[col]] = *(cgrayrow[crow] + addcol );
                 select489( garray, parray, numValues, median );
                 grayrow[col] = garray[parray[median]];
             }
         }
         pgm_writepgmrow( stdout, grayrow, cols, maxval, forceplain );
     }
-
-    {
-        unsigned int irow;
-        /* Write out remaining unchanged rows. */
-        for (irow = crowso2 + 1; irow < crows; ++irow)
-            pgm_writepgmrow(stdout, rowptr[irow], cols, maxval, forceplain);
-    }
     free(subcol);
     free(parray);
-    free(rowptr);
+    free(cgrayrow);
     pgm_freerow(garray);
 }
 
@@ -274,31 +305,40 @@ histogramSortMedian(FILE *       const ifP,
                     unsigned int const crows,
                     unsigned int const cols,
                     unsigned int const rows,
-                    unsigned int const median) {
+                    int          const format,
+                    gray         const maxval,
+                    unsigned int const median,
+                    unsigned int const firstRow) {
 
+    unsigned int const ccolso2 = ccols / 2;
+    unsigned int const crowso2 = crows / 2;
     unsigned int const histmax = maxval + 1;
 
     unsigned int * hist;
     unsigned int mdn, ltmdn;
     gray * leftCol;
     gray * rghtCol;
-    gray ** rowptr;
-
-    MALLOCARRAY(rowptr, crows);
+    gray ** cgrayrow;
+        /* The window of the image currently being convolved, with
+           cgrayrow[0] being the top row of the window.  Pointers into grays[]
+        */
+    unsigned int row;
+        /* Row number in input -- bottommost row in the window we're currently
+           convolving
+        */
+
+    MALLOCARRAY(cgrayrow, crows);
     MALLOCARRAY(hist, histmax);
 
-    if (rowptr == NULL || hist == NULL)
+    if (cgrayrow == NULL || hist == NULL)
         pm_error("Unable to allocate memory");
 
     leftCol = pgm_allocrow(crows);
     rghtCol = pgm_allocrow(crows);
 
     /* Apply median to main part of image. */
-    for ( ; row < rows; ++row) {
+    for (row = firstRow; row < rows; ++row) {
         unsigned int col;
-        unsigned int temprow;
-        unsigned int rownum;
-        unsigned int irow;
         unsigned int i;
         /* initialize hist[] */
         for (i = 0; i < histmax; ++i)
@@ -306,24 +346,18 @@ histogramSortMedian(FILE *       const ifP,
 
         pgm_readpgmrow(ifP, grays[row % crows], cols, maxval, format);
 
-        /* Rotate pointers to rows, so rows can be accessed in order. */
-        temprow = (row + 1) % crows;
-        rownum = 0;
-        for (irow = temprow; irow < crows; ++rownum, ++irow)
-            rowptr[rownum] = grays[irow];
-        for (irow = 0; irow < temprow; ++rownum, ++irow)
-            rowptr[rownum] = grays[irow];
+        setWindow(grays, crows, cgrayrow, row);
 
         for (col = 0; col < cols; ++col) {
             if (col < ccolso2 || col >= cols - ccolso2)
-                grayrow[col] = rowptr[crowso2][col];
+                grayrow[col] = cgrayrow[crowso2][col];
             else if (col == ccolso2) {
-                unsigned int crow;
                 unsigned int const leftcol = col - ccolso2;
+                unsigned int crow;
                 i = 0;
                 for (crow = 0; crow < crows; ++crow) {
                     unsigned int ccol;
-                    gray * const temprptr = rowptr[crow] + leftcol;
+                    gray * const temprptr = cgrayrow[crow] + leftcol;
                     for (ccol = 0; ccol < ccols; ++ccol) {
                         gray const g = *(temprptr + ccol);
                         ++hist[g];
@@ -334,17 +368,17 @@ histogramSortMedian(FILE *       const ifP,
                 for (mdn = 0; ltmdn <= median; ++mdn)
                     ltmdn += hist[mdn];
                 --mdn;
-                if (ltmdn > median) 
+                if (ltmdn > median)
                     ltmdn -= hist[mdn];
 
                 grayrow[col] = mdn;
             } else {
-                unsigned int crow;
                 unsigned int const subcol = col - (ccolso2 + 1);
                 unsigned int const addcol = col + ccolso2;
+                unsigned int crow;
                 for (crow = 0; crow < crows; ++crow) {
-                    leftCol[crow] = *(rowptr[crow] + subcol);
-                    rghtCol[crow] = *(rowptr[crow] + addcol);
+                    leftCol[crow] = *(cgrayrow[crow] + subcol);
+                    rghtCol[crow] = *(cgrayrow[crow] + addcol);
                 }
                 for (crow = 0; crow < crows; ++crow) {
                     {
@@ -374,7 +408,7 @@ histogramSortMedian(FILE *       const ifP,
                         ++mdn;
                     }
                     --mdn;
-                    if (ltmdn > median) 
+                    if (ltmdn > median)
                         ltmdn -= hist[mdn];
                 }
                 grayrow[col] = mdn;
@@ -382,84 +416,127 @@ histogramSortMedian(FILE *       const ifP,
         }
         pgm_writepgmrow(stdout, grayrow, cols, maxval, forceplain);
     }
-
-    {
-        /* Write out remaining unchanged rows. */
-        unsigned int irow;
-        for (irow = crowso2 + 1; irow < crows; ++irow)
-            pgm_writepgmrow(stdout, rowptr[irow], cols, maxval, forceplain);
-    }
     pgm_freerow(leftCol);
     pgm_freerow(rghtCol);
     free(hist);
-    free(rowptr);
+    free(cgrayrow);
+}
+
+
+
+static void
+convolve(FILE *            const ifP,
+         unsigned int      const cols,
+         unsigned int      const rows,
+         gray              const maxval,
+         int               const format,
+         unsigned int      const ccols,
+         unsigned int      const crows,
+         enum MedianMethod const medianMethod,
+         unsigned int      const median) {
+
+    unsigned int const crowso2 = crows / 2;
+
+    unsigned int row;
+
+    /* An even-size convolution window is biased toward the top and left.  So
+       if it is 8 rows, the window covers 4 rows above the target row and 3
+       rows below it, plus the target row itself.  'crowso2' is the number of
+       the target row within the window.  There are always 'crowso2' rows
+       above it and either crowso2 or crowso2-1 rows below it.
+    */
+
+    /* Allocate space for number of rows in mask size. */
+    grays = pgm_allocarray(cols, crows);
+    grayrow = pgm_allocrow(cols);
+
+    /* Prime the convolution window -- fill it except the last row */
+    for (row = 0; row < crows - 1; ++row)
+        pgm_readpgmrow(ifP, grays[row], cols, maxval, format);
+
+    /* Copy the top half out verbatim, since convolution kernel for these rows
+       runs off the top of the image.
+    */
+    for (row = 0; row < crowso2; ++row)
+        pgm_writepgmrow(stdout, grays[row], cols, maxval, forceplain);
+
+    switch (medianMethod) {
+    case SELECT_MEDIAN:
+        selectMedian(ifP, ccols, crows, cols, rows, format, maxval,
+                     median, crows-1);
+        break;
+
+    case HISTOGRAM_SORT_MEDIAN:
+        histogramSortMedian(ifP, ccols, crows, cols, rows, format, maxval,
+                            median, crows-1);
+        break;
+    case MEDIAN_UNSPECIFIED:
+        pm_error("INTERNAL ERROR: median unspecified");
+    }
+
+    /* Copy the bottom half of the remaining convolution window verbatim,
+       since convolution kernel for these rows runs off the bottom of the
+       image.
+    */
+    assert(crows >= crowso2 + 1);
+
+    for (row = rows - (crows-crowso2-1); row < rows; ++row)
+        pgm_writepgmrow(stdout, grays[row % crows], cols, maxval,
+                        forceplain);
+
+    pgm_freearray(grays, crows);
+    pgm_freerow(grayrow);
 }
 
 
 
 int
-main(int    argc,
-     char * argv[]) {
+main(int          argc,
+     const char * argv[]) {
 
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
     FILE * ifP;
     int cols, rows;
-    int median;
-    enum medianMethod medianMethod;
+    int format;
+    gray maxval;
+    unsigned int ccols, crows;
+    unsigned int median;
+    enum MedianMethod medianMethod;
 
-    pgm_init(&argc, argv);
+    pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
-    
+
     ifP = pm_openr(cmdline.inputFileName);
 
-    ccolso2 = cmdline.width / 2;
-    crowso2 = cmdline.height / 2;
+    assert(cmdline.height > 0 && cmdline.width > 0);
 
     pgm_readpgminit(ifP, &cols, &rows, &maxval, &format);
-    pgm_writepgminit(stdout, cols, rows, maxval, forceplain);
 
-    /* Allocate space for number of rows in mask size. */
-    grays = pgm_allocarray(cols, cmdline.height);
-    grayrow = pgm_allocrow(cols);
+    ccols = MIN(cmdline.width,  cols+1);
+    crows = MIN(cmdline.height, rows+1);
 
-    /* Read in and write out initial rows that won't get changed. */
-    for (row = 0; row < cmdline.height - 1; ++row) {
-        pgm_readpgmrow(ifP, grays[row], cols, maxval, format);
-        /* Write out the unchanged row. */
-        if (row < crowso2)
-            pgm_writepgmrow(stdout, grays[row], cols, maxval, forceplain);
-    }
+    pgm_writepgminit(stdout, cols, rows, maxval, forceplain);
 
-    median = (cmdline.height * cmdline.width) / 2;
+    median = (crows * ccols) / 2;
 
     /* Choose which sort to run. */
     if (cmdline.type == MEDIAN_UNSPECIFIED) {
-        if ((maxval / ((cmdline.width * cmdline.height) - 1)) < cmdline.cutoff)
+        if ((maxval / ((ccols * crows) - 1)) < cmdline.cutoff)
             medianMethod = HISTOGRAM_SORT_MEDIAN;
         else
             medianMethod = SELECT_MEDIAN;
     } else
         medianMethod = cmdline.type;
 
-    switch (medianMethod) {
-    case SELECT_MEDIAN:
-        selectMedian(ifP, cmdline.width, cmdline.height, cols, rows, median);
-        break;
-        
-    case HISTOGRAM_SORT_MEDIAN:
-        histogramSortMedian(ifP, cmdline.width, cmdline.height,
-                            cols, rows, median);
-        break;
-    case MEDIAN_UNSPECIFIED:
-        pm_error("INTERNAL ERROR: median unspecified");
-    }
-    
+
+    convolve(ifP, cols, rows, maxval, format, ccols, crows, medianMethod,
+             median);
+
     pm_close(ifP);
     pm_close(stdout);
 
-    pgm_freearray(grays, cmdline.height);
-    pgm_freerow(grayrow);
-
     return 0;
 }
+
+
diff --git a/editor/pnmcat.c b/editor/pnmcat.c
deleted file mode 100644
index 217f6b57..00000000
--- a/editor/pnmcat.c
+++ /dev/null
@@ -1,872 +0,0 @@
-/* pnmcat.c - concatenate PNM images
-**
-** 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.
-*/
-
-#include <assert.h>
-
-#include "pm_c_util.h"
-#include "mallocvar.h"
-#include "shhopt.h"
-#include "bitarith.h"
-#include "nstring.h"
-#include "pnm.h"
-
-#define LEFTBITS pm_byteLeftBits
-#define RIGHTBITS pm_byteRightBits
-
-enum backcolor {BACK_WHITE, BACK_BLACK, BACK_AUTO};
-
-enum orientation {TOPBOTTOM, LEFTRIGHT};
-
-enum justification {JUST_CENTER, JUST_MIN, JUST_MAX};
-
-struct imgInfo {
-    /* This obviously should be a struct pam.  We should convert this
-       to 'pamcat'.
-    */
-    FILE * ifP;
-    int    cols;
-    int    rows;
-    int    format;
-    xelval maxval;
-};
-
-
-
-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 nfiles;
-    enum backcolor backcolor;
-    enum orientation orientation;
-    enum justification justification;
-};
-
-
-
-static void
-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.
------------------------------------------------------------------------------*/
-    optEntry * option_def;
-        /* Instructions to OptParseOptions3() on how to parse our options.
-         */
-    optStruct3 opt;
-
-    unsigned int option_def_index;
-
-    unsigned int leftright, topbottom, black, white, jtop, jbottom,
-        jleft, jright, jcenter;
-
-    MALLOCARRAY_NOFAIL(option_def, 100);
-
-    option_def_index = 0;   /* incremented by OPTENTRY */
-    OPTENT3(0, "leftright",  OPT_FLAG,   NULL, &leftright,   0);
-    OPTENT3(0, "lr",         OPT_FLAG,   NULL, &leftright,   0);
-    OPTENT3(0, "topbottom",  OPT_FLAG,   NULL, &topbottom,   0);
-    OPTENT3(0, "tb",         OPT_FLAG,   NULL, &topbottom,   0);
-    OPTENT3(0, "black",      OPT_FLAG,   NULL, &black,       0);
-    OPTENT3(0, "white",      OPT_FLAG,   NULL, &white,       0);
-    OPTENT3(0, "jtop",       OPT_FLAG,   NULL, &jtop,        0);
-    OPTENT3(0, "jbottom",    OPT_FLAG,   NULL, &jbottom,     0);
-    OPTENT3(0, "jleft",      OPT_FLAG,   NULL, &jleft,       0);
-    OPTENT3(0, "jright",     OPT_FLAG,   NULL, &jright,      0);
-    OPTENT3(0, "jcenter",    OPT_FLAG,   NULL, &jcenter,     0);
-
-    opt.opt_table = option_def;
-    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
-    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
-
-    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
-        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
-
-    free(option_def);
-
-    if (leftright + topbottom > 1)
-        pm_error("You may specify only one of -topbottom (-tb) and "
-                 "-leftright (-lr)");
-    else if (leftright)
-        cmdlineP->orientation = LEFTRIGHT;
-    else if (topbottom)
-        cmdlineP->orientation = TOPBOTTOM;
-    else
-        pm_error("You must specify either -leftright or -topbottom");
-
-    if (black + white > 1)
-        pm_error("You may specify only one of -black and -white");
-    else if (black)
-        cmdlineP->backcolor = BACK_BLACK;
-    else if (white)
-        cmdlineP->backcolor = BACK_WHITE;
-    else
-        cmdlineP->backcolor = BACK_AUTO;
-
-    if (jtop + jbottom + jleft + jright + jcenter > 1)
-        pm_error("You may specify onlyone of -jtop, -jbottom, "
-                 "-jleft, and -jright");
-    else {
-        switch (cmdlineP->orientation) {
-        case LEFTRIGHT:
-            if (jleft)
-                pm_error("-jleft is invalid with -leftright");
-            if (jright)
-                pm_error("-jright is invalid with -leftright");
-            if (jtop)
-                cmdlineP->justification = JUST_MIN;
-            else if (jbottom)
-                cmdlineP->justification = JUST_MAX;
-            else if (jcenter)
-                cmdlineP->justification = JUST_CENTER;
-            else
-                cmdlineP->justification = JUST_CENTER;
-            break;
-        case TOPBOTTOM:
-            if (jtop)
-                pm_error("-jtop is invalid with -topbottom");
-            if (jbottom)
-                pm_error("-jbottom is invalid with -topbottom");
-            if (jleft)
-                cmdlineP->justification = JUST_MIN;
-            else if (jright)
-                cmdlineP->justification = JUST_MAX;
-            else if (jcenter)
-                cmdlineP->justification = JUST_CENTER;
-            else
-                cmdlineP->justification = JUST_CENTER;
-            break;
-        }
-    }
-
-    if (argc-1 < 1) {
-        MALLOCARRAY_NOFAIL(cmdlineP->inputFilespec, 1);
-        cmdlineP->inputFilespec[0] = "-";
-        cmdlineP->nfiles = 1;
-    } else {
-        unsigned int i;
-        unsigned int stdinCt;
-            /* Number of input files user specified as Standard Input */
-
-        MALLOCARRAY_NOFAIL(cmdlineP->inputFilespec, argc-1);
-
-        for (i = 0, stdinCt = 0; i < argc-1; ++i) {
-            cmdlineP->inputFilespec[i] = argv[1+i];
-            if (streq(argv[1+i], "-"))
-                ++stdinCt;
-        }
-        cmdlineP->nfiles = argc-1;
-        if (stdinCt > 1)
-            pm_error("At most one input image can come from Standard Input.  "
-                     "You specified %u", stdinCt);
-    }
-}
-
-
-
-static void
-computeOutputParms(unsigned int     const nfiles,
-                   enum orientation const orientation,
-                   struct imgInfo   const img[],
-                   unsigned int *   const newcolsP,
-                   unsigned int *   const newrowsP,
-                   xelval *         const newmaxvalP,
-                   int *            const newformatP) {
-
-    double newcols, newrows;
-    int newformat;
-    xelval newmaxval;
-
-    unsigned int i;
-
-    newcols = 0;
-    newrows = 0;
-
-    for (i = 0; i < nfiles; ++i) {
-        const struct imgInfo * const imgP = &img[i];
-
-        if (i == 0) {
-            newmaxval = imgP->maxval;
-            newformat = imgP->format;
-        } else {
-            if (PNM_FORMAT_TYPE(imgP->format) > PNM_FORMAT_TYPE(newformat))
-                newformat = imgP->format;
-            if (imgP->maxval > newmaxval)
-                newmaxval = imgP->maxval;
-        }
-        switch (orientation) {
-        case LEFTRIGHT:
-            newcols += imgP->cols;
-            if (imgP->rows > newrows)
-                newrows = imgP->rows;
-            break;
-        case TOPBOTTOM:
-            newrows += imgP->rows;
-            if (imgP->cols > newcols)
-                newcols = imgP->cols;
-            break;
-        }
-    }
-
-    /* Note that while 'double' is not in general a precise numerical type,
-       in the case of a sum of integers which is less than INT_MAX, it
-       is exact, because double's precision is greater than int's.
-    */
-    if (newcols > INT_MAX)
-       pm_error("Output width too large: %.0f.", newcols);
-    if (newrows > INT_MAX)
-       pm_error("Output height too large: %.0f.", newrows);
-
-    *newrowsP   = (unsigned int)newrows;
-    *newcolsP   = (unsigned int)newcols;
-    *newmaxvalP = newmaxval;
-    *newformatP = newformat;
-}
-
-
-
-static void
-copyBitrow(const unsigned char * const source,
-           unsigned char *       const destBitrow,
-           unsigned int          const cols,
-           unsigned int          const offset) {
-/*----------------------------------------------------------------------------
-  Copy from source to destBitrow, without shifting.  Preserve
-  surrounding image data.
------------------------------------------------------------------------------*/
-    unsigned char * const dest = & destBitrow[ offset/8 ];
-        /* Copy destination, with leading full bytes ignored. */
-    unsigned int const rs = offset % 8;
-        /* The "little offset", as measured from start of dest.  Source
-           is already shifted by this value.
-        */
-    unsigned int const trs = (cols + rs) % 8;
-        /* The number of partial bits in the final char. */
-    unsigned int const colByteCnt = pbm_packed_bytes(cols + rs);
-        /* # bytes to process, including partial ones on both ends. */
-    unsigned int const last = colByteCnt - 1;
-
-    unsigned char const origHead = dest[0];
-    unsigned char const origEnd  = dest[last];
-
-    unsigned int i;
-
-    assert(colByteCnt >= 1);
-
-    for (i = 0; i < colByteCnt; ++i)
-        dest[i] = source[i];
-
-    if (rs > 0)
-        dest[0] = LEFTBITS(origHead, rs) | RIGHTBITS(dest[0], 8-rs);
-
-    if (trs > 0)
-        dest[last] = LEFTBITS(dest[last], trs) | RIGHTBITS(origEnd, 8-trs);
-}
-
-
-
-static void
-padFillBitrow(unsigned char * const destBitrow,
-              unsigned char   const padColor,
-              unsigned int    const cols,
-              unsigned int    const offset) {
-/*----------------------------------------------------------------------------
-   Fill destBitrow, starting at offset, with padColor.  padColor is a
-   byte -- 0x00 or 0xff -- not a single bit.
------------------------------------------------------------------------------*/
-    unsigned char * const dest = &destBitrow[offset/8];
-    unsigned int const rs = offset % 8;
-    unsigned int const trs = (cols + rs) % 8;
-    unsigned int const colByteCnt = pbm_packed_bytes(cols + rs);
-    unsigned int const last = colByteCnt - 1;
-
-    unsigned char const origHead = dest[0];
-    unsigned char const origEnd  = dest[last];
-
-    unsigned int i;
-
-    assert(colByteCnt > 0);
-
-    for (i = 0; i < colByteCnt; ++i)
-        dest[i] = padColor;
-
-    if (rs > 0)
-        dest[0] = LEFTBITS(origHead, rs) | RIGHTBITS(dest[0], 8-rs);
-
-    if (trs > 0)
-        dest[last] = LEFTBITS(dest[last], trs) | RIGHTBITS(origEnd, 8-trs);
-}
-
-
-
-/* concatenateLeftRightPBM() and concatenateLeftRightGen()
-   employ almost identical algorithms.
-   The difference is in the data types and functions.
-
-   Same for concatenateTopBottomPBM() and concatenateTopBottomGen().
-*/
-
-
-struct imgInfoPbm2 {
-    /* Information about one image */
-    unsigned char * proberow;
-        /* Top row of image, when background color is
-           auto-determined.
-        */
-    unsigned int offset;
-        /* start position of image, in bits, counting from left
-           edge
-        */
-    unsigned char background;
-        /* Background color.  0x00 means white; 0xff means black */
-    unsigned int padtop;
-        /* Top padding amount */
-};
-
-
-
-static void
-getPbmImageInfo(struct imgInfo        const img[],
-                unsigned int          const nfiles,
-                unsigned int          const newrows,
-                enum justification    const justification,
-                enum backcolor        const backcolor,
-                struct imgInfoPbm2 ** const img2P) {
-/*----------------------------------------------------------------------------
-   Read the first row of each image in img[] and return that and additional
-   information about images as *img2P.
------------------------------------------------------------------------------*/
-    struct imgInfoPbm2 * img2;
-    unsigned int i;
-
-    MALLOCARRAY_NOFAIL(img2, nfiles);
-
-    for (i = 0; i < nfiles; ++i) {
-        switch (justification) {
-        case JUST_MIN:    img2[i].padtop = 0;                           break;
-        case JUST_MAX:    img2[i].padtop = newrows - img[i].rows;       break;
-        case JUST_CENTER: img2[i].padtop = (newrows - img[i].rows) / 2; break;
-        }
-        
-        img2[i].offset = (i == 0) ? 0 : img2[i-1].offset + img[i-1].cols;
-        
-        if (img[i].rows == newrows)  /* no padding */
-            img2[i].proberow = NULL;
-        else {                   /* determine pad color for image i */
-            switch (backcolor) {
-            case BACK_AUTO: {
-                bit bgBit;
-                img2[i].proberow =
-                    pbm_allocrow_packed((unsigned int)img[i].cols + 7);
-                pbm_readpbmrow_bitoffset(
-                    img[i].ifP, img2[i].proberow,
-                    img[i].cols, img[i].format, img2[i].offset % 8);
-
-                bgBit = pbm_backgroundbitrow(
-                    img2[i].proberow, img[i].cols, img2[i].offset % 8);
-
-                img2[i].background = bgBit == PBM_BLACK ? 0xff : 0x00;
-            } break;
-            case BACK_BLACK:
-                img2[i].proberow   = NULL;
-                img2[i].background = 0xff;
-                break;
-            case BACK_WHITE:
-                img2[i].proberow   = NULL;
-                img2[i].background = 0x00;
-                break;
-            }
-        }
-    }
-    *img2P = img2;
-}
-
-
-
-static void
-destroyPbmImg2(struct imgInfoPbm2 * const img2,
-               unsigned int         const nfiles) {
-
-    unsigned int i;
-
-    for (i = 0; i < nfiles; ++i) {
-        if (img2[i].proberow)
-            free(img2[i].proberow);
-    }
-    free(img2);
-}
-
-
-
-static void
-concatenateLeftRightPbm(FILE *             const ofP,
-                        unsigned int       const nfiles,
-                        unsigned int       const newcols,
-                        unsigned int       const newrows,
-                        enum justification const justification,
-                        struct imgInfo     const img[],   
-                        enum backcolor     const backcolor) {
-
-    unsigned char * const outrow = pbm_allocrow_packed(newcols);
-        /* We use just one outrow.  All padding and image data (with the
-           exeption of following img2.proberow) goes directly into this
-           packed PBM row. 
-        */
-
-    struct imgInfoPbm2 * img2;
-        /* malloc'ed array, one element per image.  Shadows img[] */
-    unsigned int row;
-
-    getPbmImageInfo(img, nfiles, newrows, justification, backcolor, &img2);
-
-    outrow[pbm_packed_bytes(newcols)-1] = 0x00;
-
-    for (row = 0; row < newrows; ++row) {
-        unsigned int i;
-
-        for (i = 0; i < nfiles; ++i) {
-
-            if ((row == 0 && img2[i].padtop > 0) ||
-                row == img2[i].padtop + img[i].rows) {
-
-                /* This row begins a run of padding, either above or below
-                   file 'i', so set 'outrow' to padding.
-                */
-                padFillBitrow(outrow, img2[i].background, img[i].cols,
-                              img2[i].offset);
-            }
-
-            if (row == img2[i].padtop && img2[i].proberow != NULL) {
-                /* Top row has been read to proberow[] to determine
-                   background.  Copy it to outrow[].
-                */
-                copyBitrow(img2[i].proberow, outrow,
-                           img[i].cols, img2[i].offset);
-            } else if (row >= img2[i].padtop &&
-                       row < img2[i].padtop + img[i].rows) {
-                pbm_readpbmrow_bitoffset(
-                    img[i].ifP, outrow, img[i].cols, img[i].format,
-                    img2[i].offset);
-            } else {
-                /* It's a row of padding, so outrow[] is already set
-                   appropriately.
-                */
-            }
-        }
-        pbm_writepbmrow_packed(ofP, outrow, newcols, 0);
-    }
-
-    destroyPbmImg2(img2, nfiles);
-
-    pbm_freerow_packed(outrow);
-}
-
-
-
-static void
-concatenateTopBottomPbm(FILE *             const ofP,
-                        unsigned int       const nfiles,
-                        int                const newcols,
-                        int                const newrows,
-                        enum justification const justification,
-                        struct imgInfo     const img[],
-                        enum backcolor     const backcolor) {
-
-    unsigned char * const outrow = pbm_allocrow_packed(newcols);
-        /* Like the left-right PBM case, all padding and image data
-           goes directly into outrow.  There is no proberow.
-        */
-    unsigned char background, backgroundPrev;
-        /* 0x00 means white; 0xff means black */
-    unsigned int  padleft;
-    bool          backChange;
-        /* Background color is different from that of the previous
-           input image.
-        */
-
-    unsigned int i;
-    unsigned int row, startRow;
-    
-    outrow[pbm_packed_bytes(newcols)-1] = 0x00;
-
-    switch (backcolor){
-    case BACK_AUTO:   /* do nothing */    break;
-    case BACK_BLACK:  background = 0xff;  break;
-    case BACK_WHITE:  background = 0x00;  break;
-    }
-
-    for (i = 0; i < nfiles; ++i) {
-        if (img[i].cols == newcols) {
-            /* No padding */
-            startRow = 0;
-            backChange = FALSE;
-            padleft = 0;
-            outrow[pbm_packed_bytes(newcols)-1] = 0x00;
-        } else {
-            /* Determine amount of padding and color */
-            switch (justification) {
-            case JUST_MIN:     padleft = 0;                           break;
-            case JUST_MAX:     padleft = newcols - img[i].cols;       break;
-            case JUST_CENTER:  padleft = (newcols - img[i].cols) / 2; break;
-            }
-
-            switch (backcolor) {
-            case BACK_AUTO: {
-                bit bgBit;
-
-                startRow = 1;
-                
-                pbm_readpbmrow_bitoffset(img[i].ifP,
-                                         outrow, img[i].cols, img[i].format,
-                                         padleft);
-
-                bgBit = pbm_backgroundbitrow(outrow, img[i].cols, padleft);
-                background = bgBit == PBM_BLACK ? 0xff : 0x00;
-
-                backChange = (i == 0 || background != backgroundPrev);
-            } break;
-            case BACK_WHITE:
-            case BACK_BLACK:
-                startRow = 0;
-                backChange = (i==0);
-                break;
-            }
-
-            if (backChange || (i > 0 && img[i-1].cols > img[i].cols)) {
-                unsigned int const padright = newcols - padleft - img[i].cols;
-                
-                if (padleft > 0)
-                    padFillBitrow(outrow, background, padleft, 0);
-                
-                if (padright > 0)            
-                    padFillBitrow(outrow, background, padright,
-                                  padleft + img[i].cols);
-                
-            }
-        }
-            
-        if (startRow == 1)
-            /* Top row already read for auto background color
-               determination.  Write it out.
-            */
-            pbm_writepbmrow_packed(ofP, outrow, newcols, 0);
-        
-        for (row = startRow; row < img[i].rows; ++row) {
-            pbm_readpbmrow_bitoffset(img[i].ifP, outrow, img[i].cols,
-                                     img[i].format, padleft);
-            pbm_writepbmrow_packed(ofP, outrow, newcols, 0);
-        }
-
-        backgroundPrev = background;
-    }
-    pbm_freerow_packed(outrow);
-}
-
-
-
-struct imgGen2 {
-    xel * xelrow;
-    xel * inrow;
-    xel   background;
-    int   padtop;
-};
-
-
-
-static void
-getGenImgInfo(struct imgInfo     const img[],
-              unsigned int       const nfiles,
-              xel *              const newxelrow,
-              unsigned int       const newrows,
-              xelval             const newmaxval,
-              int                const newformat,
-              enum justification const justification,
-              enum backcolor     const backcolor,
-              struct imgGen2 **  const img2P) {
-
-    struct imgGen2 * img2;
-    unsigned int i;
-
-    MALLOCARRAY_NOFAIL(img2, nfiles);
-
-    for (i = 0; i < nfiles; ++i) {
-        switch (justification) {  /* Determine top padding */
-            case JUST_MIN:
-                img2[i].padtop = 0;
-                break;
-            case JUST_MAX:
-                img2[i].padtop = newrows - img[i].rows;
-                break;
-            case JUST_CENTER:
-                img2[i].padtop = (newrows - img[i].rows) / 2;
-                break;
-        }
-
-        img2[i].inrow =
-            (i == 0 ? &newxelrow[0] : img2[i-1].inrow + img[i-1].cols);
-
-        if (img[i].rows == newrows)  /* no padding */
-            img2[i].xelrow = NULL;
-        else {
-            /* Determine pad color */
-            switch (backcolor){
-            case BACK_AUTO:
-                img2[i].xelrow = pnm_allocrow(img[i].cols);
-                pnm_readpnmrow(img[i].ifP, img2[i].xelrow,
-                               img[i].cols, img[i].maxval, img[i].format);
-                pnm_promoteformatrow(img2[i].xelrow, img[i].cols,
-                                     img[i].maxval, img[i].format,
-                                     newmaxval, newformat);
-                img2[i].background = pnm_backgroundxelrow(
-                    img2[i].xelrow, img[i].cols, newmaxval, newformat);
-                break;
-            case BACK_BLACK:
-                img2[i].xelrow = NULL;
-                img2[i].background = pnm_blackxel(newmaxval, newformat);
-                break;
-            case BACK_WHITE:
-                img2[i].xelrow = NULL;
-                img2[i].background = pnm_whitexel(newmaxval, newformat);
-                break;
-            }
-        }
-    }
-    *img2P = img2;
-}
-
-
-
-static void
-concatenateLeftRightGen(FILE *             const ofP,
-                        unsigned int       const nfiles,
-                        unsigned int       const newcols,
-                        unsigned int       const newrows,
-                        xelval             const newmaxval,
-                        int                const newformat,
-                        enum justification const justification,
-                        struct imgInfo     const img[],
-                        enum backcolor     const backcolor) {
-
-    xel * const outrow = pnm_allocrow(newcols);
-    struct imgGen2 * img2;
-    unsigned int row;
-
-    getGenImgInfo(img, nfiles, outrow, newrows,
-                  newmaxval, newformat, justification, backcolor, &img2);
-
-    for (row = 0; row < newrows; ++row) {
-        unsigned int i;
-
-        for (i = 0; i < nfiles; ++i) {
-            if ((row == 0 && img2[i].padtop > 0) ||
-                row == img2[i].padtop + img[i].rows) {
-                /* This row begins a run of padding, either above or below
-                   file 'i', so set 'outrow' to padding.
-                */
-                unsigned int col;
-                for (col = 0; col < img[i].cols; ++col)
-                    img2[i].inrow[col] = img2[i].background;
-            }
-            if (row == img2[i].padtop && img2[i].xelrow) {
-                /* We're at the top row of file 'i', and that row
-                   has already been read to xelrow[] to determine
-                   background.  Copy it to 'outrow'.
-                */
-                unsigned int col;
-                for (col = 0; col < img[i].cols; ++col)
-                    img2[i].inrow[col] = img2[i].xelrow[col];
-                
-                free(img2[i].xelrow);
-            } else if (row >= img2[i].padtop &&
-                       row < img2[i].padtop + img[i].rows) {
-                pnm_readpnmrow(
-                    img[i].ifP, img2[i].inrow, img[i].cols, img[i].maxval,
-                    img[i].format);
-                pnm_promoteformatrow(
-                    img2[i].inrow, img[i].cols, img[i].maxval,
-                    img[i].format, newmaxval, newformat);
-            } else {
-                /* It's a row of padding, so outrow[] is already set
-                   appropriately.
-                */
-            }
-        }
-        pnm_writepnmrow(ofP, outrow, newcols, newmaxval, newformat, 0);
-    }
-    pnm_freerow(outrow);
-}
-
-
-
-static void
-concatenateTopBottomGen(FILE *             const ofP,
-                        unsigned int       const nfiles,
-                        int                const newcols,
-                        int                const newrows,
-                        xelval             const newmaxval,
-                        int                const newformat,
-                        enum justification const justification,
-                        struct imgInfo     const img[],
-                        enum backcolor     const backcolor) {
-
-    xel * const newxelrow = pnm_allocrow(newcols);
-    xel * inrow;
-    unsigned int padleft;
-    unsigned int i;
-    unsigned int row, startRow;
-    xel background, backgroundPrev;
-    bool backChange;
-        /* The background color is different from that of the previous
-           input image.
-        */
-
-    switch (backcolor) {
-    case BACK_AUTO: /* do nothing now, determine at start of each image */
-                     break;
-    case BACK_BLACK: background = pnm_blackxel(newmaxval, newformat);
-                     break;
-    case BACK_WHITE: background = pnm_whitexel(newmaxval, newformat);
-                     break;
-    }
-
-    for ( i = 0; i < nfiles; ++i, backgroundPrev = background) {
-        if (img[i].cols == newcols) {
-            /* no padding */
-            startRow = 0;
-            backChange = FALSE;
-            inrow = newxelrow;
-        } else { /* Calculate left padding amount */ 
-            switch (justification) {
-            case JUST_MIN:    padleft = 0;                            break;
-            case JUST_MAX:    padleft = newcols - img[i].cols;        break;
-            case JUST_CENTER: padleft = (newcols - img[i].cols) / 2;  break;
-            }
-
-            if (backcolor == BACK_AUTO) {
-                /* Determine background color */
-
-                startRow = 1;
-                inrow = &newxelrow[padleft];
-
-                pnm_readpnmrow(img[i].ifP, inrow,
-                               img[i].cols, img[i].maxval, img[i].format);
-                pnm_promoteformatrow(inrow, img[i].cols, img[i].maxval,
-                                     img[i].format,
-                                     newmaxval, newformat);
-                background = pnm_backgroundxelrow(
-                    inrow, img[i].cols, newmaxval, newformat);
-
-                backChange = i==0 || !PNM_EQUAL(background, backgroundPrev);
-            } else {
-                /* background color is constant: black or white */
-                startRow = 0;
-                inrow = &newxelrow[padleft];
-                backChange = (i==0);
-            }
-
-            if (backChange || (i > 0 && img[i-1].cols > img[i].cols)) {
-                unsigned int col;
-
-                for (col = 0; col < padleft; ++col)
-                    newxelrow[col] = background;
-                for (col = padleft + img[i].cols; col < newcols; ++col)
-                    newxelrow[col] = background;
-            }
-        }
-
-        if (startRow == 1)
-            /* Top row already read for auto background
-               color determination.  Write it out. */
-            pnm_writepnmrow(ofP, newxelrow, newcols, newmaxval, newformat, 0);
-
-        for (row = startRow; row < img[i].rows; ++row) {
-            pnm_readpnmrow(img[i].ifP,
-                           inrow, img[i].cols, img[i].maxval, img[i].format);
-            pnm_promoteformatrow(
-                inrow, img[i].cols, img[i].maxval, img[i].format,
-                newmaxval, newformat);
-
-            pnm_writepnmrow(ofP, newxelrow, newcols, newmaxval, newformat, 0);
-        }
-    }
-    pnm_freerow(newxelrow);
-}
-
-
-
-int
-main(int           argc,
-     const char ** argv) {
-
-    struct cmdlineInfo cmdline;
-    struct imgInfo * img;  /* malloc'ed array */
-    xelval newmaxval;
-    int newformat;
-    unsigned int i;
-    unsigned int newrows, newcols;
-
-    pm_proginit(&argc, argv);
-
-    parseCommandLine(argc, argv, &cmdline);
-
-    MALLOCARRAY_NOFAIL(img, cmdline.nfiles);
-
-    for (i = 0; i < cmdline.nfiles; ++i) {
-        img[i].ifP = pm_openr(cmdline.inputFilespec[i]);
-        pnm_readpnminit(img[i].ifP, &img[i].cols, &img[i].rows,
-                        &img[i].maxval, &img[i].format);
-    }
-
-    computeOutputParms(cmdline.nfiles, cmdline.orientation, img,
-                       &newcols, &newrows, &newmaxval, &newformat);
-
-    pnm_writepnminit(stdout, newcols, newrows, newmaxval, newformat, 0);
-
-    if (PNM_FORMAT_TYPE(newformat) == PBM_TYPE) {
-        switch (cmdline.orientation) {
-        case LEFTRIGHT:
-            concatenateLeftRightPbm(stdout, cmdline.nfiles,
-                                    newcols, newrows, cmdline.justification,
-                                    img, cmdline.backcolor);
-            break;
-        case TOPBOTTOM:
-            concatenateTopBottomPbm(stdout, cmdline.nfiles,
-                                    newcols, newrows, cmdline.justification,
-                                    img, cmdline.backcolor);
-            break;
-        }
-    } else {
-        switch (cmdline.orientation) {
-        case LEFTRIGHT:
-            concatenateLeftRightGen(stdout, cmdline.nfiles,
-                                    newcols, newrows, newmaxval, newformat,
-                                    cmdline.justification, img,
-                                    cmdline.backcolor);
-            break;
-        case TOPBOTTOM:
-            concatenateTopBottomGen(stdout, cmdline.nfiles,
-                                    newcols, newrows, newmaxval, newformat,
-                                    cmdline.justification, img,
-                                    cmdline.backcolor);
-            break;
-        }
-    }
-    for (i = 0; i < cmdline.nfiles; ++i)
-        pm_close(img[i].ifP);
-    free(cmdline.inputFilespec);
-    free(img);
-    pm_close(stdout);
-
-    return 0;
-}
diff --git a/editor/pnmconvol.c b/editor/pnmconvol.c
index b1d8e025..fcd52bea 100644
--- a/editor/pnmconvol.c
+++ b/editor/pnmconvol.c
@@ -1314,7 +1314,7 @@ computeInitialColumnSums(struct pam *              const pamP,
                          sample **                 const convColumnSum) {
 /*----------------------------------------------------------------------------
   Add up the sum of each column of window[][], whose rows are described
-  by *inpamP.  The window's height is that of tthe convolution kernel
+  by *inpamP.  The window's height is that of the convolution kernel
   *convKernelP.
 
   Return it as convColumnSum[][].
@@ -1854,7 +1854,7 @@ convolveHorizontalRowPlane(struct pam *              const pamP,
                            pamP->maxval);
         } else if (col == ccolso2) {
             unsigned int const leftcol = 0;
-                /* Window is up againt left edge of image */
+                /* Window is up against left edge of image */
 
             {
                 unsigned int ccol;
@@ -2019,7 +2019,7 @@ convolveVerticalRowPlane(struct pam *              const pamP,
 
             if (col == ccolso2) {
                 unsigned int const leftcol = 0;
-                    /* Convolution window is againt left edge of image */
+                    /* Convolution window is against left edge of image */
 
                 unsigned int ccol;
 
diff --git a/editor/pnmcrop.c b/editor/pnmcrop.c
index d6bae1d3..43027da0 100644
--- a/editor/pnmcrop.c
+++ b/editor/pnmcrop.c
@@ -454,7 +454,9 @@ backgroundColor(FILE *         const ifP,
    *ifP, which is described by 'cols', 'rows', 'maxval', and 'format'.
 
    'backgroundChoice' is the method we are to use in determining the
-   background color.
+   background color.  'colorName' is the color we are to assume is
+   background in the case that 'backgroundChoice' says to use a particular
+   color and meangingless otherwise.
 
    Expect the file to be positioned to the start of the raster, and leave
    it positioned arbitrarily.
@@ -484,9 +486,6 @@ backgroundColor(FILE *         const ifP,
         background =
             background1Corner(ifP, rows, cols, maxval, format, corner);
         break;
-
-    default:
-        pm_error("internal error");
     }
 
     return background;
@@ -1124,63 +1123,135 @@ noCrops(struct CmdlineInfo const cmdline) {
 
 
 
+static void
+divideAllBackgroundIntoBorders(unsigned int   const totalSz,
+                               bool           const wantCropSideA,
+                               bool           const wantCropSideB,
+                               unsigned int   const wantMargin,
+                               unsigned int * const sideASzP,
+                               unsigned int * const sideBSzP) {
+/*----------------------------------------------------------------------------
+   Divide up an all-background space into fictional borders (that can be
+   trimmed or padded).
+
+   If there is to be a margin, those borders touch - the entire image is
+   borders.  But if there is not to be a margin, there has to be one pixel,
+   row, or column between the borders so that there is something left after we
+   crop off those borders (because there's no such thing as a zero-pixel
+   image).
+
+   This function does the borders in one direction - top and bottom or left
+   and right.  Top or left is called "Side A"; bottom or right is called "Side
+   B".  'totalSz' is the width of the image in the top/bottom case and the
+   height of the image in the left/right case.
+-----------------------------------------------------------------------------*/
+    unsigned int sideASz, sideBSz;
+
+    if (wantCropSideA && wantCropSideB) {
+        sideASz = totalSz/2;
+        if (wantMargin)
+            sideBSz = totalSz - sideASz;
+        else
+            sideBSz = totalSz - sideASz - 1;
+    } else if (wantCropSideA) {
+        if (wantMargin)
+            sideASz = totalSz;
+        else
+            sideASz = totalSz - 1;
+        sideBSz = 0;
+    } else if (wantCropSideB) {
+        sideASz = 0;
+        if (wantMargin)
+            sideBSz = totalSz;
+        else
+            sideBSz = totalSz - 1;
+    } else {
+        sideASz = 0;
+        sideBSz = 0;
+    }
+    *sideASzP = sideASz;
+    *sideBSzP = sideBSz;
+}
+
+
+
+static CropOp
+oneSideCrop(bool         const wantCrop,
+            unsigned int const borderSz,
+            unsigned int const margin) {
+
+    CropOp retval;
+
+    if (wantCrop) {
+        if (borderSz >= margin) {
+            retval.removeSize = borderSz - margin;
+            retval.padSize    = 0;
+        } else {
+            retval.removeSize = 0;
+            retval.padSize    = margin - borderSz;
+        }
+    } else {
+        retval.removeSize = 0;
+        retval.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.
+   Crops that crop as much as possible, reducing output to a single
+   row, column, or pixel, plus margins.
 -----------------------------------------------------------------------------*/
     CropSet retval;
 
+    unsigned int leftBorderSz, rghtBorderSz;
+    unsigned int topBorderSz, botBorderSz;
+
     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.
+    /* Note that the "entirely background" image may have several colors: this
+       happens when -closeness was specified.  That means we can't just build
+       up an image from background color - we actually have to preserve some
+       of the original image.
 
-       The "entirely background" image may have several colors: this
-       happens when -closeness was specified.
+       We divide the background into individual borders, with a foreground of
+       either nothing at all or a single row, column, or pixel, then crop
+       those fictional borders the same as if they were real.  The "nothing
+       at all" case is feasible only when there is to be a margin, because
+       otherwise cropping the borders would leave nothing.
     */
 
-    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;
-    }
+    divideAllBackgroundIntoBorders(cols,
+                                   cmdline.wantCrop[LEFT],
+                                   cmdline.wantCrop[RIGHT],
+                                   cmdline.margin > 0,
+                                   &leftBorderSz,
+                                   &rghtBorderSz);
+
+    divideAllBackgroundIntoBorders(rows,
+                                   cmdline.wantCrop[TOP],
+                                   cmdline.wantCrop[BOTTOM],
+                                   cmdline.margin > 0,
+                                   &topBorderSz,
+                                   &botBorderSz);
+
+    retval.op[LEFT  ] =
+        oneSideCrop(cmdline.wantCrop[LEFT  ], leftBorderSz, cmdline.margin);
+    retval.op[RIGHT ] =
+        oneSideCrop(cmdline.wantCrop[RIGHT ], rghtBorderSz, cmdline.margin);
+    retval.op[TOP   ] =
+        oneSideCrop(cmdline.wantCrop[TOP   ], topBorderSz,  cmdline.margin);
+    retval.op[BOTTOM] =
+        oneSideCrop(cmdline.wantCrop[BOTTOM], botBorderSz,  cmdline.margin);
 
-    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;
 }
 
@@ -1265,9 +1336,9 @@ cropOneImage(struct CmdlineInfo const cmdline,
              FILE *             const bdfP,
              FILE *             const ofP) {
 /*----------------------------------------------------------------------------
-   Crop the image to which the stream *ifP is presently positioned
+   Crop the image to which the stream *ifP is currently positioned
    and write the results to *ofP.  If bdfP is non-null, use the image
-   to which stream *bdfP is presently positioned as the borderfile
+   to which stream *bdfP is currently positioned as the borderfile
    (the file that tells us where the existing borders are in the input
    image).  Leave *ifP and *bdfP positioned after the image.
 
@@ -1330,12 +1401,9 @@ cropOneImage(struct CmdlineInfo const cmdline,
             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;
+        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);
diff --git a/editor/pnmflip b/editor/pnmflip
index 07d4ddb9..962198a2 100755
--- a/editor/pnmflip
+++ b/editor/pnmflip
@@ -46,6 +46,13 @@ exec perl -w -x -S -- "$0" "$@"
 use strict;
 use File::Basename;
 use Cwd 'abs_path';
+use IO::Handle;
+
+sub pm_message($) {
+    STDERR->print("pnmflip: $_[0]\n");
+}
+
+
 
 my $xformOpt;
 my @miscOptions;
@@ -78,8 +85,7 @@ foreach (@ARGV) {
     } else {
         # It's a parameter
         if (defined($infile)) {
-            print(STDERR
-                  "You may specify at most one non-option parameter.\n");
+            pm_message("You may specify at most one non-option parameter.");
             exit(10);
         } else {
             $infile = $_;
diff --git a/editor/pnmgamma.c b/editor/pnmgamma.c
index 0ed217d1..e10e138b 100644
--- a/editor/pnmgamma.c
+++ b/editor/pnmgamma.c
@@ -602,7 +602,7 @@ createGammaTables(enum transferFunction const transferFunction,
     if (*rtableP == NULL || *gtableP == NULL || *btableP == NULL)
         pm_error("Can't get memory to make gamma transfer tables");
 
-    /* Build the gamma corection tables. */
+    /* Build the gamma correction tables. */
     switch (transferFunction) {
     case XF_BT709RAMP: {
         buildBt709Gamma(*rtableP, maxval, newMaxval, rgamma);
diff --git a/editor/pnmindex.csh b/editor/pnmindex.csh
deleted file mode 100755
index c6f1e844..00000000
--- a/editor/pnmindex.csh
+++ /dev/null
@@ -1,189 +0,0 @@
-#!/bin/csh -f
-#
-# pnmindex - build a visual index of a bunch of anymaps
-#
-# Copyright (C) 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.
-
-# -title and -quant added by John Heidemann 13-Sep-00.
-
-set size=100		# make the images about this big
-set across=6		# show this many images per row
-set colors=256		# quantize results to this many colors
-set back="-white"	# default background color
-set doquant=true	# quantize or not
-set title=""		# default title (none)
-
-while ( 1 )
-    switch ( "$1" )
-
-	case -s*:
-	if ( $#argv < 2 ) goto usage
-	set size="$2"
-	shift
-	shift
-	breaksw
-
-	case -a*:
-	if ( $#argv < 2 ) goto usage
-	set across="$2"
-	shift
-	shift
-	breaksw
-
-	case -t*:
-	if ( $#argv < 2 ) goto usage
-	set title="$2"
-	shift
-	shift
-	breaksw
-
-	case -c*:
-	set colors="$2"
-	shift
-	shift
-	breaksw
-
-	case -noq*:
-	set doquant=false
-	shift
-	breaksw
-
-	case -q*:
-	set doquant=true
-	shift
-	breaksw
-
-	case -b*:
-	set back="-black"
-	shift
-	breaksw
-
-	case -w*:
-	set back="-white"
-	shift
-	breaksw
-
-	case -*:
-	goto usage
-	breaksw
-
-	default:
-	break
-	breaksw
-
-    endsw
-end
-
-if ( $#argv == 0 ) then
-    goto usage
-endif
-
-set tmpfile=/tmp/pi.tmp.$$
-rm -f $tmpfile
-set maxformat=PBM
-
-set rowfiles=()
-set imagefiles=()
-@ row = 1
-@ col = 1
-
-if ( "$title" != "" ) then
-    set rowfile=/tmp/pi.${row}.$$
-    rm -f $rowfile
-    pbmtext "$title" > $rowfile
-    set rowfiles=( $rowfiles $rowfile )
-    @ row += 1
-endif
-
-foreach i ( $argv )
-
-    set description=`pnmfile $i`
-    if ( $description[4] <= $size && $description[6] <= $size ) then
-	cat $i > $tmpfile
-    else
-	switch ( $description[2] )
-	    case PBM:
-	    pnmscale -quiet -xysize $size $size $i | pgmtopbm > $tmpfile
-	    breaksw
-
-	    case PGM:
-	    pnmscale -quiet -xysize $size $size $i > $tmpfile
-	    if ( $maxformat == PBM ) then
-		set maxformat=PGM
-	    endif
-	    breaksw
-
-	    default:
-	    if ( $doquant == false ) then
-	        pnmscale -quiet -xysize $size $size $i > $tmpfile
-	    else
-	        pnmscale -quiet -xysize $size $size $i | ppmquant -quiet $colors > $tmpfile
-	    endif
-	    set maxformat=PPM
-	    breaksw
-	endsw
-    endif
-    set imagefile=/tmp/pi.${row}.${col}.$$
-    rm -f $imagefile
-    if ( "$back" == "-white" ) then
-	pbmtext "$i" | pnmcat $back -tb $tmpfile - > $imagefile
-    else
-	pbmtext "$i" | pnminvert | pnmcat $back -tb $tmpfile - > $imagefile
-    endif
-    rm -f $tmpfile
-    set imagefiles=( $imagefiles $imagefile )
-
-    if ( $col >= $across ) then
-	set rowfile=/tmp/pi.${row}.$$
-	rm -f $rowfile
-	if ( $maxformat != PPM || $doquant == false ) then
-	    pnmcat $back -lr -jbottom $imagefiles > $rowfile
-	else
-	    pnmcat $back -lr -jbottom $imagefiles | ppmquant -quiet $colors > $rowfile
-	endif
-	rm -f $imagefiles
-	set imagefiles=()
-	set rowfiles=( $rowfiles $rowfile )
-	@ col = 1
-	@ row += 1
-    else
-	@ col += 1
-    endif
-
-end
-
-if ( $#imagefiles > 0 ) then
-    set rowfile=/tmp/pi.${row}.$$
-    rm -f $rowfile
-    if ( $maxformat != PPM || $doquant == false ) then
-	pnmcat $back -lr -jbottom $imagefiles > $rowfile
-    else
-	pnmcat $back -lr -jbottom $imagefiles | ppmquant -quiet $colors > $rowfile
-    endif
-    rm -f $imagefiles
-    set rowfiles=( $rowfiles $rowfile )
-endif
-
-if ( $#rowfiles == 1 ) then
-    cat $rowfiles
-else
-    if ( $maxformat != PPM || $doquant == false ) then
-	pnmcat $back -tb $rowfiles
-    else
-	pnmcat $back -tb $rowfiles | ppmquant -quiet $colors
-    endif
-endif
-rm -f $rowfiles
-
-exit 0
-
-usage:
-echo "usage: $0 [-size N] [-across N] [-colors N] [-black] pnmfile ..."
-exit 1
diff --git a/editor/pnmindex.sh b/editor/pnmindex.sh
deleted file mode 100755
index dfc5b82a..00000000
--- a/editor/pnmindex.sh
+++ /dev/null
@@ -1,214 +0,0 @@
-#!/bin/sh
-#
-# pnmindex - build a visual index of a bunch of PNM images
-#
-# Copyright (C) 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.
-
-size=100        # make the images about this big
-across=6        # show this many images per row
-colors=256      # quantize results to this many colors
-back="-white"   # default background color
-doquant=true    # quantize or not
-title=""        # default title (none)
-
-usage ()
-{
-  echo "usage: $0 [-size N] [-across N] [-colors N] [-black] pnmfile ..."
-  exit 1
-}
-
-while :; do
-    case "$1" in
-
-    -s*)
-        if [ $# -lt 2 ]; then usage; fi
-        size="$2"
-        shift
-        shift
-    ;;
-
-    -a*)
-        if [ $# -lt 2 ]; then usage; fi
-        across="$2"
-        shift
-        shift
-    ;;
-
-    -t*)
-        if [ $# -lt 2 ]; then usage; fi
-        title="$2"
-        shift
-        shift
-    ;;
-
-    -c*)
-        if [ $# -lt 2 ]; then usage; fi
-        colors="$2"
-        shift
-        shift
-    ;;
-
-    -b*)
-        back="-black"
-        shift
-    ;;
-
-    -w*)
-        back="-white"
-        shift
-    ;;
-
-    -noq*)
-        doquant=false
-        shift
-    ;;
-
-    -q*)
-        doquant=true
-        shift
-    ;;
-
-    -*)
-        usage
-    ;;
-
-    *)
-        break
-    ;;
-    esac
-done
-
-if [ $# -eq 0 ]; then
-    usage
-fi
-
-tempdir="${TMPDIR-/tmp}/pnmindex.$$"
-mkdir -m 0700 $tempdir || \
-  { echo "Could not create temporary file. Exiting."; exit 1;}
-trap 'rm -rf $tempdir' 0 1 3 15
-
-tmpfile=$tempdir/pi.tmp
-maxformat=PBM
-
-rowfiles=()
-imagefiles=()
-row=1
-col=1
-
-if [ "$title"x != ""x ] ; then
-#    rowfile=`tempfile -p pirow -m 600`
-    rowfile=$tempdir/pi.${row}
-    pbmtext "$title" > $rowfile
-    rowfiles=(${rowfiles[*]} $rowfile )
-    row=$(($row + 1))
-fi
-
-for i in "$@"; do
-
-    description=(`pnmfile $i`)
-
-    format=${description[1]}
-    width=${description[3]}
-    height=${description[5]}
-
-    if [ $? -ne 0 ]; then
-        echo pnmfile returned an error
-        exit $?
-    fi
-
-    if [ $width -le $size ] && \
-       [ $height -le $size ]; then
-        cat $i > $tmpfile
-    else
-        case $format in
-
-        PBM) 
-            pamscale -quiet -xysize $size $size $i | pgmtopbm > $tmpfile
-        ;;
-
-        PGM)
-            pamscale -quiet -xysize $size $size $i > $tmpfile
-            if [ $maxformat = PBM ]; then
-                maxformat=PGM
-            fi
-        ;;
-
-        *) 
-            if [ "$doquant" = "true" ] ; then
-                pamscale -quiet -xysize $size $size $i | \
-                pnmquant -quiet $colors > $tmpfile
-            else
-                pamscale -quiet -xysize $size $size $i > $tmpfile
-            fi
-            maxformat=PPM
-        ;;
-        esac
-    fi
-
-    imagefile=$tempdir/pi.${row}.${col}
-    rm -f $imagefile
-    if [ "$back" = "-white" ]; then
-        pbmtext "$i" | pnmcat $back -tb $tmpfile - > $imagefile
-    else
-        pbmtext "$i" | pnminvert | pnmcat $back -tb $tmpfile - > $imagefile
-    fi
-    imagefiles=( ${imagefiles[*]} $imagefile )
-
-    if [ $col -ge $across ]; then
-        rowfile=$tempdir/pi.${row}
-        rm -f $rowfile
-
-        if [ $maxformat != PPM -o "$doquant" = "false" ]; then
-            pnmcat $back -lr -jbottom ${imagefiles[*]} > $rowfile
-        else
-            pnmcat $back -lr -jbottom ${imagefiles[*]} | \
-            pnmquant -quiet $colors > $rowfile
-        fi
-
-        rm -f ${imagefiles[*]}
-        unset imagefiles
-        imagefiles=()
-        rowfiles=( ${rowfiles[*]} $rowfile )
-        col=1
-        row=$(($row + 1))
-    else
-        col=$(($col + 1))
-    fi
-done
-
-# All the full rows have been put in row files.  
-# Now put the final partial row in its row file.
-
-if [ ${#imagefiles[*]} -gt 0 ]; then
-    rowfile=$tempdir/pi.${row}
-    rm -f $rowfile
-    if [ $maxformat != PPM -o "$doquant" = "false" ]; then
-        pnmcat $back -lr -jbottom ${imagefiles[*]} > $rowfile
-    else
-        pnmcat $back -lr -jbottom ${imagefiles[*]} | \
-        pnmquant -quiet $colors > $rowfile
-    fi
-    rm -f ${imagefiles[*]}
-    rowfiles=( ${rowfiles[*]} $rowfile )
-fi
-
-if [ ${#rowfiles[*]} -eq 1 ]; then
-    cat $rowfiles
-else
-    if [ $maxformat != PPM -o "$doquant" = "false" ]; then
-        pnmcat $back -tb ${rowfiles[*]}
-    else
-        pnmcat $back -tb ${rowfiles[*]} | pnmquant -quiet $colors
-    fi
-fi
-rm -f ${rowfiles[*]}
-
-exit 0
-
diff --git a/editor/pnmmargin b/editor/pnmmargin
index 6e70aba3..94321c7f 100755
--- a/editor/pnmmargin
+++ b/editor/pnmmargin
@@ -94,7 +94,7 @@ else
 
     case "$color" in
         -gofigure )
-        pnmcut 0 0 1 1 $tmp1 | pnmtile $size 1 > $tmp2
+        pamcut 0 0 1 1 $tmp1 | pnmtile $size 1 > $tmp2
         ;;
         -white | -black )
         pnmpad $plainopt $color \
@@ -108,8 +108,8 @@ else
     pamflip -rotate90 $tmp2 > $tmp3
 
     # Cat things together.
-    pnmcat -lr $tmp2 $tmp1 $tmp2 > $tmp4
-    pnmcat -tb $plainopt $tmp3 $tmp4 $tmp3
+    pamcat -leftright $tmp2 $tmp1 $tmp2 > $tmp4
+    pamcat -topbottom $plainopt $tmp3 $tmp4 $tmp3
 fi
 
 
diff --git a/editor/pnmnlfilt.c b/editor/pnmnlfilt.c
index f55a67bd..bcb3680d 100644
--- a/editor/pnmnlfilt.c
+++ b/editor/pnmnlfilt.c
@@ -59,9 +59,9 @@ struct cmdlineInfo {
 };
 
 
-static void 
-parseCommandLine(int argc, 
-                 char ** argv, 
+static void
+parseCommandLine(int argc,
+                 char ** argv,
                  struct cmdlineInfo  * const cmdlineP) {
 
     if (argc-1 < 2)
@@ -75,9 +75,9 @@ parseCommandLine(int argc,
 
     if (sscanf( argv[2], "%lf", &cmdlineP->radius ) != 1)
         pm_error("Invalid radius (2nd) argument '%s'.  "
-                 "Must be a decimal number", 
+                 "Must be a decimal number",
                  argv[2]);
-    
+
     if ((cmdlineP->alpha > -0.1 && cmdlineP->alpha < 0.0) ||
         (cmdlineP->alpha > 0.5 && cmdlineP->alpha < 1.0))
         pm_error( "Alpha must be in range 0.0 <= alpha <= 0.5 "
@@ -118,11 +118,11 @@ parseCommandLine(int argc,
    this value.
 */
 
-#define MXIVAL PPM_MAXMAXVAL   
+#define MXIVAL PPM_MAXMAXVAL
 
-xelval omaxval; 
+xelval omaxval;
     /* global so that pixel processing code can get at it quickly */
-int noisevariance;      
+int noisevariance;
     /* global so that pixel processing code can get at it quickly */
 
 /*
@@ -157,7 +157,7 @@ static  xelval maxval;
 /* round and scale floating point to scaled integer */
 #define ROUNDSCALE(x) ((int)(((x) * (double)SCALE) + 0.5))
 /* round and un-scale scaled integer value */
-#define RUNSCALE(x) (((x) + (1 << (SCALEB-1))) >> SCALEB) 
+#define RUNSCALE(x) (((x) + (1 << (SCALEB-1))) >> SCALEB)
 /* rounded un-scale */
 #define UNSCALE(x) ((x) >> SCALEB)
 
@@ -194,9 +194,9 @@ int AVEDIV[7 * NOCSVAL];                /* divide by 7 to give average value */
 int SQUARE[2 * NOCSVAL];                /* scaled square lookup table */
 
 /* ************************************************** *
-   Hexagon intersecting square area functions 
-   Compute the area of the intersection of a triangle 
-   and a rectangle 
+   Hexagon intersecting square area functions
+   Compute the area of the intersection of a triangle
+   and a rectangle
    ************************************************** */
 
 /* Triangle orientation is per geometric axes (not graphical axies) */
@@ -210,7 +210,7 @@ int SQUARE[2 * NOCSVAL];                /* scaled square lookup table */
 
 #define SWAPI(a,b) (t = a, a = -b, b = -t)
 
-static double 
+static double
 triang_area(double rx0, double ry0, double rx1, double ry1,
             double tx0, double ty0, double tx1, double ty1,
             int tt) {
@@ -250,7 +250,7 @@ triang_area(double rx0, double ry0, double rx1, double ry1,
     a = tx0 - b * ty0;
     d = (ty1 - ty0)/(tx1 - tx0);
     c = ty0 - d * tx0;
-    
+
     /* compute top or right intersection */
     tt = 0;
     ly1 = ry1;
@@ -278,7 +278,7 @@ triang_area(double rx0, double ry0, double rx1, double ry1,
             return (rx1 - rx0) * (ry1 - ry0);
         tt |= 2;        /* bottom intersection */
     }
-    
+
     if (tt == 0) {
         /* top and left intersection */
         /* rectangle minus triangle */
@@ -293,7 +293,7 @@ triang_area(double rx0, double ry0, double rx1, double ry1,
         return ((rx1 - lx1) * (ry1 - ry0))
             + (0.5 * (lx1 - lx0) * (ry1 - ry0));
     } else {
-        /* tt == 3 */ 
+        /* tt == 3 */
         /* right and bottom intersection */
         /* triangle */
         return (0.5 * (rx1 - lx0) * (ly1 - ry0));
@@ -303,7 +303,7 @@ triang_area(double rx0, double ry0, double rx1, double ry1,
 
 
 static double
-rectang_area(double rx0, double ry0, double rx1, double ry1, 
+rectang_area(double rx0, double ry0, double rx1, double ry1,
              double tx0, double ty0, double tx1, double ty1) {
 /* Compute rectangle area */
 /* rx0,ry0,rx1,ry1:  rectangle boundaries */
@@ -326,7 +326,7 @@ rectang_area(double rx0, double ry0, double rx1, double ry1,
 
 
 
-static double 
+static double
 hex_area(double sx, double sy, double hx, double hy, double d) {
 /* compute the area of overlap of a hexagon diameter d, */
 /* centered at hx,hy, with a unit square of center sx,sy. */
@@ -384,7 +384,7 @@ setupSquare(void) {
 
     for (i=0; i < (2 * NOCSVAL); ++i) {
         /* compute square and rescale by (val >> (2 * SCALEB + 2)) table */
-        int const val = CSCTOSC(i - NOCSVAL); 
+        int const val = CSCTOSC(i - NOCSVAL);
         /* NOCSVAL offset to cope with -ve input values */
         SQUARE[i] = (val * val) >> (2 * SCALEB + 2);
     }
@@ -414,7 +414,7 @@ setup1(double   const alpha,
 
         *mmeanscaleP = *meanscaleP = maxscale/noinmean;
         if (alpha == 0.0) {
-            /* mean filter */ 
+            /* mean filter */
             *alpharangeP = 0;
             *alphafractionP = 0.0;            /* not used */
         } else if (alpha < (1.0/6.0)) {
@@ -438,9 +438,9 @@ setup1(double   const alpha,
         double const noinmean = 7.0;
         *alpharangeP = 5;                 /* edge enhancement function */
         *mmeanscaleP = *meanscaleP = maxscale;  /* compute scaled hex values */
-        *alphafractionP = 1.0/noinmean;   
+        *alphafractionP = 1.0/noinmean;
             /* Set up 1:1 division lookup - not used */
-        *noisevarianceP = sqr(alphaNormalized * omaxval) / 8.0;    
+        *noisevarianceP = sqr(alphaNormalized * omaxval) / 8.0;
             /* estimate of noise variance */
     } else if (alpha >= -0.9 && alpha <= -0.1) {
         /* edge enhancement function */
@@ -449,7 +449,7 @@ setup1(double   const alpha,
         *alpharangeP = 4;                 /* edge enhancement function */
         *meanscaleP = maxscale * (-posAlpha/((1.0 - posAlpha) * 7.0));
             /* mean of 7 and scaled by -posAlpha/(1-posAlpha) */
-        *mmeanscaleP = maxscale * (1.0/(1.0 - posAlpha) + *meanscaleP);    
+        *mmeanscaleP = maxscale * (1.0/(1.0 - posAlpha) + *meanscaleP);
             /* middle pixel has 1/(1-posAlpha) as well */
         *alphafractionP = 0.0;    /* not used */
     } else {
@@ -481,18 +481,18 @@ setupPixelWeightingTables(double const radius,
                           double const mmeanscale) {
 
     /* Setup pixel weighting tables - note we pre-compute mean
-       division here too. 
+       division here too.
     */
-    double const hexhoff = radius/2;      
+    double const hexhoff = radius/2;
         /* horizontal offset of vertical hex centers */
-    double const hexvoff = 3.0 * radius/sqrt(12.0); 
+    double const hexvoff = 3.0 * radius/sqrt(12.0);
         /* vertical offset of vertical hex centers */
 
     double const tabscale  = meanscale  / (radius * hexvoff);
     double const mtabscale = mmeanscale / (radius * hexvoff);
 
     /* scale tables to normalize by hexagon area, and number of
-       hexes used in mean 
+       hexes used in mean
     */
     double const v0 =
         hex_area(0.0,  0.0, hexhoff, hexvoff, radius) * tabscale;
@@ -538,7 +538,7 @@ setupPixelWeightingTables(double const radius,
 
 
 /* Table initialization function - return alpha range */
-static int 
+static int
 atfilt_setup(double const alpha,
              double const radius,
              double const maxscale) {
@@ -546,7 +546,7 @@ atfilt_setup(double const alpha,
     int alpharange;                 /* alpha range value 0 - 5 */
     double meanscale;               /* scale for finding mean */
     double mmeanscale;              /* scale for finding mean - midle hex */
-    double alphafraction;   
+    double alphafraction;
         /* fraction of next largest/smallest to subtract from sum */
 
     setup1(alpha, radius, maxscale,
@@ -562,7 +562,7 @@ atfilt_setup(double const alpha,
 
 
 
-static int 
+static int
 atfilt0(int * p) {
 /* Core pixel processing function - hand it 3x3 pixels and return result. */
 /* Mean filter */
@@ -586,7 +586,7 @@ atfilt0(int * p) {
         else if (xx < small) \
             small = xx; }
 
-static int 
+static int
 atfilt1(int * p) {
 /* Mean of 5 - 7 middle values */
 /* 'p' is 9 pixel values from 3x3 neighbors */
@@ -635,7 +635,7 @@ atfilt1(int * p) {
     }
 
 
-static int 
+static int
 atfilt2(int *p) {
 /* Mean of 3 - 5 middle values */
 /* 'p' is 9 pixel values from 3x3 neighbors */
@@ -708,10 +708,10 @@ atfilt2(int *p) {
                         small2 = xx; \
                                          }}
 
-static int 
+static int
 atfilt3(int *p) {
 /* Mean of 1 - 3 middle values. If only 1 value, then this is a median
-   filter. 
+   filter.
 */
 /* 'p' is pixel values from 3x3 neighbors */
     int h0,h1,h2,h3,h4,h5,h6;       /* hexagon values    2 3   */
@@ -737,12 +737,12 @@ atfilt3(int *p) {
     CHECK(h5);
     CHECK(h6);
     /* Compute mean of middle 1-3 values */
-    return  UNSCALE(h0 -big0 -big1 -small0 -small1 
+    return  UNSCALE(h0 -big0 -big1 -small0 -small1
                     -ALFRAC[(big2 + small2)>>SCALEB]);
 }
 #undef CHECK
 
-static int 
+static int
 atfilt4(int *p) {
 /* Edge enhancement */
 /* notice we use the global omaxval */
@@ -765,7 +765,7 @@ atfilt4(int *p) {
     return hav;
 }
 
-static int 
+static int
 atfilt5(int *p) {
 /* Optimal estimation - do smoothing in inverse proportion */
 /* to the local variance. */
@@ -786,19 +786,19 @@ atfilt5(int *p) {
     h6 = V0[p[0]] + V1[p[7]] + V2[p[8]] + V3[p[1]];
     mean = h0 + h1 + h2 + h3 + h4 + h5 + h6;
     mean = AVEDIV[SCTOCSC(mean)];   /* compute scaled mean by dividing by 7 */
-    temp = (h1 - mean); variance = SQUARE[NOCSVAL + SCTOCSC(temp)];  
+    temp = (h1 - mean); variance = SQUARE[NOCSVAL + SCTOCSC(temp)];
         /* compute scaled variance */
-    temp = (h2 - mean); variance += SQUARE[NOCSVAL + SCTOCSC(temp)]; 
+    temp = (h2 - mean); variance += SQUARE[NOCSVAL + SCTOCSC(temp)];
         /* and rescale to keep */
-    temp = (h3 - mean); variance += SQUARE[NOCSVAL + SCTOCSC(temp)]; 
+    temp = (h3 - mean); variance += SQUARE[NOCSVAL + SCTOCSC(temp)];
         /* within 32 bit limits */
     temp = (h4 - mean); variance += SQUARE[NOCSVAL + SCTOCSC(temp)];
     temp = (h5 - mean); variance += SQUARE[NOCSVAL + SCTOCSC(temp)];
     temp = (h6 - mean); variance += SQUARE[NOCSVAL + SCTOCSC(temp)];
-    temp = (h0 - mean); variance += SQUARE[NOCSVAL + SCTOCSC(temp)];   
+    temp = (h0 - mean); variance += SQUARE[NOCSVAL + SCTOCSC(temp)];
     /* (temp = h0 - mean) */
     if (variance != 0)      /* avoid possible divide by 0 */
-        temp = mean + (variance * temp) / (variance + noisevariance);   
+        temp = mean + (variance * temp) / (variance + noisevariance);
             /* optimal estimate */
     else temp = h0;
     if (temp < 0)
@@ -811,17 +811,17 @@ atfilt5(int *p) {
 
 
 
-static void 
+static void
 do_one_frame(FILE * const ifP) {
 
     pnm_writepnminit( stdout, cols, rows, omaxval, oformat, 0 );
-    
+
     if ( PNM_FORMAT_TYPE(oformat) == PPM_TYPE ) {
         int pr[9],pg[9],pb[9];          /* 3x3 neighbor pixel values */
         int r,g,b;
 
         for ( row = 0; row < rows; row++ ) {
-            int po,no;           /* offsets for left and right colums in 3x3 */
+            int po,no;          /* offsets for left and right columns in 3x3 */
             xel *ip0, *ip1, *ip2, *op;
 
             if (row == 0) {
@@ -897,14 +897,14 @@ do_one_frame(FILE * const ifP) {
         promote = ( PNM_FORMAT_TYPE(format) != PNM_FORMAT_TYPE(oformat) );
 
         for ( row = 0; row < rows; row++ ) {
-            int po,no;          /* offsets for left and right colums in 3x3 */
+            int po,no;          /* offsets for left and right columns in 3x3 */
             xel *ip0, *ip1, *ip2, *op;
 
             if (row == 0) {
                 irow0 = irow1;
                 pnm_readpnmrow( ifP, irow1, cols, maxval, format );
                 if ( promote )
-                    pnm_promoteformatrow( irow1, cols, maxval, 
+                    pnm_promoteformatrow( irow1, cols, maxval,
                                           format, maxval, oformat );
             }
             if (row == (rows-1))
@@ -912,7 +912,7 @@ do_one_frame(FILE * const ifP) {
             else {
                 pnm_readpnmrow( ifP, irow2, cols, maxval, format );
                 if ( promote )
-                    pnm_promoteformatrow( irow2, cols, maxval, 
+                    pnm_promoteformatrow( irow2, cols, maxval,
                                           format, maxval, oformat );
             }
 
@@ -955,7 +955,7 @@ do_one_frame(FILE * const ifP) {
 
 
 static void
-verifySame(unsigned int const imageSeq, 
+verifySame(unsigned int const imageSeq,
            int const imageCols, int const imageRows,
            xelval const imageMaxval, int const imageFormat,
            int const cols, int const rows,
@@ -1000,16 +1000,16 @@ main(int argc, char *argv[]) {
     ifP = pm_openr(cmdline.inputFileName);
 
     pnm_readpnminit(ifP, &cols, &rows, &maxval, &format);
-        
-    if (maxval > MXIVAL) 
+
+    if (maxval > MXIVAL)
         pm_error("The maxval of the input image (%d) is too large.\n"
-                 "This program's limit is %d.", 
+                 "This program's limit is %d.",
                  maxval, MXIVAL);
-        
+
     oformat = PNM_FORMAT_TYPE(format);
     /* force output to max precision without forcing new 2-byte format */
     omaxval = MIN(maxval, PPM_MAXMAXVAL);
-        
+
     atfunc = atfuncs[atfilt_setup(cmdline.alpha, cmdline.radius,
                                   (double)omaxval/(double)maxval)];
 
@@ -1038,7 +1038,7 @@ main(int argc, char *argv[]) {
             int imageFormat;
 
             ++imageSeq;
-            pnm_readpnminit(ifP, &imageCols, &imageRows, 
+            pnm_readpnminit(ifP, &imageCols, &imageRows,
                             &imageMaxval, &imageFormat);
             verifySame(imageSeq,
                        imageCols, imageRows, imageMaxval, imageFormat,
@@ -1054,3 +1054,6 @@ main(int argc, char *argv[]) {
 
     return 0;
 }
+
+
+
diff --git a/editor/pnmnorm.c b/editor/pnmnorm.c
index 3a181bf3..8c25df80 100644
--- a/editor/pnmnorm.c
+++ b/editor/pnmnorm.c
@@ -38,7 +38,7 @@
 
 enum brightMethod {BRIGHT_LUMINOSITY, BRIGHT_COLORVALUE, BRIGHT_SATURATION};
 
-struct cmdlineInfo {
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
@@ -70,7 +70,7 @@ struct cmdlineInfo {
 
 static void
 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.
@@ -265,7 +265,12 @@ buildHistogram(FILE *   const ifp,
 static xelval
 minimumValue(const unsigned int * const hist,
              unsigned int         const highest) {
+/*----------------------------------------------------------------------------
+   The minimum brightness in the image, according to histogram 'hist',
+   which goes up to 'highest'.
 
+   Abort the program if there are no pixels of any brightness.
+-----------------------------------------------------------------------------*/
     xelval i;
     bool foundOne;
 
@@ -274,7 +279,8 @@ minimumValue(const unsigned int * const hist,
             foundOne = true;
         else {
             if (i == highest)
-                pm_error("INTERNAL ERROR in '%s'.  No pixels", __FUNCTION__);
+                pm_error("INTERNAL ERROR in function 'minimumValue'.  "
+                         "No pixels");
             else
                 ++i;
         }
@@ -287,7 +293,12 @@ minimumValue(const unsigned int * const hist,
 static xelval
 maximumValue(const unsigned int * const hist,
              unsigned int         const highest) {
+/*----------------------------------------------------------------------------
+   The maximum brightness in the image, according to histogram 'hist',
+   which goes up to 'highest'.
 
+   Abort the program if there are no pixels of any brightness.
+-----------------------------------------------------------------------------*/
     xelval i;
     bool foundOne;
 
@@ -296,7 +307,8 @@ maximumValue(const unsigned int * const hist,
             foundOne = true;
         else {
             if (i == 0)
-                pm_error("INTERNAL ERROR in '%s'.  No pixels", __FUNCTION__);
+                pm_error("INTERNAL ERROR in function 'maximumValue'.  "
+                         "No pixels");
             else
                 --i;
         }
@@ -306,12 +318,32 @@ maximumValue(const unsigned int * const hist,
 
 
 
+static unsigned int
+brightnessCount(const unsigned int * const hist,
+                unsigned int         const highest) {
+/*----------------------------------------------------------------------------
+   The number of distinct brightnesses in the image according to
+   histogram 'hist', which goes up to brightness 'highest'.
+-----------------------------------------------------------------------------*/
+    xelval i;
+    unsigned int nonzeroCount;
+
+    for (i = 0, nonzeroCount = 0; i <= highest; ++i) {
+        if (hist[i] > 0)
+            ++nonzeroCount;
+    }
+
+    return nonzeroCount;
+}
+
+
+
 static void
-computeBottomPercentile(unsigned int         hist[],
-                        unsigned int   const highest,
-                        unsigned int   const total,
-                        float          const percent,
-                        unsigned int * const percentileP) {
+computeBottomPercentile(const unsigned int * const hist,
+                        unsigned int         const highest,
+                        unsigned int         const total,
+                        float                const percent,
+                        unsigned int *       const percentileP) {
 /*----------------------------------------------------------------------------
    Compute the lowest index of hist[] such that the sum of the hist[]
    values with that index and lower represent at least 'percent' per cent of
@@ -339,11 +371,11 @@ computeBottomPercentile(unsigned int         hist[],
 
 
 static void
-computeTopPercentile(unsigned int         hist[],
-                     unsigned int   const highest,
-                     unsigned int   const total,
-                     float          const percent,
-                     unsigned int * const percentileP) {
+computeTopPercentile(const unsigned int * const hist,
+                     unsigned int         const highest,
+                     unsigned int         const total,
+                     float                const percent,
+                     unsigned int *       const percentileP) {
 /*----------------------------------------------------------------------------
    Compute the highest index of hist[] such that the sum of the hist[]
    values with that index and higher represent 'percent' per cent of
@@ -494,13 +526,68 @@ disOverlap(xelval   const reqBvalue,
 
 
 
+static xelval
+resolvedPctBvalue(const unsigned int * const hist,
+                  unsigned int         const totalPixelCt,
+                  xelval               const maxval,
+                  struct CmdlineInfo   const cmdline) {
+
+    xelval retval;
+
+    if (cmdline.bsingle)
+        retval = minimumValue(hist, maxval);
+    else if (cmdline.bvalueSpec && !cmdline.bpercentSpec) {
+        retval = cmdline.bvalue;
+    } else {
+        xelval percentBvalue;
+
+        computeBottomPercentile(hist, maxval, totalPixelCt, cmdline.bpercent,
+                                &percentBvalue);
+        if (cmdline.bvalueSpec)
+            retval = MIN(percentBvalue, cmdline.bvalue);
+        else
+            retval = percentBvalue;
+    }
+    return retval;
+}
+
+
+
+static xelval
+resolvedPctWvalue(const unsigned int * const hist,
+                  unsigned int         const totalPixelCt,
+                  xelval               const maxval,
+                  struct CmdlineInfo   const cmdline) {
+
+    xelval retval;
+
+    if (cmdline.wsingle)
+        retval = maximumValue(hist, maxval);
+    else if (cmdline.wvalueSpec && !cmdline.wpercentSpec) {
+        retval = cmdline.wvalue;
+    } else {
+        xelval percentWvalue;
+
+        computeTopPercentile(hist, maxval, totalPixelCt, cmdline.wpercent,
+                             &percentWvalue);
+        if (cmdline.wvalueSpec)
+            retval = MAX(percentWvalue, cmdline.wvalue);
+        else
+            retval = percentWvalue;
+    }
+
+    return retval;
+}
+
+
+
 static void
 resolvePercentParams(FILE *             const ifP,
                      unsigned int       const cols,
                      unsigned int       const rows,
                      xelval             const maxval,
                      int                const format,
-                     struct cmdlineInfo const cmdline,
+                     struct CmdlineInfo const cmdline,
                      xelval *           const bvalueP,
                      xelval *           const wvalueP) {
 /*----------------------------------------------------------------------------
@@ -521,32 +608,17 @@ resolvePercentParams(FILE *             const ifP,
         buildHistogram(ifP, cols, rows, maxval, format, hist,
                        cmdline.brightMethod);
 
-        if (cmdline.bsingle)
-            *bvalueP = minimumValue(hist, maxval);
-        else if (cmdline.bvalueSpec && !cmdline.bpercentSpec) {
-            *bvalueP = cmdline.bvalue;
+        if (!cmdline.bvalueSpec && !cmdline.wvalueSpec &&
+            brightnessCount(hist, maxval) < 2) {
+            /* Special case - you can't stretch a single brightness to both
+               ends.  So just don't stretch at all.
+            */
+            *bvalueP = 0;
+            *wvalueP = maxval;
         } else {
-            xelval percentBvalue;
-            computeBottomPercentile(hist, maxval, cols*rows, cmdline.bpercent,
-                                    &percentBvalue);
-            if (cmdline.bvalueSpec)
-                *bvalueP = MIN(percentBvalue, cmdline.bvalue);
-            else
-                *bvalueP = percentBvalue;
-        }
+            *bvalueP = resolvedPctBvalue(hist, cols * rows, maxval, cmdline);
 
-        if (cmdline.wsingle)
-            *wvalueP = maximumValue(hist, maxval);
-        else if (cmdline.wvalueSpec && !cmdline.wpercentSpec) {
-            *wvalueP = cmdline.wvalue;
-        } else {
-            xelval percentWvalue;
-            computeTopPercentile(hist, maxval, cols*rows, cmdline.wpercent,
-                                 &percentWvalue);
-            if (cmdline.wvalueSpec)
-                *wvalueP = MAX(percentWvalue, cmdline.wvalue);
-            else
-                *wvalueP = percentWvalue;
+            *wvalueP = resolvedPctWvalue(hist, cols * rows, maxval, cmdline);
         }
         free(hist);
     }
@@ -560,7 +632,7 @@ computeEndValues(FILE *             const ifP,
                  int                const rows,
                  xelval             const maxval,
                  int                const format,
-                 struct cmdlineInfo const cmdline,
+                 struct CmdlineInfo const cmdline,
                  xelval *           const bvalueP,
                  xelval *           const wvalueP,
                  bool *             const quadraticP,
@@ -627,7 +699,7 @@ computeLinearTransfer(xelval   const bvalue,
     unsigned int val;
     /* The following for structure is a hand optimization of this one:
        for (i = bvalue; i <= wvalue; ++i)
-       newBrightness[i] = (i-bvalue)*maxval/range);
+           newBrightness[i] = (i-bvalue)*maxval/range);
        (with proper rounding)
     */
     for (i = bvalue, val = range/2;
@@ -935,8 +1007,8 @@ reportTransferParm(bool   const quadratic,
 int
 main(int argc, const char *argv[]) {
 
-    struct cmdlineInfo cmdline;
-    FILE *ifP;
+    struct CmdlineInfo cmdline;
+    FILE * ifP;
     pm_filepos imagePos;
     xelval maxval;
     int rows, cols, format;
diff --git a/editor/pnmpad.c b/editor/pnmpad.c
index 7cc53b69..9fa9f9e6 100644
--- a/editor/pnmpad.c
+++ b/editor/pnmpad.c
@@ -38,6 +38,7 @@ struct cmdlineInfo {
     unsigned int mwidth;
     unsigned int mheight;
     unsigned int white;     /* >0: pad white; 0: pad black */
+    unsigned int reportonly;
     unsigned int verbose;
 };
 
@@ -86,14 +87,16 @@ parseCommandLine(int argc, const char ** argv,
             &yalignSpec,           0);
     OPTENT3(0,   "valign",    OPT_FLOAT,   &cmdlineP->yalign,
             &yalignSpec,           0);
-    OPTENT3(0,   "black",     OPT_FLAG,    NULL,
-            &blackOpt,           0);
     OPTENT3(0,   "mwidth",    OPT_UINT,    &cmdlineP->mwidth,
             &mwidthSpec,         0);
     OPTENT3(0,   "mheight",   OPT_UINT,    &cmdlineP->mheight,
             &mheightSpec,        0);
+    OPTENT3(0,   "black",     OPT_FLAG,    NULL,
+            &blackOpt,           0);
     OPTENT3(0,   "white",     OPT_FLAG,    NULL,
             &cmdlineP->white,    0);
+    OPTENT3(0,   "reportonly", OPT_FLAG,   NULL,
+            &cmdlineP->reportonly,   0);
     OPTENT3(0,   "verbose",   OPT_FLAG,    NULL,
             &cmdlineP->verbose,  0);
 
@@ -126,16 +129,16 @@ parseCommandLine(int argc, const char ** argv,
        pm_error("You can specify -height only once");
 
     if (xalignSpec && (cmdlineP->leftSpec || cmdlineP->rightSpec))
-        pm_error("You cannot specify both -xalign and -left or -right");
+        pm_error("You cannot specify both -halign and -left or -right");
 
     if (yalignSpec && (cmdlineP->topSpec || cmdlineP->bottomSpec))
-        pm_error("You cannot specify both -yalign and -top or -bottom");
+        pm_error("You cannot specify both -valign and -top or -bottom");
 
-    if (xalignSpec && !cmdlineP->xsizeSpec)
-        pm_error("-xalign is meaningless without -width");
+    if (xalignSpec && (!cmdlineP->xsizeSpec && !mwidthSpec) )
+        pm_error("-halign is meaningless without -width or -mwidth");
 
-    if (yalignSpec && !cmdlineP->ysizeSpec)
-        pm_error("-yalign is meaningless without -height");
+    if (yalignSpec && (!cmdlineP->ysizeSpec && !mheightSpec) )
+        pm_error("-valign is meaningless without -height or -mheight");
 
     if (xalignSpec) {
         if (cmdlineP->xalign < 0)
@@ -265,7 +268,7 @@ validateHorizontalSize(struct cmdlineInfo const cmdline,
         pm_error("The right padding value you specified is too large.");
 
     if ((double) cols +
-        (double) lpad + 
+        (double) lpad +
         (double) rpad +
         (double) mwidthMaxPad > MAX_WIDTHHEIGHT)
         pm_error("Given padding parameters make output width too large "
@@ -375,9 +378,9 @@ computePadSizesOneDim(unsigned int   const unpaddedSize,
         unsigned int const totalPadBeforeMult =
             begPadBeforeMult + endPadBeforeMult;
         double const begFrac =
-            totalPadBeforeMult > 0 ? 
+            totalPadBeforeMult > 0 ?
             (double)begPadBeforeMult / totalPadBeforeMult :
-            0.0;
+            align;
         unsigned int const addlMsizeBegPad = ROUNDU(morePadNeeded * begFrac);
             /* # of pixels we have to add to the beginning to satisfy
                user's desire for the final size to be a multiple of something
@@ -489,6 +492,23 @@ computePadSizes(struct cmdlineInfo const cmdline,
 
 
 static void
+reportPadSizes(int          const inCols,
+               int          const inRows,
+               unsigned int const lpad,
+               unsigned int const rpad,
+               unsigned int const tpad,
+               unsigned int const bpad) {
+
+    unsigned int const outCols = inCols + lpad + rpad;
+    unsigned int const outRows = inRows + tpad + bpad;
+
+    printf("%u %u %u %u %u %u\n", lpad, rpad, tpad, bpad, outCols, outRows);
+
+}
+
+
+
+static void
 padPbm(FILE *       const ifP,
        unsigned int const cols,
        unsigned int const rows,
@@ -527,7 +547,7 @@ padPbm(FILE *       const ifP,
     /* Write top margin */
     for (row = 0; row < tpad; ++row)
         pbm_writepbmrow_packed(stdout, bgrow, newcols, 0);
-    
+
     /* Read rows, shift and write with left and right margins added */
     for (row = 0; row < rows; ++row) {
         pbm_readpbmrow_bitoffset(ifP, newrow, cols, format, lpad);
@@ -636,12 +656,16 @@ main(int argc, const char ** argv) {
 
     newcols = cols + lpad + rpad;
 
-    if (PNM_FORMAT_TYPE(format) == PBM_TYPE)
-        padPbm(ifP, cols, rows, format, newcols, lpad, rpad, tpad, bpad,
-               !!cmdline.white);
-    else
-        padGeneral(ifP, cols, rows, maxval, format, 
-                   newcols, lpad, rpad, tpad, bpad, !!cmdline.white);
+    if (cmdline.reportonly)
+        reportPadSizes(cols, rows, lpad, rpad, tpad, bpad);
+    else {
+        if (PNM_FORMAT_TYPE(format) == PBM_TYPE)
+            padPbm(ifP, cols, rows, format, newcols, lpad, rpad, tpad, bpad,
+                   !!cmdline.white);
+        else
+            padGeneral(ifP, cols, rows, maxval, format,
+                       newcols, lpad, rpad, tpad, bpad, !!cmdline.white);
+    }
 
     pm_close(ifP);
 
diff --git a/editor/pnmquant b/editor/pnmquant
index 0bb328d2..4dd133f1 100755
--- a/editor/pnmquant
+++ b/editor/pnmquant
@@ -37,9 +37,16 @@ use Getopt::Long;
 use File::Spec;
 #use Fcntl ":seek";  # not available in Perl 5.00503
 use Fcntl;  # gets open flags
+use IO::Handle;
 
 my ($TRUE, $FALSE) = (1,0);
 
+sub pm_message($) {
+    STDERR->print("pnmquant: $_[0]\n");
+}
+
+
+
 my ($SEEK_SET, $SEEK_CUR, $SEEK_END) = (0, 1, 2);
 
 
@@ -59,16 +66,17 @@ sub doVersionHack($) {
 
 sub tempFile($) {
 
+    my ($suffix) = @_;
+
     # We trust Perl's File::Temp to do a better job of creating the temp
     # file, but it doesn't exist before Perl 5.6.1.
 
     if (eval { require File::Temp; 1 }) {
         return File::Temp::tempfile("pnmquant_XXXX", 
-                                    SUFFIX=>".pnm", 
+                                    SUFFIX=>$suffix, 
                                     DIR=>File::Spec->tmpdir(),
                                     UNLINK=>$TRUE);
     } else {
-        my ($suffix) = @_;
         my $fileName;
         local *file;  # For some inexplicable reason, must be local, not my
         my $i;
@@ -89,7 +97,7 @@ sub parseCommandLine(@) {
 
     my %cmdline;
 
-    my $validOptions = GetOptions(\%cmdline,
+    my $optsAreValid = GetOptions(\%cmdline,
                                   "center",
                                   "meancolor",
                                   "meanpixel",
@@ -101,18 +109,17 @@ sub parseCommandLine(@) {
                                   "quiet",
                                   "plain");
 
-    if (!$validOptions) {
-        print(STDERR "Invalid option syntax.\n");
+    if (!$optsAreValid) {
+        pm_message("Invalid option syntax");
         exit(1);
     }
     if (@ARGV > 2) {
-        print(STDERR "This program takes at most 2 arguments.  You specified ",
-              scalar(@ARGV), "\n");
+        pm_message("This program takes at most 2 arguments.  You specified " .
+                   scalar(@ARGV));
         exit(1);
     } 
     elsif (@ARGV < 1) {
-        print(STDERR 
-              "You must specify the number of colors as an argument.\n");
+        pm_message("You must specify the number of colors as an argument.");
         exit(1);
     }
     my $infile;
@@ -120,9 +127,8 @@ sub parseCommandLine(@) {
     
     if (!($cmdline{ncolors} =~ m{ ^[[:digit:]]+$ }x ) || 
         $cmdline{ncolors} == 0) {
-        print(STDERR 
-              "Number of colors argument '$cmdline{ncolors}' " .
-              "is not a positive integer.\n");
+        pm_message("Number of colors argument '$cmdline{ncolors}' " .
+                   "is not a positive integer.");
         exit(1);
     }
 
@@ -199,10 +205,10 @@ sub openSeekableAsStdin($) {
 
 
 
-sub makeColormap($$$$$) {
+sub makeColormap($$$$$$$) {
 
-    my ($ncolors, $opt_meanpixel, $opt_meancolor, $opt_spreadluminosity,
-        $opt_quiet) = @_;
+    my ($ncolors, $opt_center, $opt_meanpixel, $opt_meancolor,
+        $opt_spreadbrightness, $opt_spreadluminosity, $opt_quiet) = @_;
 
     # Make a colormap of $ncolors colors from the image on Standard Input.
     # Put it in a temporary file and return its name.
@@ -210,12 +216,23 @@ sub makeColormap($$$$$) {
     my ($mapfileFh, $mapfileSpec) = tempFile(".pnm");
 
     if (!defined($mapfileFh)) {
-        print(STDERR "Unable to create temporary file for colormap.  " .
-              "errno = $ERRNO\n");
+        pm_message("Unable to create temporary file for colormap.  " .
+                   "errno = $ERRNO");
         exit(1);
     }
-
+       
     my $averageOpt;
+
+    my $colorSummaryOptCt =
+        (defined($opt_meanpixel) ? 1 : 0) +
+        (defined($opt_meancolor) ? 1 : 0) +
+        (defined($opt_center)    ? 1 : 0);
+
+    if ($colorSummaryOptCt > 1) {
+        pm_message("You can specify only one of " .
+                   "-meanpixel, -meancolor, and -center");
+        exit(1);
+    }
     if (defined($opt_meanpixel)) {
         $averageOpt = "-meanpixel";
     } elsif (defined($opt_meancolor)) {
@@ -224,6 +241,16 @@ sub makeColormap($$$$$) {
         $averageOpt = "-center";
     }
 
+    my $spreadOptCt =
+        (defined($opt_spreadluminosity) ? 1 : 0) +
+        (defined($opt_spreadbrightness) ? 1 : 0);
+
+    if ($spreadOptCt > 1) {
+        pm_message("You can specify only one of " .
+                   "-spreadluminosity and -spreadbrightness");
+        exit(1);
+    }
+
     my $spreadOpt;
     if (defined($opt_spreadluminosity)) {
         $spreadOpt = "-spreadluminosity";
@@ -242,7 +269,7 @@ sub makeColormap($$$$$) {
     my $maprc = system("pnmcolormap", $ncolors, @options);
 
     if ($maprc != 0) {
-        print(STDERR "pnmcolormap failed, rc=$maprc\n");
+        pm_message("pnmcolormap failed, rc=$maprc");
         exit(1);
     } 
     return $mapfileSpec;
@@ -265,12 +292,16 @@ sub remap($$$$$$) {
         push(@options, "-floyd");
     }
     if ($opt_norandom) {
+        if (defined($opt_randomseed)) {
+             pm_message("You cannot specify -randomseed with -norandom");
+             exit(1);
+        }
         push(@options, "-norandom");
     }
     if (defined($opt_randomseed)) {
         if ($opt_randomseed < 0) {
-             print(STDERR "-randomseed value must not be negative.  " .
-                   "You specified $opt_randomseed\n");
+             pm_message("-randomseed value must not be negative.  " .
+                        "You specified $opt_randomseed");
              exit(10);
         }
         push(@options, "-randomseed=$opt_randomseed");
@@ -285,7 +316,7 @@ sub remap($$$$$$) {
     my $remaprc = system("pnmremap", "-mapfile=$mapfileSpec", @options);
     
     if ($remaprc != 0) {
-        print(STDERR "pnmremap failed, rc=$remaprc\n");
+        pm_message("pnmremap failed, rc=$remaprc");
         exit(1);
     }
 }
@@ -308,8 +339,10 @@ select(OLDOUT);  # avoids Perl bug where it says we never use OLDOUT
 
 
 my $mapfileSpec = makeColormap($cmdlineR->{ncolors}, 
+                               $cmdlineR->{center}, 
                                $cmdlineR->{meanpixel}, 
                                $cmdlineR->{meancolor}, 
+                               $cmdlineR->{spreadbrightness},
                                $cmdlineR->{spreadluminosity},
                                $cmdlineR->{quiet});
 
diff --git a/editor/pnmquantall b/editor/pnmquantall
index aea6cc84..594e8f7b 100755
--- a/editor/pnmquantall
+++ b/editor/pnmquantall
@@ -57,11 +57,18 @@ use warnings;
 use English;
 use Fcntl;  # gets open flags
 use File::Copy;
+use IO::Handle;
 
 my $TRUE=1; my $FALSE = 0;
 
 
 
+sub pm_message($) {
+    STDERR->print("pnmquantall: $_[0]\n");
+}
+
+
+
 sub doVersionHack($) {
     my ($argvR) = @_;
 
@@ -84,7 +91,7 @@ sub parseArgs($$$$) {
 
     if (@argv > 0 && $argv[0] eq "-ext") {
         if (@argv < 2) {
-            print STDERR ("-ext requires a value\n");
+            pm_message("-ext requires a value");
         exit(100);
         } else {
             $$extR = $argv[1];
@@ -96,8 +103,8 @@ sub parseArgs($$$$) {
     }
 
     if (@argv < $firstArgPos + 2) {
-        print STDERR ("Not enough arguments.  You need at least the number " .
-                      "of colors and one file name\n");
+        pm_message("Not enough arguments.  You need at least the number " .
+                      "of colors and one file name");
         exit(100);
     }
     
@@ -110,16 +117,17 @@ sub parseArgs($$$$) {
 
 sub tempFile($) {
 
+    my ($suffix) = @_;
+
     # We trust Perl's File::Temp to do a better job of creating the temp
     # file, but it doesn't exist before Perl 5.6.1.
 
     if (eval { require File::Temp; 1 }) {
         return File::Temp::tempfile("pnmquant_XXXX", 
-                                    SUFFIX=>".pnm", 
+                                    SUFFIX=>$suffix, 
                                     DIR=>File::Spec->tmpdir(),
                                     UNLINK=>$TRUE);
     } else {
-        my ($suffix) = @_;
         my $fileName;
         local *file;  # For some inexplicable reason, must be local, not my
         my $i;
@@ -137,11 +145,11 @@ sub tempFile($) {
 sub makeColorMap($$$$) {
     my ($fileNamesR, $newColorCt, $colorMapFileName, $errorR) = @_;
 
-    my $pnmcatCmd = "pnmcat -topbottom -white -jleft @{$fileNamesR}";
+    my $pamcatCmd = "pamcat -topbottom -white -jleft @{$fileNamesR}";
 
     my $pnmcolormapCmd = "pnmcolormap $newColorCt";
 
-    my $makeMapCmd = "$pnmcatCmd | $pnmcolormapCmd >$colorMapFileName";
+    my $makeMapCmd = "$pamcatCmd | $pnmcolormapCmd >$colorMapFileName";
 
     my $termStatus = system($makeMapCmd);
 
@@ -212,7 +220,7 @@ if (!$progError) {
 my $exitStatus;
 
 if ($progError) {
-    print STDERR ("Failed.  $progError\n");
+    pm_message("Failed.  $progError");
     $exitStatus = 1;
 } else {
     $exitStatus = 0;
diff --git a/editor/pnmremap.c b/editor/pnmremap.c
index 0038f4d7..e5b59d04 100644
--- a/editor/pnmremap.c
+++ b/editor/pnmremap.c
@@ -28,6 +28,7 @@
 #include "pm_c_util.h"
 #include "mallocvar.h"
 #include "nstring.h"
+#include "rand.h"
 #include "shhopt.h"
 #include "pam.h"
 #include "ppm.h"
@@ -399,20 +400,21 @@ randomizeError(long **       const err,
    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;
+    struct pm_randSt randSt;
 
     assert(random.init != RANDOM_NONE);
 
-    srand(seed);
+    pm_randinit(&randSt);
+    pm_srand2(&randSt, random.init == RANDOM_WITHSEED, random.seed);
 
     for (col = 0; col < width; ++col) {
         unsigned int plane;
         for (plane = 0; plane < depth; ++plane)
-            err[plane][col] = rand() % (FS_SCALE * 2) - FS_SCALE;
+            err[plane][col] = pm_rand(&randSt) % (FS_SCALE * 2) - FS_SCALE;
     }
+
+    pm_randterm(&randSt);
 }
 
 
@@ -1043,13 +1045,6 @@ copyRaster(struct pam *   const inpamP,
     inrow  = pnm_allocpamrow(inpamP);
     outrow = pnm_allocpamrow(&workpam);
 
-    if (outpamP->maxval != inpamP->maxval && defaultColor)
-        pm_error("The maxval of the colormap (%u) is not equal to the "
-                 "maxval of the input image (%u).  This is allowable only "
-                 "if you are doing an approximate mapping (i.e. you don't "
-                 "specify -firstisdefault or -missingcolor)",
-                 (unsigned int)outpamP->maxval, (unsigned int)inpamP->maxval);
-
     selectDepthAdjustment(inpamP, outpamP->depth, &depthAdjustment);
 
     usehash = TRUE;
@@ -1120,6 +1115,13 @@ remap(FILE *             const ifP,
         outpam.width  = inpam.width;
         outpam.height = inpam.height;
 
+        if (outpam.maxval != inpam.maxval && defaultColor)
+            pm_error("The maxval of the colormap (%u) is not equal to the "
+                     "maxval of the input image (%u).  This is allowable only "
+                     "if you are doing an approximate mapping (i.e. you don't "
+                     "specify -firstisdefault or -missingcolor)",
+                     (unsigned int)outpam.maxval, (unsigned int)inpam.maxval);
+
         pnm_writepaminit(&outpam);
 
         /* Set up so input buffers have extra space as needed to
diff --git a/editor/pnmshear.c b/editor/pnmshear.c
index c705c261..45d74c6f 100644
--- a/editor/pnmshear.c
+++ b/editor/pnmshear.c
@@ -54,7 +54,7 @@ parseCommandLine(int argc, const char ** argv,
     OPTENT3(0, "noantialias",      OPT_FLAG,  NULL, &cmdlineP->noantialias, 0);
     OPTENT3(0, "background",       OPT_STRING, &cmdlineP->background,
             &backgroundSpec, 0);
-    
+
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;
     opt.allowNegNum = TRUE;
@@ -106,21 +106,21 @@ makeNewXel(xel *  const outputXelP,
     switch (PNM_FORMAT_TYPE(format)) {
     case PPM_TYPE:
         PPM_ASSIGN(*outputXelP,
-                   (fracnew0 * PPM_GETR(prevXel) 
-                    + omfracnew0 * PPM_GETR(curXel) 
+                   (fracnew0 * PPM_GETR(prevXel)
+                    + omfracnew0 * PPM_GETR(curXel)
                     + HALFSCALE) / SCALE,
-                   (fracnew0 * PPM_GETG(prevXel) 
-                    + omfracnew0 * PPM_GETG(curXel) 
+                   (fracnew0 * PPM_GETG(prevXel)
+                    + omfracnew0 * PPM_GETG(curXel)
                     + HALFSCALE) / SCALE,
-                   (fracnew0 * PPM_GETB(prevXel) 
-                    + omfracnew0 * PPM_GETB(curXel) 
+                   (fracnew0 * PPM_GETB(prevXel)
+                    + omfracnew0 * PPM_GETB(curXel)
                     + HALFSCALE) / SCALE );
         break;
-        
+
     default:
         PNM_ASSIGN1(*outputXelP,
-                    (fracnew0 * PNM_GET1(prevXel) 
-                     + omfracnew0 * PNM_GET1(curXel) 
+                    (fracnew0 * PNM_GET1(prevXel)
+                     + omfracnew0 * PNM_GET1(curXel)
                      + HALFSCALE) / SCALE );
         break;
     }
@@ -130,9 +130,9 @@ makeNewXel(xel *  const outputXelP,
 
 static void
 shearRow(xel *        const xelrow,
-         unsigned int const cols, 
+         unsigned int const cols,
          xel *        const newxelrow,
-         unsigned int const newcols, 
+         unsigned int const newcols,
          double       const shearCols,
          int          const format,
          xel          const bgxel,
@@ -140,7 +140,7 @@ shearRow(xel *        const xelrow,
 /*----------------------------------------------------------------------------
    Shear the row 'xelrow' by 'shearCols' columns, and return the result as
    'newxelrow'.  They are 'cols' and 'newcols' columns wide, respectively.
-   
+
    Fill in the part of the output row that doesn't contain image data with
    'bgxel'.
 
@@ -152,14 +152,14 @@ shearRow(xel *        const xelrow,
     unsigned int const intShearCols = (unsigned int) shearCols;
 
     assert(shearCols >= 0.0);
-        
+
     if (antialias) {
         const long fracnew0 = (shearCols - intShearCols) * SCALE;
         const long omfracnew0 = SCALE - fracnew0;
 
         unsigned int col;
         xel prevXel;
-            
+
         for (col = 0; col < newcols; ++col)
             newxelrow[col] = bgxel;
 
@@ -170,7 +170,7 @@ shearRow(xel *        const xelrow,
                        format);
             prevXel = xelrow[col];
         }
-        if (fracnew0 > 0) 
+        if (fracnew0 > 0)
             /* Need to add a column for what's left over */
             makeNewXel(&newxelrow[intShearCols + cols],
                        bgxel, prevXel, fracnew0, omfracnew0, format);
@@ -197,7 +197,7 @@ backgroundColor(const char * const backgroundColorName,
 
     if (backgroundColorName) {
         retval = pnm_parsecolorxel(backgroundColorName, maxval, format);
-    } else 
+    } else
         retval = pnm_backgroundxelrow(topRow, cols, maxval, format);
 
     return retval;
@@ -212,10 +212,13 @@ main(int argc, const char * argv[]) {
     xel * xelrow;
     xel * newxelrow;
     xel bgxel;
-    int rows, cols, format; 
-    int newformat, newcols; 
-    int row;
-    xelval maxval, newmaxval;
+    int rows, cols;
+    int format;
+    unsigned int newcols;
+    int newformat;
+    unsigned int row;
+    xelval maxval;
+    xelval newmaxval;
     double shearfac;
     double newcolsD;
 
@@ -230,6 +233,16 @@ main(int argc, const char * argv[]) {
     pnm_readpnminit(ifP, &cols, &rows, &maxval, &format);
     xelrow = pnm_allocrow(cols);
 
+    shearfac = tan(cmdline.angle);
+
+    newcolsD = (double) rows * fabs(shearfac) + cols + 0.999999;
+    if (newcolsD > INT_MAX-2)
+        pm_error("angle is too close to +/-90 degrees; "
+                 "output image too wide for computation");
+    else
+        newcols = (unsigned int) newcolsD;
+
+
     /* Promote PBM files to PGM. */
     if (!cmdline.noantialias && PNM_FORMAT_TYPE(format) == PBM_TYPE) {
         newformat = PGM_TYPE;
@@ -241,18 +254,9 @@ main(int argc, const char * argv[]) {
         newmaxval = maxval;
     }
 
-    shearfac = fabs(tan(cmdline.angle));
-
-    newcolsD = (double) rows * shearfac + cols + 0.999999;
-    if (newcolsD > INT_MAX-2)
-        pm_error("angle is too close to +/-90 degrees; "
-                 "output image too wide for computation");
-    else
-        newcols = (int) newcolsD;
-
     pnm_writepnminit(stdout, newcols, rows, newmaxval, newformat, 0);
     newxelrow = pnm_allocrow(newcols);
-    
+
     for (row = 0; row < rows; ++row) {
         double shearCols;
 
@@ -262,17 +266,17 @@ main(int argc, const char * argv[]) {
             bgxel = backgroundColor(cmdline.background,
                                     xelrow, cols, newmaxval, format);
 
-        if (cmdline.angle > 0.0)
+        if (shearfac > 0.0)
             shearCols = row * shearfac;
         else
-            shearCols = (rows - row) * shearfac;
+            shearCols = (rows - row) * -shearfac;
 
-        shearRow(xelrow, cols, newxelrow, newcols, 
+        shearRow(xelrow, cols, newxelrow, newcols,
                  shearCols, format, bgxel, !cmdline.noantialias);
 
         pnm_writepnmrow(stdout, newxelrow, newcols, newmaxval, newformat, 0);
     }
-    
+
     pm_close(ifP);
     pm_close(stdout);
 
diff --git a/editor/pnmstitch.c b/editor/pnmstitch.c
index eae5e1b9..b27445b0 100644
--- a/editor/pnmstitch.c
+++ b/editor/pnmstitch.c
@@ -20,7 +20,7 @@
  * otherwise) arising in any way out of the use of this software, even if
  * advised of the possibility of such damage.
  *
- * Any restrictions or encumberances added to this source code or derivitives,
+ * Any restrictions or encumberances added to this source code or derivatives,
  * is prohibited.
  *
  *  Name: pnmstitch.c
@@ -1116,7 +1116,7 @@ OutputAlloc(Output     * const me,
 static void
 StraightThroughDeAlloc(Output * me)
 {
-    /* Trick the proper freeing of resouces on the Output Image */
+    /* Trick the proper freeing of resources on the Output Image */
     me->image->pam.height = 1;
     OutputDeAlloc(me);
 } /* StraightThroughDeAlloc() - end */
@@ -1131,7 +1131,7 @@ StraightThroughAlloc(Output     * const me,
     if (OutputAlloc(me, file, width, height, prototype) == FALSE) {
         StraightThroughDeAlloc(me);
     }
-    /* Trick the proper allocation of resouces on the Output Image */
+    /* Trick the proper allocation of resources on the Output Image */
     me->image->pam.height = 1;
     me->image->tuple = pnm_allocpamarray(&me->image->pam);
     if (me->image->tuple == (tuple **)NULL) {
@@ -1322,10 +1322,10 @@ Output OutputMethods[] = {
 /* Stitcher Methods */
 
 /* These names are for the 8 parameters of a stitch, in any of the 3
-   methods this program presently implements.  Each is a subscript in
+   methods this program currently implements.  Each is a subscript in
    the parms[] array for the Stitcher object that represents a linear
-   stitching method.  
-   
+   stitching method.
+
    There are also other sets of names for the 8 parameters, such as
    Rotate_a.  I don't know why.  Maybe historical.
 */
diff --git a/editor/ppmbrighten b/editor/ppmbrighten
new file mode 100755
index 00000000..f5de436f
--- /dev/null
+++ b/editor/ppmbrighten
@@ -0,0 +1,60 @@
+#! /bin/sh
+
+# This is just for backward compatibility.  New applications should use
+# 'pambrighten'.
+
+# We don't try very hard to respond well to invalid syntax, because backward
+# compatibility is mostly like existing, working applications.
+
+pambrightenOpts=''
+normalize='no'
+expectValue='no'
+
+for word in "$@"; do
+
+    if test "$expectValue" = 'yes'; then
+        # This is the value of an option, like "40" in "-saturation 40"
+        pambrightenOpts="$pambrightenOpts $word"
+        expectValue='no'
+    else
+        # 'word_one_hyphen' is 'word' except if 'word' is a double-hyphen
+        # option, 'word_one_hyphen' is the single-hyphen version of it.
+        # E.g. word=--saturation word_one_hyphen=-saturation .
+        word_one_hyphen=$(echo "$word" | sed s/^--/-/ )
+    
+        case $word_one_hyphen in
+            -version )
+                pambrighten -version; exit $?
+                ;;
+            -normalize|-normaliz|-normali|-normal|-norma|-norm|-nor|-no|-n)
+                normalize='yes'
+                ;;
+            -*=*)
+                pambrightenOpts="$pambrightenOpts $word"
+                # This is an option with value such as "-saturation=40"
+                ;;
+            -*)
+                pambrightenOpts="$pambrightenOpts $word"
+                # Starts with hyphen, no equals sign, so the next word is the
+                # option's value (note that the only valid ppmbrighten flag
+                # option is -normalized, handled above).
+                #
+                # E.g. "-saturation 40"
+                expectValue='yes'
+                ;;
+            *)
+                # Not an option or option value - only non-option argument
+                # ppmbrighten has is optional input file name
+                infile="$word"
+                ;;
+                
+        esac
+    fi
+done
+
+if test "$normalize" = 'yes'; then
+    pnmnorm -bsingle -wsingle -colorvalue -keephues $infile | \
+        pambrighten $pambrightenOpts | ppmtoppm
+else
+    pambrighten $pambrightenOpts $infile | ppmtoppm
+fi
diff --git a/editor/ppmbrighten.c b/editor/ppmbrighten.c
deleted file mode 100644
index 0446bb75..00000000
--- a/editor/ppmbrighten.c
+++ /dev/null
@@ -1,218 +0,0 @@
-/*=============================================================================
-                              ppmbrighten
-===============================================================================
-  Change Value and Saturation of PPM image.
-=============================================================================*/
-
-#include "pm_c_util.h"
-#include "ppm.h"
-#include "shhopt.h"
-#include "mallocvar.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 saturation;
-    float value;
-    unsigned int normalize;
-};
-
-
-
-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 saturationSpec, valueSpec;
-    int saturationOpt, valueOpt;
-
-    MALLOCARRAY_NOFAIL(option_def, 100);
-
-    option_def_index = 0;   /* incremented by OPTENT3 */
-    OPTENT3(0, "saturation",  OPT_INT,    &saturationOpt,
-            &saturationSpec,      0 );
-    OPTENT3(0, "value",       OPT_INT,    &valueOpt,
-            &valueSpec,           0 );
-    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, (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%%.  "
-                     "You specified %d", saturationOpt);
-        else
-            cmdlineP->saturation = 1.0 + (float)saturationOpt / 100;
-    } else
-        cmdlineP->saturation = 1.0;
-
-    if (valueSpec) {
-        if (valueOpt < -100)
-            pm_error("Value reduction cannot be more than 100%%.  "
-                     "You specified %d", valueOpt);
-        else
-            cmdlineP->value = 1.0 + (float)valueOpt / 100;
-    } else
-        cmdlineP->value = 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
-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;
-    double minValue, maxValue;
-    unsigned int row;
-
-    pixelrow = ppm_allocrow(cols);
-
-    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) {
-            struct hsv const pixhsv =
-                ppm_hsv_from_color(pixelrow[col], maxval);
-
-            maxValue = MAX(maxValue, pixhsv.v);
-            minValue = MIN(minValue, pixhsv.v);
-        }
-    }
-    ppm_freerow(pixelrow);
-
-    *minValueP = minValue;
-    *maxValueP = maxValue;
-}
-
-
-
-int
-main(int argc, const char ** argv) {
-
-    double const EPSILON = 1.0e-5;
-    struct CmdlineInfo cmdline;
-    FILE * ifP;
-    pixel * pixelrow;
-    pixval maxval;
-    int rows, cols, format, row;
-    double minValue, maxValue;
-
-    pm_proginit(&argc, argv);
-
-    parseCommandLine(argc, argv, &cmdline);
-
-    if (cmdline.normalize)
-        ifP = pm_openr_seekable(cmdline.inputFileName);
-    else
-        ifP = pm_openr(cmdline.inputFileName);
-
-    ppm_readppminit(ifP, &cols, &rows, &maxval, &format);
-
-    if (cmdline.normalize) {
-        pm_filepos rasterPos;
-        pm_tell2(ifP, &rasterPos, sizeof(rasterPos));
-        getMinMax(ifP, cols, rows, maxval, format, &minValue, &maxValue);
-        pm_seek2(ifP, &rasterPos, sizeof(rasterPos));
-        if (maxValue - minValue > EPSILON) {
-            pm_message("Minimum value %.0f%% of full intensity "
-                       "being remapped to zero.",
-                       (minValue * 100.0));
-            pm_message("Maximum value %.0f%% of full intensity "
-                       "being remapped to full.",
-                       (maxValue * 100.0));
-        } else
-            pm_message("Sole value of %.0f%% of full intensity "
-                       "not being remapped",
-                       (maxValue * 100.0));
-    }
-
-    pixelrow = ppm_allocrow(cols);
-
-    ppm_writeppminit(stdout, cols, rows, maxval, 0);
-
-    for (row = 0; row < rows; ++row) {
-        unsigned int col;
-
-        ppm_readppmrow(ifP, pixelrow, cols, maxval, format);
-
-        for (col = 0; col < cols; ++col) {
-            struct hsv pixhsv;
-
-            pixhsv = ppm_hsv_from_color(pixelrow[col], maxval);
-                /* initial value */
-
-            if (cmdline.normalize) {
-                if (maxValue - minValue > EPSILON)
-                    pixhsv.v = (pixhsv.v - minValue) / (maxValue - minValue);
-            }
-            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 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/ppmfade b/editor/ppmfade
index dcd7bf26..7dc533a1 100755
--- a/editor/ppmfade
+++ b/editor/ppmfade
@@ -41,6 +41,8 @@ exec perl -w -x -S -- "$0" "$@"
 ##############################################################################
 use strict;
 
+use File::Temp;
+
 sub doVersionHack($) {
     my ($argvR) = @_;
 
@@ -52,7 +54,9 @@ sub doVersionHack($) {
     }
 }
 
+my $tmpdir;
 
+$tmpdir = File::Temp::tempdir("ppmfade.XXXXXX", CLEANUP => 1);
 
 my $SPREAD =  1;
 my $SHIFT =   2;
@@ -151,18 +155,18 @@ print("Frames are " . $width . "W x " . $height . "H\n");
 
 if ($first_file eq "undefined") {
     print "Fading from black to ";
-    system("ppmmake \\#000 $width $height >junk1$$.ppm");
+    system("ppmmake \\#000 $width $height >$tmpdir/junk1.ppm");
 } else {
     print "Fading from $first_file to ";
-    system("cp", $first_file, "junk1$$.ppm");
+    system("cp", $first_file, "$tmpdir/junk1.ppm");
 }
 
 if ($last_file eq "undefined") {
     print "black.\n";
-    system("ppmmake \\#000 $width $height >junk2$$.ppm");
+    system("ppmmake \\#000 $width $height >$tmpdir/junk2.ppm");
 } else {
     print "$last_file\n";
-    system("cp", $last_file, "junk2$$.ppm");
+    system("cp", $last_file, "$tmpdir/junk2.ppm");
 }
 
 #
@@ -170,14 +174,14 @@ if ($last_file eq "undefined") {
 #
 
 # Here's what our temporary files are:
-#   junk1$$.ppm: The original (fade-from) image
-#   junk2$$.ppm: The target (fade-from) image
-#   junk3$$.ppm: The frame of the fade for the current iteration of the 
-#                the for loop.
-#   junk1a$$.ppm: If the fade involves a ppmmix sequence from one intermediate
-#                 image to another, this is the first frame of that 
-#                 sequence.
-#   junk2a$$.ppm: This is the last frame of the above-mentioned ppmmix sequence
+#   junk1.ppm: The original (fade-from) image
+#   junk2.ppm: The target (fade-from) image
+#   junk3.ppm: The frame of the fade for the current iteration of the 
+#              the for loop.
+#   junk1a.ppm: If the fade involves a ppmmix sequence from one intermediate
+#               image to another, this is the first frame of that 
+#               sequence.
+#   junk2a.ppm: This is the last frame of the above-mentioned ppmmix sequence
 
 my $i;    # Frame number
 for ($i = 1; $i <= $nframes; $i++) {
@@ -185,147 +189,151 @@ for ($i = 1; $i <= $nframes; $i++) {
     if ($mode eq $SPREAD) {
         if ($i <= 10) {
             my $n = $spline20[$i] * 100;
-            system("ppmspread $n junk1$$.ppm >junk3$$.ppm");
+            system("ppmspread $n $tmpdir/junk1.ppm >$tmpdir/junk3.ppm");
         } elsif ($i <= 20) {
             my $n;
             $n = $spline20[$i] * 100;
-            system("ppmspread $n junk1$$.ppm >junk1a$$.ppm");
+            system("ppmspread $n $tmpdir/junk1.ppm >$tmpdir/junk1a.ppm");
             $n = (1-$spline20[$i-10]) * 100;
-            system("ppmspread $n junk2$$.ppm >junk2a$$.ppm");
+            system("ppmspread $n $tmpdir/junk2.ppm >$tmpdir/junk2a.ppm");
             $n = $spline10[$i-10];
-            system("ppmmix $n junk1a$$.ppm junk2a$$.ppm >junk3$$.ppm");
+            system("ppmmix $n $tmpdir/junk1a.ppm $tmpdir/junk2a.ppm " .
+                   ">$tmpdir/junk3.ppm");
         } else {
             my $n = (1-$spline20[$i-10])*100;
-            system("ppmspread $n junk2$$.ppm >junk3$$.ppm");
+            system("ppmspread $n $tmpdir/junk2.ppm >$tmpdir/junk3.ppm");
         }
     } elsif ($mode eq $SHIFT) {
         if ($i <= 10) {
             my $n = $spline20[$i] * 100;
-            system("ppmshift $n junk1$$.ppm >junk3$$.ppm");
+            system("ppmshift $n $tmpdir/junk1.ppm >$tmpdir/junk3.ppm");
         } elsif ($i <= 20) {
             my $n;
             $n = $spline20[$i] * 100;
-            system("ppmshift $n junk1$$.ppm >junk1a$$.ppm");
+            system("ppmshift $n $tmpdir/junk1.ppm >$tmpdir/junk1a.ppm");
             $n = (1-$spline20[$i-10])*100;
-            system("ppmshift $n junk2$$.ppm >junk2a$$.ppm");
+            system("ppmshift $n junk2.ppm >junk2a.ppm");
             $n = $spline10[$i-10];
-            system("ppmmix $n junk1a$$.ppm junk2a$$.ppm >junk3$$.ppm");
+            system("ppmmix $n junk1a.ppm junk2a.ppm >junk3.ppm");
         } else {
             my $n = (1-$spline20[$i-10]) * 100;
-            system("ppmshift $n junk2$$.ppm >junk3$$.ppm");
+            system("ppmshift $n junk2.ppm >junk3.ppm");
         }
     } elsif ($mode eq $RELIEF) {
         if ($i == 1) {
-            system("ppmrelief junk1$$.ppm >junk1r$$.ppm");
+            system("ppmrelief junk1.ppm >junk1r.ppm");
         }
         if ($i <= 10) {
             my $n = $spline10[$i];
-            system("ppmmix $n junk1$$.ppm junk1r$$.ppm >junk3$$.ppm");
+            system("ppmmix $n junk1.ppm junk1r.ppm >junk3.ppm");
         } elsif ($i <= 20) {
             my $n = $spline10[$i-10];
-            system("ppmmix $n junk1r$$.ppm junk2r$$.ppm >junk3$$.ppm");
+            system("ppmmix $n junk1r.ppm junk2r.ppm >junk3.ppm");
         } else {
             my $n = $spline10[$i-20];
-            system("ppmmix $n junk2r$$.ppm junk2$$.ppm >junk3$$.ppm");
+            system("ppmmix $n junk2r.ppm junk2.ppm >junk3.ppm");
         }
         if ($i == 10) {
-            system("ppmrelief junk2$$.ppm >junk2r$$.ppm");
+            system("ppmrelief junk2.ppm >junk2r.ppm");
         }
     } elsif ($mode eq $OIL) {
         if ($i == 1) {
-            system("ppmtopgm junk1$$.ppm | pgmoil >junko$$.ppm");
-            system("rgb3toppm junko$$.ppm junko$$.ppm junko$$.ppm " .
-                   ">junk1o$$.ppm");
+            system("ppmtopgm junk1.ppm | pgmoil >junko.ppm");
+            system("rgb3toppm junko.ppm junko.ppm junko.ppm " .
+                   ">junk1o.ppm");
         }
         if ($i <= 10) {
             my $n = $spline10[$i];
-            system("ppmmix $n junk1$$.ppm junk1o$$.ppm >junk3$$.ppm");
+            system("ppmmix $n junk1.ppm junk1o.ppm >junk3.ppm");
         } elsif ($i <= 20) {
             my $n = $spline10[$i-10];
-            system("ppmmix $n junk1o$$.ppm junk2o$$.ppm >junk3$$.ppm");
+            system("ppmmix $n junk1o.ppm junk2o.ppm >junk3.ppm");
         } else {
             my $n = $spline10[$i-20];
-            system("ppmmix $n junk2o$$.ppm junk2$$.ppm >junk3$$.ppm");
+            system("ppmmix $n junk2o.ppm junk2.ppm >junk3.ppm");
         }
         if ($i == 10) {
-            system("ppmtopgm junk2$$.ppm | pgmoil >junko$$.ppm");
-            system("rgb3toppm junko$$.ppm junko$$.ppm junko$$.ppm " .
-                   ">junk2o$$.ppm");
+            system("ppmtopgm junk2.ppm | pgmoil >junko.ppm");
+            system("rgb3toppm junko.ppm junko.ppm junko.ppm " .
+                   ">junk2o.ppm");
         }
     } elsif ($mode eq $EDGE) {
         if ($i == 1) {
-            system("ppmtopgm junk1$$.ppm | pgmedge >junko$$.ppm");
-            system("rgb3toppm junko$$.ppm junko$$.ppm junko$$.ppm " .
-                   ">junk1o$$.ppm");
+            system("ppmtopgm junk1.ppm | pgmedge >junko.ppm");
+            system("rgb3toppm junko.ppm junko.ppm junko.ppm " .
+                   ">junk1o.ppm");
         }
         if ($i <= 10) {
             my $n = $spline10[$i];
-            system("ppmmix $n junk1$$.ppm junk1o$$.ppm >junk3$$.ppm");
+            system("ppmmix $n junk1.ppm junk1o.ppm >junk3.ppm");
         } elsif ($i <= 20) {
             my $n = $spline10[$i-10];
-            system("ppmmix $n junk1o$$.ppm junk2o$$.ppm >junk3$$.ppm");
+            system("ppmmix $n junk1o.ppm junk2o.ppm >junk3.ppm");
         } else {
             my $n = $spline10[$i-20];
-            system("ppmmix $n junk2o$$.ppm junk2$$.ppm >junk3$$.ppm");
+            system("ppmmix $n junk2o.ppm junk2.ppm >junk3.ppm");
         }
         if ($i == 10) {
-            system("ppmtopgm junk2$$.ppm | pgmedge >junko$$.ppm");
-            system("rgb3toppm junko$$.ppm junko$$.ppm junko$$.ppm " .
-                   ">junk2o$$.ppm");
+            system("ppmtopgm junk2.ppm | pgmedge >junko.ppm");
+            system("rgb3toppm junko.ppm junko.ppm junko.ppm " .
+                   ">junk2o.ppm");
         } 
     } elsif ($mode eq $BENTLEY) {
         if ($i == 1) {
-            system("ppmtopgm junk1$$.ppm | pgmbentley >junko$$.ppm");
-            system("rgb3toppm junko$$.ppm junko$$.ppm junko$$.ppm " .
-                   ">junk1o$$.ppm");
+            system("ppmtopgm junk1.ppm | pgmbentley >junko.ppm");
+            system("rgb3toppm junko.ppm junko.ppm junko.ppm " .
+                   ">junk1o.ppm");
         }
         if ($i <= 10) {
             my $n = $spline10[$i];
-            system("ppmmix $n junk1$$.ppm junk1o$$.ppm >junk3$$.ppm");
+            system("ppmmix $n junk1.ppm junk1o.ppm >junk3.ppm");
         } elsif ($i <= 20) {
             my $n = $spline10[$i-10];
-            system("ppmmix $n junk1o$$.ppm junk2o$$.ppm >junk3$$.ppm");
+            system("ppmmix $n junk1o.ppm junk2o.ppm >junk3.ppm");
         } else {
             my $n = $spline10[$i-20];
-            system("ppmmix $n junk2o$$.ppm junk2$$.ppm >junk3$$.ppm");
+            system("ppmmix $n $tmpdir/junk2o.ppm $tmpdir/junk2.ppm " .
+                   ">$tmpdir/junk3.ppm");
         }
         if ($i == 10) {
-            system("ppmtopgm junk2$$.ppm | pgmbentley >junko$$.ppm");
-            system("rgb3toppm junko$$.ppm junko$$.ppm junko$$.ppm " .
-                   ">junk2o$$.ppm");
+               system("ppmtopgm $tmpdir/junk2.ppm | pgmbentley " .
+                      ">$tmpdir/junko.ppm");
+            system("rgb3toppm $tmpdir/junko.ppm $tmpdir/junko.ppm " .
+                   "$tmpdir/junko.ppm " .
+                   ">$tmpdir/junk2o.ppm");
         }
     } elsif ($mode eq $BLOCK) {
         if ($i <= 10) {
             my $n = 1 - 1.9*$spline20[$i];
-            system("pamscale $n junk1$$.ppm | " .
-                   "pamscale -width $width -height $height >junk3$$.ppm");
+            system("pamscale $n $tmpdir/junk1.ppm | " .
+                   "pamscale -width $width -height $height " .
+                   ">$tmpdir/junk3.ppm");
         } elsif ($i <= 20) {
             my $n = $spline10[$i-10];
-            system("ppmmix $n junk1a$$.ppm junk2a$$.ppm >junk3$$.ppm");
+            system("ppmmix $n $tmpdir/junk1a.ppm $tmpdir/junk2a.ppm " .
+                   ">$tmpdir/junk3.ppm");
         } else {
             my $n = 1 - 1.9*$spline20[31-$i];
-            system("pamscale $n junk2$$.ppm | " .
-                   "pamscale -width $width -height $height >junk3$$.ppm");
+            system("pamscale $n $tmpdir/junk2.ppm | " .
+                   "pamscale -width $width -height $height " .
+                   ">$tmpdir/junk3.ppm");
         }
         if ($i == 10) {
-            system("cp", "junk3$$.ppm", "junk1a$$.ppm");
-            system("pamscale $n junk2$$.ppm | " .
-                   "pamscale -width $width -height $height >junk2a$$.ppm");
+            system("cp", "$tmpdir/junk3.ppm", "$tmpdir/junk1a.ppm");
+            system("pamscale $n $tmpdir/junk2.ppm | " .
+                   "pamscale -width $width -height $height " .
+                   ">$tmpdir/junk2a.ppm");
         }    
     } elsif ($mode eq $MIX) {
         my $fade_factor = sqrt(1/($nframes-$i+1));
-        system("ppmmix $fade_factor junk1$$.ppm junk2$$.ppm >junk3$$.ppm");
+        system("ppmmix $fade_factor $tmpdir/junk1.ppm $tmpdir/junk2.ppm " .
+               ">$tmpdir/junk3.ppm");
     } else {
         print("Internal error: impossible mode value '$mode'\n");
     }
 
     my $outfile = sprintf("%s.%04d.ppm", $base_name, $i);
-    system("cp", "junk3$$.ppm", $outfile);
+    system("cp", "$tmpdir/junk3.ppm", $outfile);
 }
 
-#
-#  Clean up shop.
-#
-system("rm junk*$$.ppm");
-
 exit(0);
diff --git a/editor/ppmshadow b/editor/ppmshadow
index ae6b1b0f..438d4fa9 100755
--- a/editor/ppmshadow
+++ b/editor/ppmshadow
@@ -89,7 +89,7 @@ sub imageDimensions($) {
 sub backgroundColor($) {
     my ($fileName) = @_;
 #-----------------------------------------------------------------------------
-#  Return the color of the backround of the image in the file named $fileName.
+#  Return the color of the background of the image in the file named $fileName.
 #-----------------------------------------------------------------------------
     # We call the color of the top left pixel the background color.
 
@@ -198,7 +198,7 @@ if ($keeptemp) {
     mkdir($ourtmp, 0777) or
         die("Unable to create directory for temporary files '$ourtmp");
 } else {
-    $ourtmp = File::Temp::tempdir("$tmpdir/ppmshadowXXXX", UNLINK=>1);
+    $ourtmp = File::Temp::tempdir("$tmpdir/ppmshadowXXXX", CLEANUP=>1);
 }
 
 #   Apply defaults for arguments not specified
diff --git a/editor/specialty/pampaintspill.c b/editor/specialty/pampaintspill.c
index eb1888f7..7490fcef 100644
--- a/editor/specialty/pampaintspill.c
+++ b/editor/specialty/pampaintspill.c
@@ -6,7 +6,7 @@
  *
  * ----------------------------------------------------------------------
  *
- * Copyright (C) 2010 Scott Pakin <scott+pbm@pakin.org>
+ * Copyright (C) 2010-2021 Scott Pakin <scott+pbm@pakin.org>
  *
  * 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
@@ -45,11 +45,11 @@
 
 #include "mallocvar.h"
 #include "nstring.h"
+#include "rand.h"
 #include "shhopt.h"
 #include "pam.h"
 #include "pammap.h"
 
-
 static time_t const timeUpdateDelta = 30;
     /* Seconds between progress updates */
 static int const    minUpdates = 4;
@@ -67,6 +67,10 @@ struct cmdlineInfo {
     unsigned int all;
     float        power;
     unsigned int downsample;
+    unsigned int randomseedSpec;
+    unsigned int randomseed;
+    unsigned int nearSpec;
+    unsigned int near;
 };
 
 struct coords {
@@ -98,16 +102,20 @@ parseCommandLine(int argc, const char ** const argv,
     MALLOCARRAY_NOFAIL(option_def, 100);
     option_def_index = 0;          /* Incremented by OPTENTRY */
 
-    OPTENT3(0, "bgcolor",    OPT_STRING, &cmdlineP->bgcolor,    
+    OPTENT3(0, "bgcolor",    OPT_STRING, &cmdlineP->bgcolor,
             &bgcolorSpec, 0);
     OPTENT3(0, "wrap",       OPT_FLAG,   NULL,
             &cmdlineP->wrap,       0);
     OPTENT3(0, "all",        OPT_FLAG,   NULL,
             &cmdlineP->all,        0);
-    OPTENT3(0, "power",      OPT_FLOAT,  &cmdlineP->power,      
+    OPTENT3(0, "power",      OPT_FLOAT,  &cmdlineP->power,
             &powerSpec, 0);
-    OPTENT3(0, "downsample", OPT_UINT,   &cmdlineP->downsample, 
+    OPTENT3(0, "downsample", OPT_UINT,   &cmdlineP->downsample,
             &downsampleSpec, 0);
+    OPTENT3(0, "randomseed", OPT_UINT,   &cmdlineP->randomseed,
+            &cmdlineP->randomseedSpec, 0);
+    OPTENT3(0, "near",       OPT_UINT,   &cmdlineP->near,
+            &cmdlineP->nearSpec, 0);
 
     opt.opt_table = option_def;
     opt.short_allowed = 0;
@@ -124,6 +132,11 @@ parseCommandLine(int argc, const char ** const argv,
     if (!downsampleSpec)
         cmdlineP->downsample = 0;
 
+    if (cmdlineP->nearSpec) {
+        if (cmdlineP->near == 0)
+            pm_error("The -near option requires a positive argument");
+    }
+
     if (argc-1 < 1)
         cmdlineP->inputFilename = "-";
     else {
@@ -223,7 +236,9 @@ locatePaintSources(struct pam *            const pamP,
                    tuple **                const tuples,
                    tuple                   const bgColor,
                    unsigned int            const downsample,
-                   struct paintSourceSet * const paintSourcesP) {
+                   struct paintSourceSet * const paintSourcesP,
+                   bool                    const randomseedSpec,
+                   unsigned int            const randomseed) {
 /*--------------------------------------------------------------------
   Construct a list of all pixel coordinates in the input image that
   represent a non-background color.
@@ -248,21 +263,24 @@ locatePaintSources(struct pam *            const pamP,
     pm_message("Image contains %u background + %u non-background pixels",
                pamP->width * pamP->height - paintSources.size,
                paintSources.size);
-    
+
     /* Reduce the number of paint sources to reduce execution time. */
     if (downsample > 0 && downsample < paintSources.size) {
+        struct pm_randSt randSt;
         unsigned int i;
 
-        srand(pm_randseed());
+        pm_randinit(&randSt);
+        pm_srand2(&randSt, randomseedSpec, randomseed);
 
         for (i = 0; i < downsample; ++i) {
             unsigned int const swapIdx =
-                i + rand() % (paintSources.size - i);
+                i + pm_rand(&randSt) % (paintSources.size - i);
             struct coords const swapVal = paintSources.list[i];
 
             paintSources.list[i] = paintSources.list[swapIdx];
             paintSources.list[swapIdx] = swapVal;
         }
+        pm_randterm(&randSt);
         paintSources.size = downsample;
     }
 
@@ -281,7 +299,7 @@ euclideanDistanceSqr(const struct coords * const p0,
                      unsigned int          const width,
                      unsigned int          const height) {
 /*----------------------------------------------------------------------------
-   Return the square of the Euclidian distance between p0 and p1.
+   Return the square of the Euclidean distance between p0 and p1.
 -----------------------------------------------------------------------------*/
     double const deltax = (double) (int) (p1->x - p0->x);
     double const deltay = (double) (int) (p1->y - p0->y);
@@ -299,7 +317,7 @@ euclideanDistanceTorusSqr(const struct coords * const p0,
                           unsigned int          const width,
                           unsigned int          const height) {
 /*----------------------------------------------------------------------------
-   Return the square of the Euclidian distance between p0 and p1, assuming
+   Return the square of the Euclidean distance between p0 and p1, assuming
    it's a toroidal surface on which the top row curves around to meet the
    bottom and the left column to the right.
 -----------------------------------------------------------------------------*/
@@ -356,6 +374,70 @@ reportProgress(unsigned int const rowsComplete,
 
 
 
+struct distanceList {
+    struct coords * sources;  /* malloc'ed */
+        /* The list of places in the image from which paint comes */
+    double * distSqrs;        /* malloc'ed */
+        /* The list of squared distances from the current point */
+    unsigned int size;
+        /* Number of entries in sources[] */
+};
+
+
+
+static void
+computeDistances(struct pam *           const pamP,
+                 struct coords          const target,
+                 struct paintSourceSet  const paintSources,
+                 distFunc_t *           const distFunc,
+                 bool                   const nearOnly,
+                 unsigned int           const numNear,
+                 struct distanceList *  const distancesP) {
+
+    unsigned int ps;
+
+    /* Acquire a list of all distances. */
+    distancesP->size = 0;
+    for (ps = 0; ps < paintSources.size; ++ps) {
+        struct coords const source = paintSources.list[ps];
+        double const distSqr =
+            (*distFunc)(&target, &source,
+                        pamP->width, pamP->height);
+        distancesP->sources[distancesP->size]  = source;
+        distancesP->distSqrs[distancesP->size] = distSqr;
+        ++distancesP->size;
+    }
+
+    /* If requested, truncate the list to the smallest numNear distances. */
+    if (nearOnly && numNear < distancesP->size) {
+        unsigned int i;
+
+        /* Perform a partial sort -- just enough to identify the numNear
+           smallest distances.  For performance reasons we assume that
+           numNear is much less than paintSources.size (say, less than
+           log2(paintSources.size)).
+        */
+        for (i = 0; i < numNear; ++i) {
+            unsigned int j;
+            for (j = i + 1; j < distancesP->size; ++j) {
+                if (distancesP->distSqrs[i] > distancesP->distSqrs[j]) {
+                    /* Swap elements i and j. */
+                    struct coords const src   = distancesP->sources[i];;
+                    double        const dist2 = distancesP->distSqrs[i];
+
+                    distancesP->sources[i]  = distancesP->sources[j];
+                    distancesP->distSqrs[i] = distancesP->distSqrs[j];
+                    distancesP->sources[j]  = src;
+                    distancesP->distSqrs[j] = dist2;
+                }
+            }
+        }
+        distancesP->size = numNear;
+    }
+}
+
+
+
 static void
 spillOnePixel(struct pam *          const pamP,
               struct coords         const target,
@@ -363,37 +445,38 @@ spillOnePixel(struct pam *          const pamP,
               distFunc_t *          const distFunc,
               double                const distPower,
               tuple                 const outTuple,
-              double *              const newColor) {
+              double *              const newColor,
+              bool                  const nearOnly,
+              unsigned int          const numNear,
+              struct distanceList * const distancesP) {
 
     unsigned int plane;
-    unsigned int ps;
+    unsigned int d;
     double       totalWeight;
 
     for (plane = 0; plane < pamP->depth; ++plane)
         newColor[plane] = 0.0;
+    computeDistances(pamP, target, paintSources, distFunc,
+                     nearOnly, numNear, distancesP);
     totalWeight = 0.0;
-    for (ps = 0; ps < paintSources.size; ++ps) {
-        struct coords const source = paintSources.list[ps];
-        double const distSqr =
-            (*distFunc)(&target, &source,
-                        pamP->width, pamP->height);
+    for (d = 0; d < distancesP->size; ++d) {
+        double        const distSqr = distancesP->distSqrs[d];
+        struct coords const source  = distancesP->sources[d];
 
-        if (distSqr > 0.0) {
-            /* We do special cases for some common cases with code
-               that is much faster than pow().
-            */
-            double const weight =
-                distPower == -2.0 ? 1.0 / distSqr :
-                distPower == -1.0 ? 1.0 / sqrt(distSqr):
-                pow(distSqr, distPower/2);
+        /* We do special cases for some common cases with code
+           that is much faster than pow().
+        */
+        double const weight =
+            distPower == -2.0 ? 1.0 / distSqr :
+            distPower == -1.0 ? 1.0 / sqrt(distSqr):
+            pow(distSqr, distPower/2);
 
-            unsigned int plane;
+        unsigned int plane;
 
-            for (plane = 0; plane < pamP->depth; ++plane)
-                newColor[plane] += weight * source.color[plane];
+        for (plane = 0; plane < pamP->depth; ++plane)
+            newColor[plane] += weight * source.color[plane];
 
-            totalWeight += weight;
-        }
+        totalWeight += weight;
     }
     for (plane = 0; plane < pamP->depth; ++plane)
         outTuple[plane] = (sample) (newColor[plane] / totalWeight);
@@ -409,6 +492,8 @@ produceOutputImage(struct pam *          const pamP,
                    distFunc_t *          const distFunc,
                    double                const distPower,
                    bool                  const all,
+                   bool                  const nearOnly,
+                   unsigned int          const numNear,
                    tuple ***             const outtuplesP) {
 /*--------------------------------------------------------------------
   Color each background pixel (or, if allPixels is 1, all pixels)
@@ -424,10 +509,14 @@ produceOutputImage(struct pam *          const pamP,
     rowsComplete = 0;
     #pragma omp parallel for
     for (row = 0; row < pamP->height; ++row) {
-        struct coords   target;
-        double        * newColor;
-        
+        struct coords         target;
+        double *              newColor;   /* malloc'ed */
+        struct distanceList * distancesP; /* malloc'ed */
+
         MALLOCARRAY(newColor, pamP->depth);
+        MALLOCVAR_NOFAIL(distancesP);
+        MALLOCARRAY_NOFAIL(distancesP->sources,  paintSources.size);
+        MALLOCARRAY_NOFAIL(distancesP->distSqrs, paintSources.size);
 
         target.y = row;
         for (target.x = 0; target.x < pamP->width; ++target.x) {
@@ -436,13 +525,17 @@ produceOutputImage(struct pam *          const pamP,
 
             if (all || tupleEqualColor(pamP, targetTuple, bgColor))
                 spillOnePixel(pamP, target, paintSources, distFunc, distPower,
-                              outputTuple, newColor);
+                              outputTuple, newColor, nearOnly, numNear,
+                              distancesP);
             else
                 pnm_assigntuple(pamP,  outputTuple, targetTuple);
         }
         #pragma omp critical (rowTally)
         reportProgress(++rowsComplete, pamP->height);
 
+        free(distancesP->distSqrs);
+        free(distancesP->sources);
+        free(distancesP);
         free(newColor);
     }
     *outtuplesP = outtuples;
@@ -484,10 +577,12 @@ main(int argc, const char *argv[]) {
                pnm_colorname(&inPam, bgColor, PAM_COLORNAME_HEXOK));
 
     locatePaintSources(&inPam, inTuples, bgColor, cmdline.downsample,
-                       &paintSources);
+                       &paintSources,
+                       cmdline.randomseedSpec, cmdline.randomseed);
 
     produceOutputImage(&inPam, inTuples, bgColor, paintSources, distFunc,
-                       cmdline.power, cmdline.all, &outTuples);
+                       cmdline.power, cmdline.all,
+                       cmdline.nearSpec, cmdline.near, &outTuples);
 
     outPam = inPam;
     outPam.file = stdout;
@@ -498,3 +593,6 @@ main(int argc, const char *argv[]) {
 
     return 0;
 }
+
+
+
diff --git a/editor/specialty/pampop9.c b/editor/specialty/pampop9.c
index d6c61e4f..b92c7d6b 100644
--- a/editor/specialty/pampop9.c
+++ b/editor/specialty/pampop9.c
@@ -25,9 +25,6 @@
 
 #include "pam.h"
 
-static const char * const copyright = 
-  "(c) Robert Tinsley 2003 (http://www.thepoacher.net/contact)";
-
 static const char *usagestr = "pnmfile|- xtiles ytiles xdelta ydelta";
 
 int main(int argc, char *argv[])
diff --git a/editor/specialty/pgmabel.c b/editor/specialty/pgmabel.c
index 0f4233ac..5badfd19 100644
--- a/editor/specialty/pgmabel.c
+++ b/editor/specialty/pgmabel.c
@@ -44,7 +44,7 @@ static const char* const version="$VER: pgmabel 1.009 (24 Jan 2002)";
 #include "pgm.h"
 
 #ifndef PID2          /*  PI/2 (on AMIGA always defined) */
-#define PID2    1.57079632679489661923  
+#define PID2    1.57079632679489661923
 #endif
 
 
@@ -59,7 +59,7 @@ static double *aldl, *ardl;                /* pointer for weighting factors */
 **      xr    <-  array of the calculated elements of the row
 **      adl   <-  pre-calculated surface coefficient for each segment
 */
-static double 
+static double
 Sum ( int n, double *xr, int N, double *adl)
 {
     int k;
@@ -79,7 +79,7 @@ Sum ( int n, double *xr, int N, double *adl)
 **      R, N  <-  indizes of the coefficient
 **      r     <-  radial position of the center of the surface
 */
-static double 
+static double
 dr ( int R, double r,  int N)
 {
     double a;
@@ -95,7 +95,7 @@ dr ( int R, double r,  int N)
 **        N    <-  width of the array
 **        adl  <-  array with pre-calculated weighting factors
 */
-static void 
+static void
 abel ( float *y, int N, double *adl)
 {
     register int n;
@@ -126,7 +126,7 @@ abel ( float *y, int N, double *adl)
 /* ----------------------------------------------------------------------------
 ** printing a help message if Option -h(elp) is chosen
 */
-static void 
+static void
 help()
 {
     pm_message("-----------------------------------------------------------------");
@@ -142,7 +142,7 @@ help()
     pm_message("|   verbose : output of useful data                             |");
     pm_message("|   pgmfile : Name of a pgmfile (optional)                      |");
     pm_message("|                                                               |");
-    pm_message("| for further information please contact the manpage            |"); 
+    pm_message("| for further information please contact the manpage            |");
     pm_message("-----------------------------------------------------------------");
     pm_message("%s",version);     /* telling the version      */
     exit(-1);                     /* retur-code for no result */
@@ -267,11 +267,11 @@ int main( argc, argv )
     }
     for (col = 0; col < (cols-midcol); ++col)      /* factors for right side */
     {
-        for (tc = 0; tc < (cols-midcol); ++tc) 
+        for (tc = 0; tc < (cols-midcol); ++tc)
             ardl[col*(cols-midcol)+tc] = dr(col,tc+0.5,cols-midcol);
     }
 
-    /* abel-transformation for each row splitted in right and left side      */
+    /* abel-transformation for each row split into right and left side      */
     for ( row = 0; row < rows ; ++row )
     {
         pgm_readpgmrow( ifp, grayorig, cols, maxval, format );
@@ -308,3 +308,5 @@ int main( argc, argv )
     exit( 0 );                       /* end of procedure                     */
 }
 
+
+
diff --git a/editor/specialty/pnmindex.c b/editor/specialty/pnmindex.c
index 438fe058..2b39e4ec 100644
--- a/editor/specialty/pnmindex.c
+++ b/editor/specialty/pnmindex.c
@@ -1,5 +1,5 @@
 /*============================================================================
-                              pnmindex   
+                                pnmindex
 ==============================================================================
 
   build a visual index of a bunch of PNM images
@@ -32,7 +32,7 @@
 #include "nstring.h"
 #include "pnm.h"
 
-struct cmdlineInfo {
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
@@ -56,11 +56,11 @@ systemf(const char * const fmt,
         ...) {
 
     va_list varargs;
-    
+
     size_t dryRunLen;
-    
+
     va_start(varargs, fmt);
-    
+
     pm_vsnprintf(NULL, 0, fmt, varargs, &dryRunLen);
 
     va_end(varargs);
@@ -83,7 +83,7 @@ systemf(const char * const fmt,
             va_start(varargs, fmt);
 
             pm_vsnprintf(shellCommand, allocSize, fmt, varargs, &realLen);
-                
+
             assert(realLen == dryRunLen);
             va_end(varargs);
 
@@ -94,12 +94,12 @@ systemf(const char * const fmt,
             if (rc != 0)
                 pm_error("shell command '%s' failed.  rc %d",
                          shellCommand, rc);
-            
+
             pm_strfree(shellCommand);
         }
     }
 }
-        
+
 
 
 static const char *
@@ -168,8 +168,8 @@ shellQuote(const char * const arg) {
 
 
 static void
-parseCommandLine(int argc, char ** argv, 
-                 struct cmdlineInfo * const cmdlineP) {
+parseCommandLine(int argc, char ** argv,
+                 struct CmdlineInfo * const cmdlineP) {
 
     unsigned int option_def_index;
     optEntry *option_def;
@@ -183,13 +183,13 @@ parseCommandLine(int argc, char ** argv,
     MALLOCARRAY_NOFAIL(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENT3 */
-    OPTENT3(0, "black",       OPT_FLAG,   NULL,                  
+    OPTENT3(0, "black",       OPT_FLAG,   NULL,
             &cmdlineP->black,         0);
-    OPTENT3(0, "noquant",     OPT_FLAG,   NULL,                  
+    OPTENT3(0, "noquant",     OPT_FLAG,   NULL,
             &cmdlineP->noquant,       0);
-    OPTENT3(0, "quant",       OPT_FLAG,   NULL,                  
+    OPTENT3(0, "quant",       OPT_FLAG,   NULL,
             &quant,                   0);
-    OPTENT3(0, "verbose",     OPT_FLAG,   NULL,                  
+    OPTENT3(0, "verbose",     OPT_FLAG,   NULL,
             &cmdlineP->verbose,       0);
     OPTENT3(0, "size",        OPT_UINT,   &cmdlineP->size,
             &sizeSpec,                0);
@@ -202,7 +202,7 @@ parseCommandLine(int argc, char ** argv,
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
-    opt.allowNegNum = FALSE; 
+    opt.allowNegNum = FALSE;
 
     pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdline_p and others. */
@@ -212,7 +212,7 @@ parseCommandLine(int argc, char ** argv,
 
     if (!colorsSpec)
         cmdlineP->colors = 256;
-    
+
     if (!sizeSpec)
         cmdlineP->size = 100;
 
@@ -246,7 +246,7 @@ parseCommandLine(int argc, char ** argv,
 
 
 static void
-freeCmdline(struct cmdlineInfo const cmdline) {
+freeCmdline(struct CmdlineInfo const cmdline) {
 
     unsigned int i;
 
@@ -326,9 +326,9 @@ rowFileName(const char * const dirName,
             unsigned int const row) {
 
     const char * fileName;
-    
+
     pm_asprintf(&fileName, "%s/pi.%u", dirName, row);
-    
+
     return fileName;
 }
 
@@ -368,7 +368,7 @@ copyImage(const char * const inputFileName,
     systemf("cat %s > %s", inputFileNmToken, outputFileName);
 
     pm_strfree(inputFileNmToken);
-} 
+}
 
 
 
@@ -386,26 +386,26 @@ copyScaleQuantImage(const char * const inputFileName,
 
     switch (PNM_FORMAT_TYPE(format)) {
     case PBM_TYPE:
-        pm_asprintf(&scaleCommand, 
+        pm_asprintf(&scaleCommand,
                     "pamscale -quiet -xysize %u %u %s "
                     "| pgmtopbm > %s",
                     size, size, inputFileNmToken, outputFileName);
         break;
-        
+
     case PGM_TYPE:
-        pm_asprintf(&scaleCommand, 
+        pm_asprintf(&scaleCommand,
                     "pamscale -quiet -xysize %u %u %s >%s",
                     size, size, inputFileNmToken, outputFileName);
         break;
-        
+
     case PPM_TYPE:
         if (quant)
-            pm_asprintf(&scaleCommand, 
+            pm_asprintf(&scaleCommand,
                         "pamscale -quiet -xysize %u %u %s "
                         "| pnmquant -quiet %u > %s",
                         size, size, inputFileNmToken, colors, outputFileName);
         else
-            pm_asprintf(&scaleCommand, 
+            pm_asprintf(&scaleCommand,
                         "pamscale -quiet -xysize %u %u %s >%s",
                         size, size, inputFileNmToken, outputFileName);
         break;
@@ -426,7 +426,7 @@ formatTypeMax(int const typeA,
               int const typeB) {
 
     if (typeA == PPM_TYPE || typeB == PPM_TYPE)
-        return PPM_TYPE; 
+        return PPM_TYPE;
     else if (typeA == PGM_TYPE || typeB == PGM_TYPE)
         return PGM_TYPE;
     else
@@ -441,9 +441,9 @@ thumbnailFileName(const char * const dirName,
                   unsigned int const col) {
 
     const char * fileName;
-    
+
     pm_asprintf(&fileName, "%s/pi.%u.%u", dirName, row, col);
-    
+
     return fileName;
 }
 
@@ -464,7 +464,7 @@ thumbnailFileList(const char * const dirName,
         pm_error("Unable to allocate %u bytes for file list", maxListSize);
 
     list[0] = '\0';
-    
+
     for (col = 0; col < cols; ++col) {
         const char * const fileName = thumbnailFileName(dirName, row, col);
 
@@ -487,19 +487,28 @@ makeImageFile(const char * const thumbnailFileName,
               const char * const inputFileName,
               bool         const blackBackground,
               const char * const outputFileName) {
+/*----------------------------------------------------------------------------
+   Create one thumbnail image.  It consists of the image in the file named
+   'thumbnailFileName' with text of that name appended to the bottom.
 
+   Write the result to the file named 'outputFileName'.
+
+   'blackBackground' means give the image a black background where padding
+   is necessary and make the text white on black.  If false, give the image
+   a white background instead.
+-----------------------------------------------------------------------------*/
     const char * const blackWhiteOpt = blackBackground ? "-black" : "-white";
     const char * const invertStage   = blackBackground ? "| pnminvert " : "";
     const char * inputFileNmToken    = shellQuote(inputFileName);
 
     systemf("pbmtext %s %s"
-            "| pnmcat %s -topbottom %s - "
+            "| pamcat %s -topbottom %s - "
             "> %s",
             inputFileNmToken, invertStage, blackWhiteOpt,
             thumbnailFileName, outputFileName);
 
     pm_strfree(inputFileNmToken);
-}    
+}
 
 
 
@@ -519,21 +528,21 @@ makeThumbnail(const char *  const inputFileName,
     xelval maxval;
     const char * tmpfile;
     const char * fileName;
-        
+
     ifP = pm_openr(inputFileName);
     pnm_readpnminit(ifP, &imageCols, &imageRows, &maxval, &format);
     pm_close(ifP);
-    
+
     pm_asprintf(&tmpfile, "%s/pi.tmp", tempDir);
 
     if (imageCols < size && imageRows < size)
         copyImage(inputFileName, tmpfile);
     else
-        copyScaleQuantImage(inputFileName, tmpfile, format, 
+        copyScaleQuantImage(inputFileName, tmpfile, format,
                             size, quant, colors);
 
     fileName = thumbnailFileName(tempDir, row, col);
-        
+
     makeImageFile(tmpfile, inputFileName, black, fileName);
 
     unlink(tmpfile);
@@ -543,7 +552,7 @@ makeThumbnail(const char *  const inputFileName,
 
     *formatP = format;
 }
-        
+
 
 
 static void
@@ -552,7 +561,7 @@ unlinkThumbnailFiles(const char * const dirName,
                      unsigned int const cols) {
 
     unsigned int col;
-    
+
     for (col = 0; col < cols; ++col) {
         const char * const fileName = thumbnailFileName(dirName, row, col);
 
@@ -569,7 +578,7 @@ unlinkRowFiles(const char * const dirName,
                unsigned int const rows) {
 
     unsigned int row;
-    
+
     for (row = 0; row < rows; ++row) {
         const char * const fileName = rowFileName(dirName, row);
 
@@ -595,7 +604,7 @@ combineIntoRowAndDelete(unsigned int const row,
     const char * fileName;
     const char * quantStage;
     const char * fileList;
-    
+
     fileName = rowFileName(tempDir, row);
 
     unlink(fileName);
@@ -607,7 +616,7 @@ combineIntoRowAndDelete(unsigned int const row,
 
     fileList = thumbnailFileList(tempDir, row, cols);
 
-    systemf("pnmcat %s -leftright -jbottom %s "
+    systemf("pamcat %s -leftright -jbottom %s "
             "%s"
             ">%s",
             blackWhiteOpt, fileList, quantStage, fileName);
@@ -641,7 +650,7 @@ rowFileList(const char * const dirName,
 
         if (strlen(list) + strlen(fileName) + 1 > maxListSize - 1)
             pm_error("File name list too long for this program to handle.");
-        
+
         else {
             strcat(list, " ");
             strcat(list, fileName);
@@ -666,7 +675,7 @@ writeRowsAndDelete(unsigned int const rows,
 
     const char * quantStage;
     const char * fileList;
-    
+
     if (maxFormatType == PPM_TYPE && quant)
         pm_asprintf(&quantStage, "| pnmquant -quiet %u ", colors);
     else
@@ -674,7 +683,7 @@ writeRowsAndDelete(unsigned int const rows,
 
     fileList = rowFileList(tempDir, rows);
 
-    systemf("pnmcat %s -topbottom %s %s",
+    systemf("pamcat %s -topbottom %s %s",
             blackWhiteOpt, fileList, quantStage);
 
     pm_strfree(fileList);
@@ -687,7 +696,7 @@ writeRowsAndDelete(unsigned int const rows,
 
 int
 main(int argc, char *argv[]) {
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
     const char * tempDir;
     int maxFormatType;
     unsigned int colsInRow;
@@ -699,7 +708,7 @@ main(int argc, char *argv[]) {
     parseCommandLine(argc, argv, &cmdline);
 
     verbose = cmdline.verbose;
-    
+
     makeTempDir(&tempDir);
 
     maxFormatType = PBM_TYPE;
@@ -714,7 +723,7 @@ main(int argc, char *argv[]) {
 
         int format;
 
-        makeThumbnail(inputFileName, cmdline.size, cmdline.black, 
+        makeThumbnail(inputFileName, cmdline.size, cmdline.black,
                       !cmdline.noquant, cmdline.colors, tempDir,
                       rowsDone, colsInRow, &format);
 
@@ -742,3 +751,6 @@ main(int argc, char *argv[]) {
 
     return 0;
 }
+
+
+
diff --git a/editor/specialty/ppmshift.c b/editor/specialty/ppmshift.c
index a765daa5..27cbb78c3 100644
--- a/editor/specialty/ppmshift.c
+++ b/editor/specialty/ppmshift.c
@@ -9,124 +9,183 @@
 /* V1.1    16.11.1993  Rewritten to be NetPBM.programming conforming */
 /*********************************************************************/
 
+#include <stdbool.h>
+
+#include "mallocvar.h"
+#include "rand.h"
+#include "shhopt.h"
 #include "ppm.h"
 
-/**************************/
-/* start of main function */
-/**************************/
+
+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 shift;
+    unsigned int seedSpec;
+    unsigned int seed;
+};
+
+
+
+static void
+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.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options. */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    MALLOCARRAY(option_def, 100);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = false;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = true;  /* We have no parms that are negative numbers */
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "seed",            OPT_UINT,     &cmdlineP->seed,
+            &cmdlineP->seedSpec,         0);
+
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (argc-1 < 1)
+        pm_error("You must specify the shift factor as an argument");
+    else {
+        int const arg1 = atoi(argv[1]);
+        if (arg1 < 0)
+            pm_error("shift factor must be 0 or more");
+        cmdlineP->shift = arg1;
+
+        if (argc-1 < 2)
+            cmdlineP->inputFileName = "-";
+        else {
+            cmdlineP->inputFileName = argv[2];
+
+            if (argc-1 > 2)
+                pm_error("Too many arguments (%u).  "
+                         "Shift factor and input file name are the only "
+                         "possible arguments", argc-1);
+        }
+    }
+    free(option_def);
+}
+
+
+
+static void
+shiftRow(pixel *            const srcrow,
+         unsigned int       const cols,
+         unsigned int       const shift,
+         pixel *            const destrow,
+         struct pm_randSt * const randStP) {
+
+    /* the range by which a line is shifted lays in the range from */
+    /* -shift/2 .. +shift/2 pixels; however, within this range it is */
+    /* randomly chosen */
+
+    pixel * pP;
+    pixel * pP2;
+    int nowshift;
+
+    if (shift != 0)
+        nowshift = (pm_rand(randStP) % (shift+1)) - ((shift+1) / 2);
+    else
+        nowshift = 0;
+
+    pP  = &srcrow[0];
+    pP2 = &destrow[0];
+
+    /* if the shift value is less than zero, we take the original
+       pixel line and copy it into the destination line translated
+       to the left by x pixels. The empty pixels on the right end
+       of the destination line are filled up with the pixel that
+       is the right-most in the original pixel line.
+    */
+    if (nowshift < 0) {
+        unsigned int col;
+        pP += abs(nowshift);
+        for (col = 0; col < cols; ++col) {
+            PPM_ASSIGN(*pP2, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
+            ++pP2;
+            if (col < (cols + nowshift) - 1)
+                ++pP;
+        }
+    } else {
+        unsigned int col;
+        /* The shift value is 0 or positive, so fill the first
+           <nowshift> pixels of the destination line with the
+           first pixel from the source line, and copy the rest of
+           the source line to the dest line
+        */
+        for (col = 0; col < cols; ++col) {
+            PPM_ASSIGN(*pP2, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
+            ++pP2;
+            if (col >= nowshift)
+                ++pP;
+        }
+    }
+}
+
+
+
 int
-main(int    argc,
-     char * argv[]) {
+main(int argc, const char ** argv) {
 
     FILE * ifP;
-    unsigned int row;
-    int argn, rows, cols, format;
+    struct CmdlineInfo cmdline;
+    int rows, cols, format;
+    pixval maxval;
     pixel * srcrow;
     pixel * destrow;
-    pixval maxval;
-    int shift, nowshift;
-    int shiftArg;
-
-    const char * const usage = "shift [ppmfile]\n        shift: maximum number of pixels to shift a line by\n";
+    unsigned int row;
+    unsigned int shift;
+    struct pm_randSt randSt;
 
     /* parse in 'default' parameters */
-    ppm_init(&argc, argv);
-
-    argn = 1;
-
-    /* parse in shift number */
-    if (argn == argc)
-        pm_usage(usage);
-    if (sscanf(argv[argn], "%d", &shiftArg) != 1)
-        pm_usage(usage);
-    if (shiftArg < 0)
-        pm_error("shift factor must be 0 or more");
-    ++argn;
-
-    /* parse in filename (if present, stdin otherwise) */
-    if (argn != argc)
-    {
-        ifP = pm_openr(argv[argn]);
-        ++argn;
-    }
-    else
-        ifP = stdin;
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    pm_randinit(&randSt);
+    pm_srand2(&randSt, cmdline.seedSpec, cmdline.seed);
 
-    if (argn != argc)
-        pm_usage(usage);
+    ifP = pm_openr(cmdline.inputFileName);
 
     /* read first data from file */
     ppm_readppminit(ifP, &cols, &rows, &maxval, &format);
 
-    if (shiftArg > cols) {
+    if (cmdline.shift > cols) {
         shift = cols;
-        pm_message("shift amount is larger than picture width - reset to %d",
+        pm_message("shift amount is larger than picture width - reset to %u",
                    shift);
     } else
-        shift = shiftArg;
-
-    srcrow = ppm_allocrow(cols);
+        shift = cmdline.shift;
 
+    srcrow  = ppm_allocrow(cols);
     destrow = ppm_allocrow(cols);
 
     ppm_writeppminit(stdout, cols, rows, maxval, 0);
 
-    srand(pm_randseed());
-
-    /** now do the shifting **/
-    /* the range by which a line is shifted lays in the range from */
-    /* -shift/2 .. +shift/2 pixels; however, within this range it is */
-    /* randomly chosen */
     for (row = 0; row < rows; ++row) {
-        pixel * pP;
-        pixel * pP2;
-
-        if (shift != 0)
-            nowshift = (rand() % (shift+1)) - ((shift+1) / 2);
-        else
-            nowshift = 0;
-
         ppm_readppmrow(ifP, srcrow, cols, maxval, format);
 
-        pP  = &srcrow[0];
-        pP2 = &destrow[0];
-
-        /* if the shift value is less than zero, we take the original
-           pixel line and copy it into the destination line translated
-           to the left by x pixels. The empty pixels on the right end
-           of the destination line are filled up with the pixel that
-           is the right-most in the original pixel line.
-        */
-        if (nowshift < 0) {
-            unsigned int col;
-            pP += abs(nowshift);
-            for (col = 0; col < cols; ++col) {
-                PPM_ASSIGN(*pP2, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
-                ++pP2;
-                if (col < (cols + nowshift) - 1)
-                    ++pP;
-            }
-        } else {
-            unsigned int col;
-            /* The shift value is 0 or positive, so fill the first
-               <nowshift> pixels of the destination line with the
-               first pixel from the source line, and copy the rest of
-               the source line to the dest line
-            */
-            for (col = 0; col < cols; ++col) {
-                PPM_ASSIGN(*pP2, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
-                ++pP2;
-                if (col >= nowshift)
-                    ++pP;
-            }
-        }
+        shiftRow(srcrow, cols, shift, destrow, &randSt);
 
         ppm_writeppmrow(stdout, destrow, cols, maxval, 0);
     }
 
-    pm_close(ifP);
-    ppm_freerow(srcrow);
     ppm_freerow(destrow);
+    ppm_freerow(srcrow);
+    pm_close(ifP);
+    pm_randterm(&randSt);
 
     return 0;
 }
diff --git a/editor/specialty/ppmspread.c b/editor/specialty/ppmspread.c
index 6753f4fe..7b9558e3 100644
--- a/editor/specialty/ppmspread.c
+++ b/editor/specialty/ppmspread.c
@@ -10,102 +10,158 @@
 
 #include <string.h>
 
+#include "nstring.h"
+#include "rand.h"
+#include "shhopt.h"
 #include "ppm.h"
 
 
+struct CmdlineInfo {
+    /* This structure represents all of the information the user
+       supplied in the command line but in a form that's easy for the
+       program to use.
+    */
+    const char * inputFilename;  /* '-' if stdin */
+    unsigned int spread;
+    unsigned int randomseedSpec;
+    unsigned int randomseed;
+};
+
+
+
+static void
+parseCommandLine(int argc, const char ** const argv,
+                 struct CmdlineInfo * const cmdlineP ) {
+
+    optEntry     * option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options */
+    optStruct3     opt;
+    unsigned int   option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+    option_def_index = 0;          /* Incremented by OPTENTRY */
+
+    OPTENT3(0, "randomseed", OPT_UINT,   &cmdlineP->randomseed,
+            &cmdlineP->randomseedSpec, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = 0;
+    opt.allowNegNum = 1;
+
+    pm_optParseOptions3( &argc, (char **)argv, opt, sizeof(opt), 0 );
+
+    if (argc-1 < 1)
+        pm_error("You must specify the spread factor as an argument");
+    else {
+        const char * error;
+        pm_string_to_uint(argv[1], &cmdlineP->spread, &error);
+
+        if (error)
+            pm_error("Spread factor '%s' is not an unsigned integer.  %s",
+                     argv[1], error);
+
+        if (argc-1 < 2)
+            cmdlineP->inputFilename = "-";
+        else {
+            cmdlineP->inputFilename = argv[2];
+            if (argc-1 >2)
+                pm_error("Too many arguments: %u.  "
+                         "The only possible arguments are "
+                         "the spread factor and the optional input file name",
+                         argc-1);
+        }
+    }
+}
+
+
+
+static void
+spreadRow(pixel **           const srcarray,
+          unsigned int       const cols,
+          unsigned int       const rows,
+          unsigned int       const spread,
+          unsigned int       const row,
+          pixel **           const destarray,
+          struct pm_randSt * const randStP) {
+
+    unsigned int col;
+
+    for (col = 0; col < cols; ++col) {
+        pixel const p = srcarray[row][col];
+
+        int const xdis = (pm_rand(randStP) % (spread + 1) )
+            - ((spread + 1) / 2);
+        int const ydis = (pm_rand(randStP) % (spread + 1))
+            - ((spread + 1) / 2);
+
+        int const xnew = col + xdis;
+        int const ynew = row + ydis;
+
+        /* only set the displaced pixel if it's within the bounds
+           of the image
+        */
+        if (xnew >= 0 && xnew < cols && ynew >= 0 && ynew < rows) {
+            /* Displacing a pixel is accomplished by swapping it
+               with another pixel in its vicinity.
+            */
+            pixel const p2 = srcarray[ynew][xnew];
+                /* Original value of second pixel */
+
+            /* Set second pixel to new value */
+            PPM_ASSIGN(destarray[ynew][xnew],
+                       PPM_GETR(p), PPM_GETG(p), PPM_GETB(p));
+
+            /* Set first pixel to (old) value of second */
+            PPM_ASSIGN(destarray[row][col],
+                       PPM_GETR(p2), PPM_GETG(p2), PPM_GETB(p2));
+        } else {
+            /* Displaced pixel is out of bounds; leave the old pixel there.
+            */
+            PPM_ASSIGN(destarray[row][col],
+                       PPM_GETR(p), PPM_GETG(p), PPM_GETB(p));
+        }
+    }
+}
+
+
 
 int
-main(int    argc,
-     char * argv[]) {
+main(int          argc,
+     const char * argv[]) {
 
+    struct CmdlineInfo cmdline;
     FILE * ifP;
-    int argn, rows, cols;
+    int rows, cols;
     unsigned int row;
-    pixel ** destarray, ** srcarray;
-    pixel * pP;
-    pixel * pP2;
+    pixel ** destarray;
+    pixel ** srcarray;
     pixval maxval;
-    pixval r1, g1, b1;
-    int amount;
-    const char * const usage = "amount [ppmfile]\n        amount: # of pixels to displace a pixel by at most\n";
-
-    /* parse in 'default' parameters */
-    ppm_init(&argc, argv);
-
-    argn = 1;
-
-    /* parse in amount & seed */
-    if (argn == argc)
-        pm_usage(usage);
-    if (sscanf(argv[argn], "%d", &amount) != 1)
-        pm_usage(usage);
-    if (amount < 0)
-        pm_error("amount should be a positive number");
-    ++argn;
-
-    /* parse in filename (if present, stdin otherwise) */
-    if (argn != argc)
-    {
-        ifP = pm_openr(argv[argn]);
-        ++argn;
-    }
-    else
-        ifP = stdin;
+    struct pm_randSt randSt;
 
-    if (argn != argc)
-        pm_usage(usage);
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilename);
 
     srcarray = ppm_readppm(ifP, &cols, &rows, &maxval);
 
     destarray = ppm_allocarray(cols, rows);
 
+    pm_randinit(&randSt);
+    pm_srand2(&randSt, cmdline.randomseedSpec, cmdline.randomseed);
+
     /* clear out the buffer */
     for (row = 0; row < rows; ++row)
         memset(destarray[row], 0, cols * sizeof(pixel));
 
-    srand(pm_randseed());
-
-    /* start displacing pixels */
+    /* Displace pixels */
     for (row = 0; row < rows; ++row) {
-        unsigned int col;
-        pP = &srcarray[row][0];
-
-        for (col = 0; col < cols; ++col) {
-            int const xdis = (rand() % (amount+1)) - ((amount+1) / 2);
-            int const ydis = (rand() % (amount+1)) - ((amount+1) / 2);
+        spreadRow(srcarray, cols, rows, cmdline.spread, row,
+                  destarray, &randSt);
 
-            int const xnew = col + xdis;
-            int const ynew = row + ydis;
-
-            /* only set the displaced pixel if it's within the bounds
-               of the image
-            */
-            if (xnew >= 0 && xnew < cols && ynew >= 0 && ynew < rows) {
-                /* displacing a pixel is accomplished by swapping it
-                   with another pixel in its vicinity - so, first
-                   store other pixel's RGB
-                */
-                pP2 = &srcarray[ynew][xnew];
-                r1 = PPM_GETR(*pP2);
-                g1 = PPM_GETG(*pP2);
-                b1 = PPM_GETB(*pP2);
-                /* set second pixel to new value */
-                pP2 = &destarray[ynew][xnew];
-                PPM_ASSIGN(*pP2, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
-                
-                /* now, set first pixel to (old) value of second */
-                pP2 = &destarray[row][col];
-                PPM_ASSIGN(*pP2, r1, g1, b1);
-            } else {
-                /* displaced pixel is out of bounds; leave the old
-                   pixel there
-                */
-                pP2 = &destarray[row][col];
-                PPM_ASSIGN(*pP2, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
-            }
-            ++pP;
-        }
     }
+    pm_randterm(&randSt);
 
     ppm_writeppm(stdout, destarray, cols, rows, maxval, 0);
 
@@ -115,3 +171,5 @@ main(int    argc,
 
     return 0;
 }
+
+