about summary refs log tree commit diff
path: root/editor
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2016-03-27 01:38:28 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2016-03-27 01:38:28 +0000
commit367c9cb514c9da766488b9bdb218a18e31cb7624 (patch)
treef9e343be94161a4837f0f1c1d072a35538ae0f63 /editor
parent6e88e3326cb0c7f7975b56189278cab3f84ba1bd (diff)
downloadnetpbm-mirror-367c9cb514c9da766488b9bdb218a18e31cb7624.tar.gz
netpbm-mirror-367c9cb514c9da766488b9bdb218a18e31cb7624.tar.xz
netpbm-mirror-367c9cb514c9da766488b9bdb218a18e31cb7624.zip
Promote Stable (10.47) to Super Stable
git-svn-id: http://svn.code.sf.net/p/netpbm/code/super_stable@2691 9d0c8265-081b-0410-96cb-a4ca84ce46f8
Diffstat (limited to 'editor')
-rw-r--r--editor/Makefile70
-rw-r--r--editor/dithers.h87
-rw-r--r--editor/pamaddnoise.c4
-rw-r--r--editor/pambackground.c505
-rw-r--r--editor/pamcomp.c125
-rw-r--r--editor/pamcut.c284
-rw-r--r--editor/pamdice.c30
-rw-r--r--editor/pamdither.c1
-rw-r--r--editor/pamditherbw.c239
-rw-r--r--editor/pamedge.c2
-rw-r--r--editor/pamenlarge.c243
-rw-r--r--editor/pamenlarge.test2
-rw-r--r--editor/pamflip.c1168
-rw-r--r--editor/pamfunc.c191
-rw-r--r--editor/pammasksharpen.c6
-rw-r--r--editor/pammixinterlace.c173
-rw-r--r--editor/pamperspective.c715
-rw-r--r--editor/pamscale.c6
-rw-r--r--editor/pamsistoaglyph.c421
-rwxr-xr-xeditor/pamstretch-gen8
-rw-r--r--editor/pamstretch.c2
-rw-r--r--editor/pamthreshold.c88
-rw-r--r--editor/pamundice.c702
-rw-r--r--editor/pbmclean.c2
-rw-r--r--editor/pbmpscale.c230
-rw-r--r--editor/pbmreduce.c15
-rw-r--r--editor/pgmbentley.c68
-rw-r--r--editor/pgmdeshadow.c2
-rw-r--r--editor/pgmmedian.c278
-rw-r--r--editor/pnmcat.c832
-rw-r--r--editor/pnmcomp.c1
-rw-r--r--editor/pnmconvol.c21
-rw-r--r--editor/pnmcrop.c727
-rw-r--r--editor/pnmcut.c427
-rwxr-xr-xeditor/pnmflip1
-rw-r--r--editor/pnmgamma.c20
-rw-r--r--editor/pnmhisteq.c1
-rwxr-xr-xeditor/pnmindex.sh5
-rw-r--r--editor/pnminvert.test2
-rwxr-xr-xeditor/pnmmargin66
-rw-r--r--editor/pnmmontage.c770
-rw-r--r--editor/pnmnlfilt.c122
-rw-r--r--editor/pnmnorm.c185
-rw-r--r--editor/pnmpad.c285
-rw-r--r--editor/pnmpaste.c577
-rw-r--r--editor/pnmremap.c568
-rw-r--r--editor/pnmrotate.c50
-rw-r--r--editor/pnmscale.c748
-rw-r--r--editor/pnmscalefixed.c2
-rw-r--r--editor/pnmshear.c170
-rw-r--r--editor/pnmsmooth.c39
-rw-r--r--editor/pnmstitch.c14
-rw-r--r--editor/pnmtile.c127
-rw-r--r--editor/ppm3d.c138
-rw-r--r--editor/ppmbrighten.c12
-rw-r--r--editor/ppmchange.c17
-rw-r--r--editor/ppmcolormask.c2
-rw-r--r--editor/ppmdraw.c30
-rwxr-xr-xeditor/ppmfade22
-rwxr-xr-xeditor/ppmquantall5
-rw-r--r--editor/ppmshift.c137
-rw-r--r--editor/ppmspread.c127
-rw-r--r--editor/specialty/Makefile55
-rw-r--r--editor/specialty/pamdeinterlace.c (renamed from editor/pamdeinterlace.c)1
-rw-r--r--editor/specialty/pammixinterlace.c345
-rw-r--r--editor/specialty/pamoil.c (renamed from editor/pamoil.c)0
-rw-r--r--editor/specialty/pampop9.c (renamed from editor/pampop9.c)0
-rw-r--r--editor/specialty/pbmlife.c (renamed from editor/pbmlife.c)0
-rw-r--r--editor/specialty/pgmabel.c (renamed from editor/pgmabel.c)0
-rw-r--r--editor/specialty/pgmbentley.c74
-rw-r--r--editor/specialty/pgmmorphconv.c (renamed from editor/pgmmorphconv.c)0
-rw-r--r--editor/specialty/pnmindex.c (renamed from editor/pnmindex.c)6
-rw-r--r--editor/specialty/ppm3d.c308
-rw-r--r--editor/specialty/ppmglobe.c (renamed from editor/ppmglobe.c)1
-rw-r--r--editor/specialty/ppmntsc.c (renamed from editor/ppmntsc.c)2
-rw-r--r--editor/specialty/ppmrelief.c (renamed from editor/ppmrelief.c)0
-rw-r--r--editor/specialty/ppmshift.c132
-rw-r--r--editor/specialty/ppmspread.c117
-rw-r--r--editor/specialty/ppmtv.c (renamed from editor/ppmtv.c)0
79 files changed, 8331 insertions, 4627 deletions
diff --git a/editor/Makefile b/editor/Makefile
index 18165666..d5d7633f 100644
--- a/editor/Makefile
+++ b/editor/Makefile
@@ -5,7 +5,9 @@ endif
 SUBDIR = editor
 VPATH=.:$(SRCDIR)/$(SUBDIR)
 
-include $(BUILDDIR)/Makefile.config
+include $(BUILDDIR)/config.mk
+
+SUBDIRS = specialty
 
 # We tend to separate out the build targets so that we don't have
 # any more dependencies for a given target than it really needs.
@@ -14,25 +16,24 @@ include $(BUILDDIR)/Makefile.config
 # This package is so big, it's useful even when some parts won't 
 # build.
 
-PORTBINARIES = pamaddnoise pamcomp pamcut \
-	       pamdeinterlace pamdice pamditherbw pamedge \
+PORTBINARIES = pamaddnoise pambackground pamcomp pamcut \
+	       pamdice pamditherbw pamedge \
 	       pamenlarge \
-	       pamflip pamfunc pammasksharpen pammixinterlace \
-	       pamoil pamperspective pampop9 \
-	       pamscale pamstretch pamthreshold \
-	       pbmclean pbmlife pbmmask pbmpscale pbmreduce \
-	       pgmabel pgmbentley pgmdeshadow pgmenhance \
-	       pgmmedian pgmmorphconv \
-	       pnmalias pnmcat pnmcomp pnmconvol pnmcrop pnmcut \
+	       pamflip pamfunc pammasksharpen \
+	       pamperspective \
+	       pamscale pamsistoaglyph pamstretch pamthreshold pamundice \
+	       pbmclean pbmmask pbmpscale pbmreduce \
+	       pgmdeshadow pgmenhance \
+	       pgmmedian \
+	       pnmalias pnmcat pnmcomp pnmconvol pnmcrop \
 	       pnmgamma \
-	       pnmhisteq pnmindex pnminvert pnmmontage \
+	       pnmhisteq pnminvert pnmmontage \
 	       pnmnlfilt pnmnorm pnmpad pnmpaste \
 	       pnmremap pnmrotate \
-	       pnmscale pnmscalefixed pnmshear pnmsmooth pnmstitch pnmtile \
-	       ppm3d ppmbrighten ppmchange ppmcolormask \
+	       pnmscalefixed pnmshear pnmsmooth pnmstitch pnmtile \
+	       ppmbrighten ppmchange ppmcolormask \
 	       ppmdim ppmdist ppmdither ppmdraw \
-	       ppmflash ppmglobe ppmlabel ppmmix \
-	       ppmntsc ppmrelief ppmshift ppmspread ppmtv
+	       ppmflash ppmlabel ppmmix \
 
 # We don't include programs that have special library dependencies in the
 # merge scheme, because we don't want those dependencies to prevent us
@@ -51,36 +52,41 @@ OBJECTS = $(BINARIES:%=%.o)
 MERGE_OBJECTS = $(MERGEBINARIES:%=%.o2)
 
 .PHONY: all
-all: $(BINARIES)
+all: $(BINARIES) $(SUBDIRS:%=%/all)
 
-include $(SRCDIR)/Makefile.common
+include $(SRCDIR)/common.mk
 
 install.bin: 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 named pnmnoraw
 # backward compatibility: program used to be pnminterp
 	cd $(PKGDIR)/bin ; \
-	rm -f pnminterp; \
-	$(SYMLINK) pamstretch$(EXE) pnminterp
-# pamoil replaced pgmoil in June 2001.
-	cd $(PKGDIR)/bin ; \
-	rm -f pgmoil ; \
-	$(SYMLINK) pamoil$(EXE) pgmoil
+	rm -f pnminterp$(EXE); \
+	$(SYMLINK) pamstretch$(EXE) pnminterp$(EXE)
 # In March 2002, pnmnorm replaced ppmnorm and pgmnorm
 	cd $(PKGDIR)/bin ; \
-	rm -f ppmnorm ; \
-	$(SYMLINK) pnmnorm$(EXE) ppmnorm 
+	rm -f ppmnorm$(EXE) ; \
+	$(SYMLINK) pnmnorm$(EXE) ppmnorm$(EXE)
 	cd $(PKGDIR)/bin ; \
-	rm -f pgmnorm ; \
-	$(SYMLINK) pnmnorm$(EXE) pgmnorm
+	rm -f pgmnorm$(EXE) ; \
+	$(SYMLINK) pnmnorm$(EXE) pgmnorm$(EXE)
 # In March 2003, pamedge replaced pgmedge
 	cd $(PKGDIR)/bin ; \
-	rm -f pgmedge ; \
-	$(SYMLINK) pamedge$(EXE) pgmedge
+	rm -f pgmedge$(EXE) ; \
+	$(SYMLINK) pamedge$(EXE) pgmedge$(EXE)
 # In October 2004, pamenlarge replaced pnmenlarge
 	cd $(PKGDIR)/bin ; \
-	rm -f pnmenlarge ; \
-	$(SYMLINK) pamenlarge$(EXE) pnmenlarge
+	rm -f pnmenlarge$(EXE) ; \
+	$(SYMLINK) pamenlarge$(EXE) pnmenlarge$(EXE)
+# In March 2009, pamcut replaced pnmcut (but pamcut is much older -- pnmcut
+# was obsoleted by pamcut long before this).
+	cd $(PKGDIR)/bin ; \
+	rm -f pnmcut$(EXE) ; \
+	$(SYMLINK) pamcut$(EXE) pnmcut$(EXE)
+# In March 2009, pamscale replaced pnmscale (but pamscale is much older --
+# pnmscale was obsoleted by pamscale long before this).
+	cd $(PKGDIR)/bin ; \
+	rm -f pnmscale$(EXE) ; \
+	$(SYMLINK) pamscale$(EXE) pnmscale$(EXE)
diff --git a/editor/dithers.h b/editor/dithers.h
deleted file mode 100644
index 24a9fb39..00000000
--- a/editor/dithers.h
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
-** dithers.h
-**
-** Here are some dithering matrices.  They are all taken from "Digital
-** Halftoning" by Robert Ulichney, MIT Press, ISBN 0-262-21009-6.
-*/
-
-
-#if 0
-/*
-** Order-6 ordered dithering matrix.  Note that smaller ordered dithers
-** have no advantage over larger ones, so use dither8 instead.
-*/
-static int const dither6[8][8] = {
-  {  1, 59, 15, 55,  2, 56, 12, 52 },
-  { 33, 17, 47, 31, 34, 18, 44, 28 },
-  {  9, 49,  5, 63, 10, 50,  6, 60 },
-  { 41, 25, 37, 21, 42, 26, 38, 22 },
-  {  3, 57, 13, 53,  0, 58, 14, 54 },
-  { 35, 19, 45, 29, 32, 16, 46, 30 },
-  { 11, 51,  7, 61,  8, 48,  4, 62 },
-  { 43, 27, 39, 23, 40, 24, 36, 20 }
-  };
-#endif
-
-/* Order-8 ordered dithering matrix. */
-static int const dither8[16][16] = {
-  {   1,235, 59,219, 15,231, 55,215,  2,232, 56,216, 12,228, 52,212},
-  { 129, 65,187,123,143, 79,183,119,130, 66,184,120,140, 76,180,116},
-  {  33,193, 17,251, 47,207, 31,247, 34,194, 18,248, 44,204, 28,244},
-  { 161, 97,145, 81,175,111,159, 95,162, 98,146, 82,172,108,156, 92},
-  {   9,225, 49,209,  5,239, 63,223, 10,226, 50,210,  6,236, 60,220},
-  { 137, 73,177,113,133, 69,191,127,138, 74,178,114,134, 70,188,124},
-  {  41,201, 25,241, 37,197, 21,255, 42,202, 26,242, 38,198, 22,252},
-  { 169,105,153, 89,165,101,149, 85,170,106,154, 90,166,102,150, 86},
-  {   3,233, 57,217, 13,229, 53,213,  0,234, 58,218, 14,230, 54,214},
-  { 131, 67,185,121,141, 77,181,117,128, 64,186,122,142, 78,182,118},
-  {  35,195, 19,249, 45,205, 29,245, 32,192, 16,250, 46,206, 30,246},
-  { 163, 99,147, 83,173,109,157, 93,160, 96,144, 80,174,110,158, 94},
-  {  11,227, 51,211,  7,237, 61,221,  8,224, 48,208,  4,238, 62,222},
-  { 139, 75,179,115,135, 71,189,125,136, 72,176,112,132, 68,190,126},
-  {  43,203, 27,243, 39,199, 23,253, 40,200, 24,240, 36,196, 20,254},
-  { 171,107,155, 91,167,103,151, 87,168,104,152, 88,164,100,148, 84} 
-};
-
-/* Order-3 clustered dithering matrix. */
-static int const cluster3[6][6] = {
-  {  9,11,10, 8, 6, 7},
-  { 12,17,16, 5, 0, 1},
-  { 13,14,15, 4, 3, 2},
-  {  8, 6, 7, 9,11,10},
-  {  5, 0, 1,12,17,16},
-  {  4, 3, 2,13,14,15}
-};
-
-/* Order-4 clustered dithering matrix. */
-static int const cluster4[8][8] = {
-  { 18,20,19,16,13,11,12,15},
-  { 27,28,29,22, 4, 3, 2, 9},
-  { 26,31,30,21, 5, 0, 1,10},
-  { 23,25,24,17, 8, 6, 7,14},
-  { 13,11,12,15,18,20,19,16},
-  {  4, 3, 2, 9,27,28,29,22},
-  {  5, 0, 1,10,26,31,30,21},
-  {  8, 6, 7,14,23,25,24,17}
-};
-
-/* Order-8 clustered dithering matrix. */
-static int const cluster8[16][16] = {
-   { 64, 69, 77, 87, 86, 76, 68, 67, 63, 58, 50, 40, 41, 51, 59, 60},
-   { 70, 94,100,109,108, 99, 93, 75, 57, 33, 27, 18, 19, 28, 34, 52},
-   { 78,101,114,116,115,112, 98, 83, 49, 26, 13, 11, 12, 15, 29, 44},
-   { 88,110,123,124,125,118,107, 85, 39, 17,  4,  3,  2,  9, 20, 42},
-   { 89,111,122,127,126,117,106, 84, 38, 16,  5,  0,  1, 10, 21, 43},
-   { 79,102,119,121,120,113, 97, 82, 48, 25,  8,  6,  7, 14, 30, 45},
-   { 71, 95,103,104,105, 96, 92, 74, 56, 32, 24, 23, 22, 31, 35, 53},
-   { 65, 72, 80, 90, 91, 81, 73, 66, 62, 55, 47, 37, 36, 46, 54, 61},
-   { 63, 58, 50, 40, 41, 51, 59, 60, 64, 69, 77, 87, 86, 76, 68, 67},
-   { 57, 33, 27, 18, 19, 28, 34, 52, 70, 94,100,109,108, 99, 93, 75},
-   { 49, 26, 13, 11, 12, 15, 29, 44, 78,101,114,116,115,112, 98, 83},
-   { 39, 17,  4,  3,  2,  9, 20, 42, 88,110,123,124,125,118,107, 85},
-   { 38, 16,  5,  0,  1, 10, 21, 43, 89,111,122,127,126,117,106, 84},
-   { 48, 25,  8,  6,  7, 14, 30, 45, 79,102,119,121,120,113, 97, 82},
-   { 56, 32, 24, 23, 22, 31, 35, 53, 71, 95,103,104,105, 96, 92, 74},
-   { 62, 55, 47, 37, 36, 46, 54, 61, 65, 72, 80, 90, 91, 81, 73, 66}
-};
-
diff --git a/editor/pamaddnoise.c b/editor/pamaddnoise.c
index 9c2d12f7..cf1af815 100644
--- a/editor/pamaddnoise.c
+++ b/editor/pamaddnoise.c
@@ -208,7 +208,7 @@ main(int argc, char * argv[]) {
     int argn;
     const char * inputFilename;
     int noise_type;
-    int seed;
+    unsigned int seed;
     int i;
     const char * const usage = "[-type noise_type] [-lsigma x] [-mgsigma x] "
         "[-sigma1 x] [-sigma2 x] [-lambda x] [-seed n] "
@@ -247,7 +247,7 @@ main(int argc, char * argv[]) {
 
     pnm_init(&argc, argv);
 
-    seed = time(NULL) ^ getpid();
+    seed = pm_randseed();
     noise_type = GAUSSIAN;
 
     argn = 1;
diff --git a/editor/pambackground.c b/editor/pambackground.c
new file mode 100644
index 00000000..1aa53177
--- /dev/null
+++ b/editor/pambackground.c
@@ -0,0 +1,505 @@
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "shhopt.h"
+#include "pam.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;  
+    unsigned int verbose;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** const argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to OptParseOptions2 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, "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 */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else {
+        cmdlineP->inputFileName = argv[1];
+        if (argc-1 > 1)
+            pm_error("There is at most one argument:  input file name.  "
+                     "You specified %d", argc-1);
+    }
+}        
+
+
+
+static void
+initOutpam(const struct pam * const inpamP,
+           struct pam *       const outpamP) {
+
+    outpamP->file             = stdout;
+    outpamP->format           = PAM_FORMAT;
+    outpamP->plainformat      = 0;
+    outpamP->width            = inpamP->width;
+    outpamP->height           = inpamP->height;
+    outpamP->depth            = 1;
+    outpamP->maxval           = 1;
+    outpamP->bytes_per_sample = pnm_bytespersample(outpamP->maxval);
+    outpamP->len              = PAM_STRUCT_SIZE(bytes_per_sample);
+    outpamP->size             = sizeof(*outpamP);
+}
+
+
+
+static void
+allocateOutputPointerRow(unsigned int const width,
+                         tuple **     const tuplerowP) {
+
+    MALLOCARRAY(*tuplerowP, width);
+
+    if (*tuplerowP == NULL)
+        pm_error("Could not allocate a %u-column tuple pointer array", width);
+}
+
+
+
+static void
+createWhiteTuple(const struct pam * const pamP, 
+                 tuple *            const whiteTupleP) {
+/*----------------------------------------------------------------------------
+   Create a "white" tuple.  By that we mean a tuple all of whose elements
+   are zero.  If it's an RGB, grayscale, or b&w pixel, that means it's black.
+-----------------------------------------------------------------------------*/
+    tuple whiteTuple;
+    unsigned int plane;
+
+    whiteTuple = pnm_allocpamtuple(pamP);
+
+    for (plane = 0; plane < pamP->depth; ++plane)
+        whiteTuple[plane] = pamP->maxval;
+
+    *whiteTupleP = whiteTuple;
+}
+
+
+
+static void
+selectBackground(struct pam * const pamP,
+                 tuple        const ul,
+                 tuple        const ur,
+                 tuple        const lr,
+                 tuple        const ll,
+                 tuple *      const bgColorP) {
+
+    tuple bg;  /* Reference to one of ul, ur, ll, lr */
+
+    if (pnm_tupleequal(pamP, ul, ur) &&
+        (pnm_tupleequal(pamP, ul, ll) ||
+         pnm_tupleequal(pamP, ul, lr)))
+        bg = ul;
+    else if (pnm_tupleequal(pamP, ll, lr) &&
+             pnm_tupleequal(pamP, lr, ul))
+        bg = ll;
+    else {
+        /* No 3 corners are same color; look for 2 corners */
+        if (pnm_tupleequal(pamP, ul, ur))  /* top edge */
+            bg = ul;
+        else if (pnm_tupleequal(pamP, ul, ll)) /* left edge */
+            bg = ul;
+        else if (pnm_tupleequal(pamP, ur, lr)) /* right edge */
+            bg = ur;
+        else if (pnm_tupleequal(pamP, ll, lr)) /* bottom edge */
+            bg = ll;
+        else {
+            /* No two corners are same color; just use upper left corner */
+            bg = ul;
+        }
+    }
+    
+    *bgColorP = pnm_allocpamtuple(pamP);
+    pnm_assigntuple(pamP, *bgColorP, bg);
+}
+
+
+
+static void
+determineBackgroundColor(struct pam * const pamP,
+                         bool         const verbose,
+                         tuple *      const bgColorP) {
+/*----------------------------------------------------------------------------
+   Determine what color is the background color of the image in the
+   file represented by *pamP.
+
+   Expect the file to be positioned to the start of the raster, and leave
+   it positioned arbitrarily.
+-----------------------------------------------------------------------------*/
+    unsigned int row;
+    tuple * tuplerow;
+    tuple ul, ur, ll, lr;
+        /* Color of upper left, upper right, lower left, lower right */
+
+    tuplerow  = pnm_allocpamrow(pamP);
+    ul = pnm_allocpamtuple(pamP);
+    ur = pnm_allocpamtuple(pamP);
+    ll = pnm_allocpamtuple(pamP);
+    lr = pnm_allocpamtuple(pamP);
+
+    pnm_readpamrow(pamP, tuplerow);
+
+    pnm_assigntuple(pamP, ul, tuplerow[0]);
+    pnm_assigntuple(pamP, ur, tuplerow[pamP->width-1]);
+
+    for (row = 1; row < pamP->height; ++row)
+        pnm_readpamrow(pamP, tuplerow);
+
+    pnm_assigntuple(pamP, ll, tuplerow[0]);
+    pnm_assigntuple(pamP, lr, tuplerow[pamP->width-1]);
+
+    selectBackground(pamP, ul, ur, ll, lr, bgColorP);
+
+    if (verbose) {
+        int const hexokTrue = 1;
+        const char * const colorname =
+            pnm_colorname(pamP, *bgColorP, hexokTrue);
+        pm_message("Background color is %s", colorname);
+
+        strfree(colorname);
+    }
+
+    pnm_freepamtuple(lr);
+    pnm_freepamtuple(ll);
+    pnm_freepamtuple(ur);
+    pnm_freepamtuple(ul);
+    pnm_freepamrow(tuplerow);
+}
+
+
+static unsigned char const PT_UNKNOWN = 0;
+static unsigned char const PT_BG      = 1;
+static unsigned char const PT_FG      = 2;
+
+
+static unsigned char **
+newPi(unsigned int const width,
+      unsigned int const height) {
+
+    unsigned char ** pi;
+    unsigned int row;
+
+    MALLOCARRAY(pi, height);
+    if (pi == NULL)
+        pm_error("Out of memory allocating %u row pointers", height);
+    for (row = 0; row < height; ++row) {
+        MALLOCARRAY(pi[row], width);
+        if (pi[row] == NULL)
+            pm_error("Out of memory allocating row %u", row);
+    }
+    return pi;
+}
+
+
+
+static void
+destroyPi(const unsigned char *const * const pi,
+          unsigned int                 const height) {
+
+    unsigned int row;
+
+    for (row = 0; row < height; ++row)
+        free((void*)pi[row]);
+
+    free((void*)pi);
+}
+
+
+
+static void
+initPi(unsigned char **   const pi,
+       const struct pam * const pamP,
+       tuple              const backgroundColor) {
+/*----------------------------------------------------------------------------
+  Set the initial info about every pixel in pi[][].
+
+  Read through the image in the file described by *inpamP and set each
+  pixel which is not of background color, and therefore obviously
+  foreground, to type PT_FG.  Set every other pixel to PT_UNKNOWN.
+-----------------------------------------------------------------------------*/
+    tuple * tuplerow;
+    unsigned int row;
+
+    tuplerow  = pnm_allocpamrow(pamP);
+
+    for (row = 0; row < pamP->height; ++row) {
+        unsigned int col;
+
+        pnm_readpamrow(pamP, tuplerow);
+
+        for (col = 0; col < pamP->width; ++col) {
+            pi[row][col] =
+                pnm_tupleequal(pamP, tuplerow[col], backgroundColor) ?
+                PT_UNKNOWN : PT_FG;
+        }
+    }
+    pnm_freepamrow(tuplerow);
+}
+
+
+
+static void
+setEdges(unsigned char ** const pi,
+         unsigned int     const width,
+         unsigned int     const height) {
+/*----------------------------------------------------------------------------
+  Do the four edges.
+
+  Anything of background color in an edge is background.  Pixel type
+  PT_UNKNOWN implies background color, so we change all PT_UNKNOWN pixels
+  to PT_BG.
+-----------------------------------------------------------------------------*/
+    unsigned int row, col;
+
+    for (col = 0; col < width; ++col) {
+        if (pi[0][col] == PT_UNKNOWN)
+            pi[0][col] = PT_BG;
+
+        if (pi[height-1][col] == PT_UNKNOWN)
+            pi[height-1][col] = PT_BG;
+    }
+    for (row = 0; row < height; ++row) {
+        if (pi[row][0] == PT_UNKNOWN)
+            pi[row][0] = PT_BG;
+
+        if (pi[row][width-1] == PT_UNKNOWN)
+            pi[row][width-1] = PT_BG;
+    }
+}
+
+
+
+static void
+expandBackgroundHoriz(unsigned char ** const pi,
+                      unsigned int     const width,
+                      unsigned int     const height,
+                      bool *           const expandedP) {
+/*----------------------------------------------------------------------------
+   In every row, expand the background rightward from any known background
+   pixel through all consecutive unknown pixels.
+
+   Then do the same thing leftward.
+
+   Iff we managed to expand the background at all, return *expandedP == TRUE.
+-----------------------------------------------------------------------------*/
+    unsigned int row;
+    bool expanded;
+
+    for (row = 1, expanded = FALSE; row < height-1; ++row) {
+        int col;
+
+        for (col = 1; col < width - 1; ++col) {
+            if (pi[row][col] == PT_UNKNOWN && pi[row][col-1] == PT_BG) {
+                expanded = TRUE;
+                /* Set the whole consecutive row of unknown to background */
+                for (; pi[row][col] == PT_UNKNOWN && col < width - 1; ++col)
+                    pi[row][col] = PT_BG;
+            }
+        }
+
+        for (col = width-2; col > 0; --col) {
+            if (pi[row][col] == PT_UNKNOWN && pi[row][col+1] == PT_BG) {
+                expanded = TRUE;
+                /* Set the whole consecutive row of unknown to background */
+                for (; pi[row][col] == PT_UNKNOWN && col > 0; --col)
+                    pi[row][col] = PT_BG;
+            }
+        }
+    }
+    *expandedP = expanded;
+}
+
+
+
+static void
+expandBackgroundVert(unsigned char ** const pi,
+                     unsigned int     const width,
+                     unsigned int     const height,
+                     bool *           const expandedP) {
+/*----------------------------------------------------------------------------
+   In every column, expand the background downward from any known background
+   pixel through all consecutive unknown pixels.
+
+   Then do the same thing upward.
+
+   Iff we managed to expand the background at all, return *expandedP == TRUE.
+-----------------------------------------------------------------------------*/
+    unsigned int col;
+    bool expanded;
+
+    for (col = 1, expanded = FALSE; col < width-1; ++col) {
+        int row;
+
+        for (row = 1; row < height - 1; ++row) {
+            if (pi[row][col] == PT_UNKNOWN && pi[row-1][col] == PT_BG) {
+                expanded = TRUE;
+                /* Set the whole consecutive col of unknown to background */
+                for (; pi[row][col] == PT_UNKNOWN && row < height - 1; ++row)
+                    pi[row][col] = PT_BG;
+            }
+        }
+
+        for (row = height-2; row > 0; --row) {
+            if (pi[row][col] == PT_UNKNOWN && pi[row+1][col] == PT_BG) {
+                expanded = TRUE;
+                /* Set the whole consecutive col of unknown to background */
+                for (; pi[row][col] == PT_UNKNOWN && row > 0; --row)
+                    pi[row][col] = PT_BG;
+            }
+        }
+    }
+    *expandedP = expanded;
+}
+
+
+
+static void
+findBackgroundPixels(struct pam *                   const inpamP,
+                     tuple                          const backgroundColor,
+                     bool                           const verbose,
+                     const unsigned char * const ** const piP) {
+/*----------------------------------------------------------------------------
+   Figure out which pixels of the image are background.  Read the
+   image from the input file described by *inpamP (positioned now to
+   the start of the raster) and generate the matrix *piP telling which
+   pixels are background and which are foreground, given that the
+   background color is 'backgroundColor'.
+
+   Note that it isn't as simple as finding which pixels are of color
+   'backgroundColor'.  They have to be part of a contiguous region that
+   touches one of the 4 edges of the image.
+
+   In the matrix we return, there is one element for each pixel in the
+   image, and it has value PT_BG or PT_FG.
+-----------------------------------------------------------------------------*/
+    unsigned char ** pi;
+    bool backgroundComplete;
+    unsigned int passes;
+
+    pi = newPi(inpamP->width, inpamP->height);
+
+    initPi(pi, inpamP, backgroundColor);
+
+    setEdges(pi, inpamP->width, inpamP->height);
+
+    backgroundComplete = FALSE;
+    passes = 0;
+    
+    while (!backgroundComplete) {
+        bool expandedHoriz, expandedVert;
+
+        expandBackgroundHoriz(pi, inpamP->width, inpamP->height,
+                              &expandedHoriz);
+    
+        expandBackgroundVert(pi, inpamP->width, inpamP->height,
+                             &expandedVert);
+
+        backgroundComplete = !expandedHoriz && !expandedVert;
+
+        ++passes;
+    }
+
+    if (verbose)
+        pm_message("Background found in %u passes", passes);
+
+    *piP = (const unsigned char * const *)pi;
+}
+
+                     
+
+static void
+writeOutput(const struct pam *            const inpamP,
+            const unsigned char * const * const pi) {
+
+    tuple black, white;
+    tuple * outputTuplerow;
+        /* This is not a normal tuplerow; it is just pointers to either
+           'black' or 'white'
+        */
+    unsigned int row;
+    struct pam outpam;
+
+    initOutpam(inpamP, &outpam);
+
+    allocateOutputPointerRow(outpam.width, &outputTuplerow);
+    pnm_createBlackTuple(&outpam, &black);
+    createWhiteTuple(&outpam, &white);
+
+    pnm_writepaminit(&outpam);
+
+    for (row = 0; row < outpam.height; ++row) {
+        unsigned int col;
+        for (col = 0; col < outpam.width; ++col)
+            outputTuplerow[col] = pi[row][col] == PT_BG ? white : black;
+
+        pnm_writepamrow(&outpam, outputTuplerow);
+    }
+    pnm_freepamtuple(white);
+    pnm_freepamtuple(black);
+    free(outputTuplerow);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    struct pam inpam;
+    FILE * ifP;
+    pm_filepos rasterpos;
+    tuple backgroundColor;
+    const unsigned char * const * pi;
+    
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr_seekable(cmdline.inputFileName);
+
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    pm_tell2(ifP, &rasterpos, sizeof(rasterpos));
+
+    determineBackgroundColor(&inpam, cmdline.verbose, &backgroundColor);
+
+    pm_seek2(ifP, &rasterpos, sizeof(rasterpos));
+
+    findBackgroundPixels(&inpam, backgroundColor, cmdline.verbose, &pi);
+
+    writeOutput(&inpam, pi);
+
+    destroyPi(pi, inpam.height);
+
+    pm_close(ifP);
+
+    pnm_freepamtuple(backgroundColor);
+    
+    return 0;
+}
diff --git a/editor/pamcomp.c b/editor/pamcomp.c
index c9c389b7..e89509cc 100644
--- a/editor/pamcomp.c
+++ b/editor/pamcomp.c
@@ -22,14 +22,17 @@
    additional work by multiple authors.
 -----------------------------------------------------------------------------*/
 
-#define _BSD_SOURCE    /* Make sure strcasecmp() is in string.h */
+#define _BSD_SOURCE    /* Make sure strcaseceq() is in nstring.h */
+#include <assert.h>
 #include <string.h>
 #include <math.h>
 
-#include "pam.h"
-#include "pm_gamma.h"
-#include "shhopt.h"
+#include "pm_c_util.h"
 #include "mallocvar.h"
+#include "nstring.h"
+#include "shhopt.h"
+#include "pm_gamma.h"
+#include "pam.h"
 
 enum horizPos {BEYONDLEFT, LEFT, CENTER, RIGHT, BEYONDRIGHT};
 enum vertPos {ABOVE, TOP, MIDDLE, BOTTOM, BELOW};
@@ -64,7 +67,7 @@ struct cmdlineInfo {
 
 static void
 parseCommandLine(int                        argc, 
-                 char **                    argv,
+                 const char **              argv,
                  struct cmdlineInfo * const cmdlineP ) {
 /*----------------------------------------------------------------------------
    Parse program command line described in Unix standard form by argc
@@ -111,7 +114,7 @@ parseCommandLine(int                        argc,
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
     opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
 
-    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+    optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
 
@@ -123,15 +126,15 @@ parseCommandLine(int                        argc,
         cmdlineP->alphaFilespec = NULL;
 
     if (alignSpec) {
-        if (strcasecmp(align, "BEYONDLEFT") == 0)
+        if (strcaseeq(align, "BEYONDLEFT"))
             cmdlineP->align = BEYONDLEFT;
-        else if (strcasecmp(align, "LEFT") == 0)
+        else if (strcaseeq(align, "LEFT"))
             cmdlineP->align = LEFT;
-        else if (strcasecmp(align, "CENTER") == 0)
+        else if (strcaseeq(align, "CENTER"))
             cmdlineP->align = CENTER;
-        else if (strcasecmp(align, "RIGHT") == 0)
+        else if (strcaseeq(align, "RIGHT"))
             cmdlineP->align = RIGHT;
-        else if (strcasecmp(align, "BEYONDRIGHT") == 0)
+        else if (strcaseeq(align, "BEYONDRIGHT"))
             cmdlineP->align = BEYONDRIGHT;
         else
             pm_error("Invalid value for align option: '%s'.  Only LEFT, "
@@ -141,15 +144,15 @@ parseCommandLine(int                        argc,
         cmdlineP->align = LEFT;
 
     if (valignSpec) {
-        if (strcasecmp(valign, "ABOVE") == 0)
+        if (strcaseeq(valign, "ABOVE"))
             cmdlineP->valign = ABOVE;
-        else if (strcasecmp(valign, "TOP") == 0)
+        else if (strcaseeq(valign, "TOP"))
             cmdlineP->valign = TOP;
-        else if (strcasecmp(valign, "MIDDLE") == 0)
+        else if (strcaseeq(valign, "MIDDLE"))
             cmdlineP->valign = MIDDLE;
-        else if (strcasecmp(valign, "BOTTOM") == 0)
+        else if (strcaseeq(valign, "BOTTOM"))
             cmdlineP->valign = BOTTOM;
-        else if (strcasecmp(valign, "BELOW") == 0)
+        else if (strcaseeq(valign, "BELOW"))
             cmdlineP->valign = BELOW;
         else
             pm_error("Invalid value for valign option: '%s'.  Only TOP, "
@@ -268,12 +271,12 @@ determineOutputType(struct pam * const composedPamP,
 
 
 static void
-warnOutOfFrame( int const originLeft,
-                int const originTop, 
-                int const overCols,
-                int const overRows,
-                int const underCols,
-                int const underRows ) {
+warnOutOfFrame(int const originLeft,
+               int const originTop, 
+               int const overCols,
+               int const overRows,
+               int const underCols,
+               int const underRows) {
 
     if (originLeft >= underCols)
         pm_message("WARNING: the overlay is entirely off the right edge "
@@ -339,6 +342,10 @@ computeOverlayPosition(int                const underCols,
    The origin may be outside the underlying image (so e.g. *originLeftP may
    be negative or > image width).  That means not all of the overlay image
    actually gets used.  In fact, there may be no overlap at all.
+
+   But we insist that the span from the topmost row of the two images
+   to the bottommost row be less than INT_MAX so that Caller can
+   use an integer for a row number in the combination.
 -----------------------------------------------------------------------------*/
     int xalign, yalign;
 
@@ -470,6 +477,44 @@ adaptRowToOutputFormat(struct pam * const inpamP,
 
 
 static void
+composeRow(int              const originleft, 
+           struct pam *     const underlayPamP,
+           struct pam *     const overlayPamP,
+           bool             const invertAlpha,
+           float            const masterOpacity,
+           bool             const overlayHasOpacity,
+           unsigned int     const opacityPlane,
+           struct pam *     const composedPamP,
+           enum sampleScale const sampleScale,
+           const tuple *    const underlayTuplerow,
+           const tuple *    const overlayTuplerow,
+           const tuplen *   const alphaTuplerown,
+           tuple *          const composedTuplerow) {
+
+    unsigned int col;
+    for (col = 0; col < composedPamP->width; ++col) {
+        int const ovlcol = col - originleft;
+
+        if (ovlcol >= 0 && ovlcol < overlayPamP->width) {
+            tuplen const alphaTuplen = 
+                alphaTuplerown ? alphaTuplerown[ovlcol] : NULL;
+
+            overlayPixel(overlayTuplerow[ovlcol], overlayPamP,
+                         underlayTuplerow[col], underlayPamP,
+                         alphaTuplen, invertAlpha,
+                         overlayHasOpacity, opacityPlane,
+                         composedTuplerow[col], composedPamP,
+                         masterOpacity, sampleScale);
+        } else
+            /* Overlay image does not touch this column. */
+            pnm_assigntuple(composedPamP, composedTuplerow[col],
+                            underlayTuplerow[col]);
+    }
+}
+
+
+
+static void
 composite(int          const originleft, 
           int          const origintop, 
           struct pam * const underlayPamP,
@@ -495,6 +540,9 @@ composite(int          const originleft,
    go.  It is not necessarily inside the underlying image (in fact,
    may be negative).  Only the part of the overlay that actually
    intersects the underlying image, if any, gets into the output.
+
+   We assume that the span from the topmost row of the two images to
+   the bottommost row is less than INT_MAX.
 -----------------------------------------------------------------------------*/
     enum sampleScale const sampleScale = 
         assumeLinear ? INTENSITY_SAMPLE : GAMMA_SAMPLE;
@@ -515,9 +563,13 @@ composite(int          const originleft,
     overlayTuplerow  = pnm_allocpamrow(overlayPamP);
     if (alphaPamP)
         alphaTuplerown = pnm_allocpamrown(alphaPamP);
+    else
+        alphaTuplerown = NULL;
 
     pnm_writepaminit(composedPamP);
 
+    assert(INT_MAX - overlayPamP->height > origintop); /* arg constraint */
+
     for (underlayRow = MIN(0, origintop), overlayRow = MIN(0, -origintop);
          underlayRow < MAX(underlayPamP->height, 
                            origintop + overlayPamP->height);
@@ -541,25 +593,12 @@ composite(int          const originleft,
 
                 pnm_writepamrow(composedPamP, underlayTuplerow);
             } else {
-                unsigned int col;
-                for (col = 0; col < composedPamP->width; ++col) {
-                    int const ovlcol = col - originleft;
-
-                    if (ovlcol >= 0 && ovlcol < overlayPamP->width) {
-                        tuplen const alphaTuplen = 
-                            alphaPamP ? alphaTuplerown[ovlcol] : NULL;
-
-                        overlayPixel(overlayTuplerow[ovlcol], overlayPamP,
-                                     underlayTuplerow[col], underlayPamP,
-                                     alphaTuplen, invertAlpha,
-                                     overlayHasOpacity, opacityPlane,
-                                     composedTuplerow[col], composedPamP,
-                                     masterOpacity, sampleScale);
-                    } else
-                        /* Overlay image does not touch this column. */
-                        pnm_assigntuple(composedPamP, composedTuplerow[col],
-                                        underlayTuplerow[col]);
-                }
+                composeRow(originleft, underlayPamP, overlayPamP,
+                           invertAlpha, masterOpacity, overlayHasOpacity,
+                           opacityPlane, composedPamP, sampleScale,
+                           underlayTuplerow, overlayTuplerow, alphaTuplerown,
+                           composedTuplerow);
+                
                 pnm_writepamrow(composedPamP, composedTuplerow);
             }
         }
@@ -574,7 +613,7 @@ composite(int          const originleft,
 
 
 int
-main(int argc, char *argv[]) {
+main(int argc, const char *argv[]) {
 
     struct cmdlineInfo cmdline;
     FILE * underlayFileP;
@@ -586,7 +625,7 @@ main(int argc, char *argv[]) {
     struct pam composedPam;
     int originLeft, originTop;
 
-    pnm_init(&argc, argv);
+    pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
 
diff --git a/editor/pamcut.c b/editor/pamcut.c
index c5d53f44..8d4c2240 100644
--- a/editor/pamcut.c
+++ b/editor/pamcut.c
@@ -12,6 +12,8 @@
 
 #include <limits.h>
 #include <assert.h>
+
+#include "pm_c_util.h"
 #include "pam.h"
 #include "shhopt.h"
 #include "mallocvar.h"
@@ -94,8 +96,10 @@ parseCommandLine(int argc, char ** const argv,
         pm_error("-height may not be negative.");
 
     if ((argc-1) != 0 && (argc-1) != 1 && (argc-1) != 4 && (argc-1) != 5)
-        pm_error("Wrong number of arguments.  "
-                 "Must be 0, 1, 4, or 5 arguments.");
+        pm_error("Wrong number of arguments: %u.  The only argument in "
+                 "the preferred syntax is an optional input file name.  "
+                 "In older syntax, there are also forms with 4 and 5 "
+                 "arguments.", argc-1);
 
     switch (argc-1) {
     case 0:
@@ -269,44 +273,51 @@ computeCutBounds(const int cols, const int rows,
 
 
 static void
-rejectOutOfBounds(const int cols, const int rows, 
-                  const int leftcol, const int rightcol, 
-                  const int toprow, const int bottomrow) {
-
-    /* Reject coordinates off the edge */
-
-    if (leftcol < 0)
-        pm_error("You have specified a left edge (%d) that is beyond\n"
-                 "the left edge of the image (0)", leftcol);
-    if (leftcol > cols-1)
-        pm_error("You have specified a left edge (%d) that is beyond\n"
-                 "the right edge of the image (%d)", leftcol, cols-1);
-    if (rightcol < 0)
-        pm_error("You have specified a right edge (%d) that is beyond\n"
-                 "the left edge of the image (0)", rightcol);
-    if (rightcol > cols-1)
-        pm_error("You have specified a right edge (%d) that is beyond\n"
-                 "the right edge of the image (%d)", rightcol, cols-1);
-    if (leftcol > rightcol) 
-        pm_error("You have specified a left edge (%d) that is to the right\n"
-                 "of the right edge you specified (%d)", 
+rejectOutOfBounds(unsigned int const cols,
+                  unsigned int const rows,
+                  int          const leftcol,
+                  int          const rightcol,
+                  int          const toprow,
+                  int          const bottomrow,
+                  bool         const pad) {
+
+     /* Reject coordinates off the edge */
+
+    if (!pad) {
+        if (leftcol < 0)
+            pm_error("You have specified a left edge (%d) that is beyond "
+                     "the left edge of the image (0)", leftcol);
+        if (leftcol > (int)(cols-1))
+            pm_error("You have specified a left edge (%d) that is beyond "
+                     "the right edge of the image (%u)", leftcol, cols-1);
+        if (rightcol < 0)
+            pm_error("You have specified a right edge (%d) that is beyond "
+                     "the left edge of the image (0)", rightcol);
+        if (rightcol > (int)(cols-1))
+            pm_error("You have specified a right edge (%d) that is beyond "
+                     "the right edge of the image (%u)", rightcol, cols-1);
+        if (toprow < 0)
+            pm_error("You have specified a top edge (%d) that is above "
+                     "the top edge of the image (0)", toprow);
+        if (toprow > (int)(rows-1))
+            pm_error("You have specified a top edge (%d) that is below "
+                     "the bottom edge of the image (%u)", toprow, rows-1);
+        if (bottomrow < 0)
+            pm_error("You have specified a bottom edge (%d) that is above "
+                     "the top edge of the image (0)", bottomrow);
+        if (bottomrow > (int)(rows-1))
+            pm_error("You have specified a bottom edge (%d) that is below "
+                     "the bottom edge of the image (%u)", bottomrow, rows-1);
+    }
+
+    if (leftcol > rightcol)
+        pm_error("You have specified a left edge (%d) that is to the right of "
+                 "the right edge you specified (%d)",
                  leftcol, rightcol);
-    
-    if (toprow < 0)
-        pm_error("You have specified a top edge (%d) that is above the top "
-                 "edge of the image (0)", toprow);
-    if (toprow > rows-1)
-        pm_error("You have specified a top edge (%d) that is below the\n"
-                 "bottom edge of the image (%d)", toprow, rows-1);
-    if (bottomrow < 0)
-        pm_error("You have specified a bottom edge (%d) that is above the\n"
-                 "top edge of the image (0)", bottomrow);
-    if (bottomrow > rows-1)
-        pm_error("You have specified a bottom edge (%d) that is below the\n"
-                 "bottom edge of the image (%d)", bottomrow, rows-1);
-    if (toprow > bottomrow) 
-        pm_error("You have specified a top edge (%d) that is below\n"
-                 "the bottom edge you specified (%d)", 
+
+    if (toprow > bottomrow)
+        pm_error("You have specified a top edge (%d) that is below "
+                 "the bottom edge you specified (%d)",
                  toprow, bottomrow);
 }
 
@@ -398,13 +409,15 @@ struct rowCutter {
    create a new one then.
 */
 
+
+
 static void
-createRowCutter(struct pam *        const inpamP,
-                struct pam *        const outpamP,
+createRowCutter(const struct pam *  const inpamP,
+                const struct pam *  const outpamP,
                 int                 const leftcol,
                 int                 const rightcol,
                 struct rowCutter ** const rowCutterPP) {
-    
+
     struct rowCutter * rowCutterP;
     tuple * inputPointers;
     tuple * outputPointers;
@@ -412,7 +425,7 @@ createRowCutter(struct pam *        const inpamP,
     tuple blackTuple;
     tuple discardTuple;
     int col;
-    
+
     assert(inpamP->depth >= outpamP->depth);
         /* Entry condition.  If this weren't true, we could not simply
            treat an input tuple as an output tuple.
@@ -483,15 +496,144 @@ destroyRowCutter(struct rowCutter * const rowCutterP) {
 
 
 static void
+extractRowsGen(const struct pam * const inpamP,
+               const struct pam * const outpamP,
+               int                const leftcol,
+               int                const rightcol,
+               int                const toprow,
+               int                const bottomrow) {
+
+    struct rowCutter * rowCutterP;
+    int row;
+
+    /* Write out top padding */
+    if (0 - toprow > 0)
+        writeBlackRows(outpamP, 0 - toprow);
+
+    createRowCutter(inpamP, outpamP, leftcol, rightcol, &rowCutterP);
+
+    /* Read input and write out rows extracted from it */
+    for (row = 0; row < inpamP->height; ++row) {
+        if (row >= toprow && row <= bottomrow){
+            pnm_readpamrow(inpamP, rowCutterP->inputPointers);
+            pnm_writepamrow(outpamP, rowCutterP->outputPointers);
+        } else  /* row < toprow || row > bottomrow */
+            pnm_readpamrow(inpamP, NULL);
+        
+        /* Note that we may be tempted just to quit after reaching the bottom
+           of the extracted image, but that would cause a broken pipe problem
+           for the process that's feeding us the image.
+        */
+    }
+
+    destroyRowCutter(rowCutterP);
+    
+    /* Write out bottom padding */
+    if ((bottomrow - (inpamP->height-1)) > 0)
+        writeBlackRows(outpamP, bottomrow - (inpamP->height-1));
+}
+
+
+
+static void
+makeBlackPBMRow(unsigned char * const bitrow,
+                unsigned int    const cols) {
+
+    unsigned int const colByteCnt = pbm_packed_bytes(cols);
+
+    unsigned int i;
+
+    for (i = 0; i < colByteCnt; ++i)
+        bitrow[i] = PBM_BLACK * 0xff;
+
+    if (PBM_BLACK != 0 && cols % 8 > 0)
+        bitrow[colByteCnt-1] <<= (8 - cols % 8);
+}
+
+
+
+static void
+extractRowsPBM(const struct pam * const inpamP,
+               const struct pam * const outpamP,
+               int                const leftcol,
+               int                const rightcol,
+               int                const toprow,
+               int                const bottomrow) {
+
+    unsigned char * bitrow;
+    int             readOffset, writeOffset;
+    int             row;
+    unsigned int    totalWidth;
+
+    assert(leftcol <= rightcol);
+    assert(toprow <= bottomrow);
+
+    if (leftcol > 0) {
+        totalWidth = MAX(rightcol+1, inpamP->width) + 7;
+        if (totalWidth > INT_MAX)
+            /* Prevent overflows in pbm_allocrow_packed() */
+            pm_error("Specified right edge is too far "
+                     "from the right end of input image");
+        
+        readOffset  = 0;
+        writeOffset = leftcol;
+    } else {
+        totalWidth = -leftcol + MAX(rightcol+1, inpamP->width);
+        if (totalWidth > INT_MAX)
+            pm_error("Specified left/right edge is too far "
+                     "from the left/right end of input image");
+        
+        readOffset = -leftcol;
+        writeOffset = 0;
+    }
+
+    bitrow = pbm_allocrow_packed(totalWidth);
+
+    if (toprow < 0 || leftcol < 0 || rightcol >= inpamP->width){
+        makeBlackPBMRow(bitrow, totalWidth);
+        if (toprow < 0) {
+            int row;
+            for (row=0; row < 0 - toprow; ++row)
+                pbm_writepbmrow_packed(outpamP->file, bitrow,
+                                       outpamP->width, 0);
+        }
+    }
+
+    for (row = 0; row < inpamP->height; ++row){
+        if (row >= toprow && row <= bottomrow) {
+            pbm_readpbmrow_bitoffset(inpamP->file, bitrow, inpamP->width,
+                                     inpamP->format, readOffset);
+
+            pbm_writepbmrow_bitoffset(outpamP->file, bitrow, outpamP->width,
+                                      0, writeOffset);
+  
+            if (rightcol >= inpamP->width)
+                /* repair right padding */
+                bitrow[writeOffset/8 + pbm_packed_bytes(outpamP->width) - 1] =
+                    0xff * PBM_BLACK;
+        } else
+            pnm_readpamrow(inpamP, NULL);    /* read and discard */
+    }
+
+    if (bottomrow - (inpamP->height-1) > 0) {
+        int row;
+        makeBlackPBMRow(bitrow, outpamP->width);
+        for (row = 0; row < bottomrow - (inpamP->height-1); ++row)
+            pbm_writepbmrow_packed(outpamP->file, bitrow, outpamP->width, 0);
+    }
+    pbm_freerow_packed(bitrow);
+}
+
+
+
+static void
 cutOneImage(FILE *             const ifP,
             struct cmdlineInfo const cmdline,
             FILE *             const ofP) {
 
-    int row;
     int leftcol, rightcol, toprow, bottomrow;
     struct pam inpam;   /* Input PAM image */
     struct pam outpam;  /* Output PAM image */
-    struct rowCutter * rowCutterP;
 
     pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
     
@@ -501,22 +643,11 @@ cutOneImage(FILE *             const ifP,
                      cmdline.width, cmdline.height, 
                      &leftcol, &rightcol, &toprow, &bottomrow);
 
-    if (!cmdline.pad)
-        rejectOutOfBounds(inpam.width, inpam.height, leftcol, rightcol, 
-                          toprow, bottomrow);
-    else {
-        if (cmdline.left > cmdline.right) 
-            pm_error("You have specified a left edge (%d) that is to the right\n"
-                     "of the right edge you specified (%d)", 
-                     cmdline.left, cmdline.right);
-        
-        if (cmdline.top > cmdline.bottom) 
-            pm_error("You have specified a top edge (%d) that is below\n"
-                     "the bottom edge you specified (%d)", 
-                     cmdline.top, cmdline.bottom);
-    }
+    rejectOutOfBounds(inpam.width, inpam.height, leftcol, rightcol, 
+                      toprow, bottomrow, cmdline.pad);
+
     if (cmdline.verbose) {
-        pm_message("Image goes from Row 0, Column 0 through Row %d, Column %d",
+        pm_message("Image goes from Row 0, Column 0 through Row %u, Column %u",
                    inpam.height-1, inpam.width-1);
         pm_message("Cutting from Row %d, Column %d through Row %d Column %d",
                    toprow, leftcol, bottomrow, rightcol);
@@ -524,36 +655,15 @@ cutOneImage(FILE *             const ifP,
 
     outpam = inpam;    /* Initial value -- most fields should be same */
     outpam.file   = ofP;
-    outpam.width  = rightcol-leftcol+1;
-    outpam.height = bottomrow-toprow+1;
+    outpam.width  = rightcol - leftcol + 1;
+    outpam.height = bottomrow - toprow + 1;
 
     pnm_writepaminit(&outpam);
 
-    /* Write out top padding */
-    if (0 - toprow > 0)
-        writeBlackRows(&outpam, 0 - toprow);
-
-    createRowCutter(&inpam, &outpam, leftcol, rightcol, &rowCutterP);
-
-    /* Read input and write out rows extracted from it */
-    for (row = 0; row < inpam.height; ++row) {
-        if (row >= toprow && row <= bottomrow){
-            pnm_readpamrow(&inpam, rowCutterP->inputPointers);
-            pnm_writepamrow(&outpam, rowCutterP->outputPointers);
-        } else  /* row < toprow || row > bottomrow */
-            pnm_readpamrow(&inpam, NULL);
-        
-        /* Note that we may be tempted just to quit after reaching the bottom
-           of the extracted image, but that would cause a broken pipe problem
-           for the process that's feeding us the image.
-        */
-    }
-
-    destroyRowCutter(rowCutterP);
-    
-    /* Write out bottom padding */
-    if ((bottomrow - (inpam.height-1)) > 0)
-        writeBlackRows(&outpam, bottomrow - (inpam.height-1));
+    if (PNM_FORMAT_TYPE(outpam.format) == PBM_TYPE)
+        extractRowsPBM(&inpam, &outpam, leftcol, rightcol, toprow, bottomrow);
+    else
+        extractRowsGen(&inpam, &outpam, leftcol, rightcol, toprow, bottomrow);
 }
 
 
diff --git a/editor/pamdice.c b/editor/pamdice.c
index 062e05e3..f72420ab 100644
--- a/editor/pamdice.c
+++ b/editor/pamdice.c
@@ -11,6 +11,7 @@
 
 #include <string.h>
 
+#include "pm_c_util.h"
 #include "pam.h"
 #include "shhopt.h"
 #include "nstring.h"
@@ -23,8 +24,8 @@ 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 */
-    char * outstem; 
+    const char * inputFileName;  /* '-' if stdin */
+    const char * outstem; 
         /* null-terminated string, max MAXFILENAMELEN-10 characters */
     unsigned int sliceVertically;    /* boolean */
     unsigned int sliceHorizontally;  /* boolean */
@@ -39,8 +40,8 @@ struct cmdlineInfo {
 
 
 static void
-parseCommandLine ( int argc, char ** argv,
-                   struct cmdlineInfo * const cmdlineP ) {
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP ) {
 /*----------------------------------------------------------------------------
    parse program command line described in Unix standard form by argc
    and argv.  Return the information in the options as *cmdlineP.  
@@ -63,17 +64,17 @@ parseCommandLine ( int argc, char ** argv,
 
     option_def_index = 0;   /* incremented by OPTENT3 */
     OPTENT3(0, "width",       OPT_UINT,    &cmdlineP->width,       
-            &cmdlineP->sliceVertically,       0 );
+            &cmdlineP->sliceVertically,       0);
     OPTENT3(0, "height",      OPT_UINT,    &cmdlineP->height,
-            &cmdlineP->sliceHorizontally,     0 );
+            &cmdlineP->sliceHorizontally,     0);
     OPTENT3(0, "hoverlap",    OPT_UINT,    &cmdlineP->hoverlap,
-            &hoverlapSpec,                    0 );
+            &hoverlapSpec,                    0);
     OPTENT3(0, "voverlap",    OPT_UINT,    &cmdlineP->voverlap,
-            &voverlapSpec,                    0 );
+            &voverlapSpec,                    0);
     OPTENT3(0, "outstem",     OPT_STRING,  &cmdlineP->outstem,
-            &outstemSpec,                     0 );
+            &outstemSpec,                     0);
     OPTENT3(0, "verbose",     OPT_FLAG,    NULL,
-            &cmdlineP->verbose,               0 );
+            &cmdlineP->verbose,               0);
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
@@ -104,13 +105,14 @@ parseCommandLine ( int argc, char ** argv,
     if (!outstemSpec)
         pm_error("You must specify the -outstem option to indicate where to "
                  "put the output images.");
+
     if (argc-1 < 1)
-        cmdlineP->inputFilespec = "-";
+        cmdlineP->inputFileName = "-";
     else if (argc-1 == 1)
-        cmdlineP->inputFilespec = argv[1];
+        cmdlineP->inputFileName = argv[1];
     else 
         pm_error("Progam takes at most 1 parameter: the file specification.  "
-                 "You specified %d", argc-1);
+                 "You specified %u", argc-1);
 }
 
 
@@ -451,7 +453,7 @@ main(int argc, char ** argv) {
     
     parseCommandLine(argc, argv, &cmdline);
         
-    ifP = pm_openr(cmdline.inputFilespec);
+    ifP = pm_openr(cmdline.inputFileName);
 
     pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
 
diff --git a/editor/pamdither.c b/editor/pamdither.c
index 5eb931a6..e084892c 100644
--- a/editor/pamdither.c
+++ b/editor/pamdither.c
@@ -8,6 +8,7 @@
   This is meant to replace Ppmdither by Christos Zoulas, 1991.
 =============================================================================*/
 
+#include "pm_c_util.h"
 #include "mallocvar.h"
 #include "shhopt.h"
 #include "pam.h"
diff --git a/editor/pamditherbw.c b/editor/pamditherbw.c
index f9e159f1..90e2abd5 100644
--- a/editor/pamditherbw.c
+++ b/editor/pamditherbw.c
@@ -13,13 +13,19 @@
 #include <assert.h>
 #include <string.h>
 
+#include "pm_c_util.h"
 #include "pam.h"
 #include "dithers.h"
 #include "mallocvar.h"
 #include "shhopt.h"
 #include "pm_gamma.h"
 
-enum halftone {QT_FS, QT_THRESH, QT_DITHER8, QT_CLUSTER, QT_HILBERT};
+enum halftone {QT_FS,
+               QT_ATKINSON,
+               QT_THRESH,
+               QT_DITHER8,
+               QT_CLUSTER,
+               QT_HILBERT};
 
 enum ditherType {DT_REGULAR, DT_CLUSTER};
 
@@ -53,7 +59,7 @@ parseCommandLine(int argc, char ** argv,
     optStruct3 opt;
 
     unsigned int option_def_index;
-    unsigned int floydOpt, hilbertOpt, thresholdOpt, dither8Opt,
+    unsigned int floydOpt, atkinsonOpt, hilbertOpt, thresholdOpt, dither8Opt,
         cluster3Opt, cluster4Opt, cluster8Opt;
     unsigned int valueSpec, clumpSpec;
 
@@ -62,6 +68,7 @@ parseCommandLine(int argc, char ** argv,
     option_def_index = 0;   /* incremented by OPTENTRY */
     OPTENT3(0, "floyd",     OPT_FLAG,  NULL, &floydOpt,     0);
     OPTENT3(0, "fs",        OPT_FLAG,  NULL, &floydOpt,     0);
+    OPTENT3(0, "atkinson",  OPT_FLAG,  NULL, &atkinsonOpt,     0);
     OPTENT3(0, "threshold", OPT_FLAG,  NULL, &thresholdOpt, 0);
     OPTENT3(0, "hilbert",   OPT_FLAG,  NULL, &hilbertOpt,   0);
     OPTENT3(0, "dither8",   OPT_FLAG,  NULL, &dither8Opt,   0);
@@ -84,15 +91,17 @@ parseCommandLine(int argc, char ** argv,
     optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
-    if (floydOpt + thresholdOpt + hilbertOpt + dither8Opt + 
+    if (floydOpt + atkinsonOpt + thresholdOpt + hilbertOpt + dither8Opt + 
         cluster3Opt + cluster4Opt + cluster8Opt == 0)
         cmdlineP->halftone = QT_FS;
-    else if (floydOpt + thresholdOpt + dither8Opt + 
+    else if (floydOpt + atkinsonOpt + thresholdOpt + dither8Opt + 
         cluster3Opt + cluster4Opt + cluster8Opt > 1)
-        pm_error("No cannot specify more than one halftoning type");
+        pm_error("Cannot specify more than one halftoning type");
     else {
         if (floydOpt)
             cmdlineP->halftone = QT_FS;
+        else if (atkinsonOpt)
+            cmdlineP->halftone = QT_ATKINSON;
         else if (thresholdOpt)
             cmdlineP->halftone = QT_THRESH;
         else if (hilbertOpt) {
@@ -390,10 +399,22 @@ struct converter {
 
 struct fsState {
     float * thiserr;
+        /* 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 
+           future column N of the next row.
+        */
     bool fs_forward;
-    samplen threshval;
-        /* The power value we consider to be half white */
+        /* We're going forward (left to right) through the current row */
+    samplen white;
+        /* The power value we consider to be white (normally 1.0).
+           Constant. */
+    samplen halfWhite;
+        /* The power value we consider to be half white (always half of
+           'white'; carried separately to save computation)
+        */
 };
 
 
@@ -423,37 +444,39 @@ fsConvertRow(struct converter * const converterP,
     }
 
     do {
-        samplen sum;
+        samplen const thisPixelPower =
+            MIN(pm_ungamma709(grayrow[col][0]), stateP->white);
+        samplen accum;
+
+        accum = thisPixelPower + thiserr[col + 1];
 
-        sum = MIN(2*stateP->threshval, pm_ungamma709(grayrow[col][0])) +
-            thiserr[col + 1];
-        if (sum >= stateP->threshval) {
-            /* We've accumulated enough light to justify a white output
-               pixel.
+        if (accum >= stateP->halfWhite) {
+            /* We've accumulated enough light (power) to justify a
+               white output pixel.
             */
             bitrow[col][0] = PAM_BW_WHITE;
-            /* Remove from sum the power of the white output pixel */
-            sum -= 2*stateP->threshval;
+            /* Remove from sum the power of this white output pixel */
+            accum -= stateP->white;
         } else
             bitrow[col][0] = PAM_BLACK;
-        
-        /* Forward the power from current input pixel and the power
-           forwarded from previous input pixels to the current pixel,
-           to future output pixels, but subtract out any power we put
-           into the current output pixel.  
+
+        /* Forward to future output pixels 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
+           current output pixel.
         */
         if (stateP->fs_forward) {
-            thiserr[col + 2] += (sum * 7) / 16;
-            nexterr[col    ] += (sum * 3) / 16;
-            nexterr[col + 1] += (sum * 5) / 16;
-            nexterr[col + 2] += (sum    ) / 16;
+            thiserr[col + 2] += (accum * 7) / 16;
+            nexterr[col    ] += (accum * 3) / 16;
+            nexterr[col + 1] += (accum * 5) / 16;
+            nexterr[col + 2] += (accum * 1) / 16;
             
             ++col;
         } else {
-            thiserr[col    ] += (sum * 7) / 16;
-            nexterr[col + 2] += (sum * 3) / 16;
-            nexterr[col + 1] += (sum * 5) / 16;
-            nexterr[col    ] += (sum    ) / 16;
+            thiserr[col    ] += (accum * 7) / 16;
+            nexterr[col + 2] += (accum * 3) / 16;
+            nexterr[col + 1] += (accum * 5) / 16;
+            nexterr[col    ] += (accum * 1) / 16;
             
             --col;
         }
@@ -468,7 +491,13 @@ fsConvertRow(struct converter * const converterP,
 
 static void
 fsDestroy(struct converter * const converterP) {
-    free(converterP->stateP);
+
+    struct fsState * const stateP = converterP->stateP;
+
+    free(stateP->thiserr);
+    free(stateP->nexterr);
+
+    free(stateP);
 }
 
 
@@ -489,7 +518,7 @@ createFsConverter(struct pam * const graypamP,
     /* Initialize Floyd-Steinberg error vectors. */
     MALLOCARRAY_NOFAIL(stateP->thiserr, graypamP->width + 2);
     MALLOCARRAY_NOFAIL(stateP->nexterr, graypamP->width + 2);
-    srand((int)(time(NULL) ^ getpid()));
+    srand(pm_randseed());
 
     {
         /* (random errors in [-1/8 .. 1/8]) */
@@ -498,7 +527,8 @@ createFsConverter(struct pam * const graypamP,
             stateP->thiserr[col] = ((float)rand()/RAND_MAX - 0.5) / 4;
     }
 
-    stateP->threshval  = threshFraction;
+    stateP->halfWhite = threshFraction;
+    stateP->white = 2 * threshFraction;
 
     stateP->fs_forward = TRUE;
 
@@ -509,6 +539,150 @@ createFsConverter(struct pam * const graypamP,
 
 
 
+struct atkinsonState {
+    float * error[3];
+        /* error[R][C] is the power from previous pixels to include
+           in column C of the Rth row down from the current row
+           (0th row is the current row).
+
+           No error propagates down more than two rows.
+
+           For R == 0, C is a column we haven't done yet.
+        */
+    samplen white;
+        /* The power value we consider to be white (normally 1.0).
+           Constant. */
+    samplen halfWhite;
+        /* The power value we consider to be half white (always half of
+           'white'; carried separately to save computation)
+        */
+};
+
+
+static void
+moveAtkinsonErrorWindowDown(struct converter * const converterP) {
+                            
+    struct atkinsonState * const stateP = converterP->stateP;
+
+    float * const oldError0 = stateP->error[0];
+
+    unsigned int relRow;
+    unsigned int col;
+
+    for (relRow = 0; relRow < 2; ++relRow)
+        stateP->error[relRow] = stateP->error[relRow+1];
+
+    for (col = 0; col < converterP->cols + 2; ++col)
+        oldError0[col] = 0.0;
+
+    stateP->error[2] = oldError0;
+}
+
+
+
+static void
+atkinsonConvertRow(struct converter * const converterP,
+                   unsigned int       const row,
+                   tuplen                   grayrow[],
+                   tuple                    bitrow[]) {
+
+    /* See http://www.tinrocket.com/projects/programming/graphics/00158/
+       for a description of the Atkinson algorithm
+    */
+
+    struct atkinsonState * const stateP = converterP->stateP;
+
+    samplen ** const error = stateP->error;
+
+    unsigned int col;
+
+    for (col = 0; col < converterP->cols; ++col) {
+        samplen accum;
+
+        accum = pm_ungamma709(grayrow[col][0]) + error[0][col + 1];
+        if (accum >= stateP->halfWhite) {
+            /* We've accumulated enough light (power) to justify a
+               white output pixel.
+            */
+            bitrow[col][0] = PAM_BW_WHITE;
+            /* Remove from accum the power of this white output pixel */
+            accum -= stateP->white;
+        } else
+            bitrow[col][0] = PAM_BLACK;
+        
+        /* 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
+           current output pixel.
+        */
+        error[0][col+1] += accum/8;
+        error[0][col+2] += accum/8;
+        if (col > 0)
+            error[1][col-1] += accum/8;
+        error[1][col  ] += accum/8;
+        error[1][col+1] += accum/8;
+        error[2][col  ] += accum/8;
+    }
+    
+    moveAtkinsonErrorWindowDown(converterP);
+}
+
+
+
+static void
+atkinsonDestroy(struct converter * const converterP) {
+
+    struct atkinsonState * const stateP = converterP->stateP;
+
+    unsigned int relRow;
+
+    for (relRow = 0; relRow < 3; ++relRow)
+        free(stateP->error[relRow]);
+
+    free(stateP);
+}
+
+
+
+static struct converter
+createAtkinsonConverter(struct pam * const graypamP,
+                        float        const threshFraction) {
+
+    struct atkinsonState * stateP;
+    struct converter converter;
+    unsigned int relRow;
+    
+    converter.cols       = graypamP->width;
+    converter.convertRow = &atkinsonConvertRow;
+    converter.destroy    = &atkinsonDestroy;
+
+    MALLOCVAR_NOFAIL(stateP);
+
+    for (relRow = 0; relRow < 3; ++relRow)
+        MALLOCARRAY_NOFAIL(stateP->error[relRow], graypamP->width + 2);
+
+    srand(pm_randseed());
+
+    {
+        /* (random errors in [-1/8 .. 1/8]) */
+        unsigned int col;
+        for (col = 0; col < graypamP->width + 2; ++col) {
+            stateP->error[0][col] = ((float)rand()/RAND_MAX - 0.5) / 4;
+            stateP->error[1][col] = 0.0;
+            stateP->error[2][col] = 0.0;
+        }
+    }
+
+    stateP->halfWhite = threshFraction;
+    stateP->white = 2 * threshFraction;
+
+    converter.stateP = stateP;
+
+    return converter;
+}
+
+
+
 struct threshState {
     samplen threshval;
 };
@@ -705,6 +879,9 @@ main(int argc, char *argv[]) {
         case QT_FS:
             converter = createFsConverter(&graypam, cmdline.threshval);
             break;
+        case QT_ATKINSON:
+            converter = createAtkinsonConverter(&graypam, cmdline.threshval);
+            break;
         case QT_THRESH:
             converter = createThreshConverter(&graypam, cmdline.threshval);
             break;
diff --git a/editor/pamedge.c b/editor/pamedge.c
index e73c9d17..ac94e2ae 100644
--- a/editor/pamedge.c
+++ b/editor/pamedge.c
@@ -188,7 +188,7 @@ main(int argc, char *argv[]) {
     pnm_writepaminit(&outpam);
 
     /* First row is black: */
-    writeBlackRow(&outpam      );
+    writeBlackRow(&outpam);
 
     writeMiddleRows(&inpam, &outpam);
 
diff --git a/editor/pamenlarge.c b/editor/pamenlarge.c
index 15b91b4f..b3039424 100644
--- a/editor/pamenlarge.c
+++ b/editor/pamenlarge.c
@@ -5,8 +5,10 @@
   author.
 =============================================================================*/
 
-#include "pam.h"
-#include "mallocvar.h"
+#include "netpbm/mallocvar.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/pam.h"
+#include "netpbm/pbm.h"
 
 struct cmdlineInfo {
     /* All the information the user supplied in the command line,
@@ -19,7 +21,8 @@ struct cmdlineInfo {
 
 
 static void
-parseCommandLine(int argc, char ** const argv,
+parseCommandLine(int                  const argc,
+                 const char **        const argv,
                  struct cmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that the file spec array we return is stored in the storage that
@@ -69,45 +72,231 @@ makeOutputRowMap(tuple **     const outTupleRowP,
 
 
 
-int
-main(int    argc, 
-     char * argv[]) {
+static void
+validateComputableDimensions(unsigned int const width,
+                             unsigned int const height,
+                             unsigned int const scaleFactor) {
+/*----------------------------------------------------------------------------
+   Make sure that multiplication for output image width and height do not
+   overflow.
+   See validateComputetableSize() in libpam.c
+   and pbm_readpbminitrest() in libpbm2.c
+-----------------------------------------------------------------------------*/
+    unsigned int const maxWidthHeight = INT_MAX - 2;
+    unsigned int const maxScaleFactor = maxWidthHeight / MAX(height, width);
 
-    struct cmdlineInfo cmdline;
-    FILE * ifP;
-    struct pam inpam;
-    struct pam outpam; 
-    tuple * tuplerow;
-    tuple * newtuplerow;
-    int row;
+    if (scaleFactor > maxScaleFactor)
+       pm_error("Scale factor '%u' too large.  "
+                "The maximum for this %u x %u input image is %u.",
+                scaleFactor, width, height, maxScaleFactor);
+}
 
-    pnm_init(&argc, argv);
 
-    parseCommandLine(argc, argv, &cmdline);
 
-    ifP = pm_openr(cmdline.inputFilespec);
- 
-    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+static void
+enlargePbmRowHorizontally(struct pam *          const inpamP,
+                          const unsigned char * const inrow,
+                          unsigned int          const inColChars,
+                          unsigned int          const outColChars,
+                          unsigned int          const scaleFactor,
+                          unsigned char *       const outrow) {
+
+    static unsigned char const dbl[16] = { 
+        0x00, 0x03, 0x0C, 0x0F, 0x30, 0x33, 0x3C, 0x3F, 
+        0xC0, 0xC3, 0xCC, 0xCF, 0xF0, 0xF3, 0xFC, 0xFF };
+
+    static unsigned char const trp1[8] = { 
+        0x00, 0x03, 0x1C, 0x1F, 0xE0, 0xE3, 0xFC, 0xFF };
+        
+    static unsigned char const trp2[16] = { 
+        0x00, 0x01, 0x0E, 0x0F, 0x70, 0x71, 0x7E, 0x7F,
+        0x80, 0x81, 0x8E, 0x8F, 0xF0, 0xF1, 0xFE, 0xFF };
+
+    static unsigned char const trp3[8] = { 
+        0x00, 0x07, 0x38, 0x3F, 0xC0, 0xC7, 0xF8, 0xFF };
+
+    static unsigned char const quin2[8] = {
+        0x00, 0x01, 0x3E, 0x3F, 0xC0, 0xC1, 0xFE, 0xFF };
+
+    static unsigned char const quin4[8] = {
+        0x00, 0x03, 0x7C, 0x7F, 0x80, 0x83, 0xFC, 0xFF };
+
+    static unsigned int const pair[4] = { 0x0000, 0x00FF, 0xFF00, 0xFFFF };
+
+    unsigned int colChar;
+
+    switch (scaleFactor) {
+    case 1:  break; /* outrow set to inrow */
+    case 2:  /* Make outrow using prefabricated parts (same for 3, 5). */ 
+        for (colChar = 0; colChar < inColChars; ++colChar) { 
+            outrow[colChar*2]  = dbl[(inrow[colChar] & 0xF0) >> 4];
+            outrow[colChar*2+1]= dbl[inrow[colChar] & 0x0F];
+            /* Possible outrow overrun by one byte. */
+        }
+        break;   
+    case 3:
+        for (colChar = 0; colChar < inColChars; ++colChar) { 
+            outrow[colChar*3]  = trp1[(inrow[colChar] & 0xF0) >> 5];
+            outrow[colChar*3+1]= trp2[(inrow[colChar] >> 2) & 0x0F];
+            outrow[colChar*3+2]= trp3[inrow[colChar] & 0x07];
+        }
+        break;  
+    case 5:
+        for (colChar = 0; colChar < inColChars; ++colChar) { 
+            outrow[colChar*5]  = pair[ (inrow[colChar] >>6) & 0x03 ] >> 5; 
+            outrow[colChar*5+1]= quin2[(inrow[colChar] >>4) & 0x07 ]; 
+            outrow[colChar*5+2]= pair[ (inrow[colChar] >>3) & 0x03 ] >> 4; 
+            outrow[colChar*5+3]= quin4[(inrow[colChar] >>1) & 0x07 ];
+            outrow[colChar*5+4]= pair[ inrow[colChar] & 0x03 ] >>3; 
+        }
+        break;
+    case 4: default:
+        /*  Unlike the above cases, we iterate through outrow.  The color
+            composition of each outrow byte is computed by consulting
+            a single bit or two consecutive bits in inrow. 
+            Color changes never happen twice in a single outrow byte.
+        */
+        for (colChar = 0; colChar < outColChars; ++colChar) {
+            unsigned int const mult = scaleFactor;
+            unsigned int const mod = colChar % mult;
+            unsigned int const bit = (mod*8)/mult;
+            /* source bit position, leftmost=0 */
+            unsigned int const offset = mult - (mod*8)%mult;
+            /* number of outrow bits derived from the same
+               "source" inrow bit, starting at and to the right
+               of leftmost bit of outrow byte, inclusive
+            */
+
+            if (offset >= 8)  /* Bits in outrow byte are all 1 or 0 */
+                outrow[colChar] =
+                    (inrow[colChar/mult] >> (7-bit) & 0x01) * 0xFF;
+            else           /* Two inrow bits influence this outrow byte */ 
+                outrow[colChar] = (unsigned char)
+                    (pair[inrow[colChar/mult] >> (6-bit) & 0x03] >> offset)
+                    & 0xFF;
+        }
+    }
+}
+
+
+
+static void
+enlargePbm(struct pam * const inpamP,
+           unsigned int const scaleFactor,
+           FILE *       const ofP) {
+
+    unsigned char * inrow;
+    unsigned char * outrow;
+
+    unsigned int row;
+
+    unsigned int const outcols = inpamP->width * scaleFactor;
+    unsigned int const outrows = inpamP->height * scaleFactor;
+    unsigned int const inColChars  = pbm_packed_bytes(inpamP->width);
+    unsigned int const outColChars = pbm_packed_bytes(outcols);
+
+    inrow  = pbm_allocrow_packed(inpamP->width);
+    
+    if (scaleFactor == 1)
+        outrow = inrow;
+    else  
+        outrow = pbm_allocrow_packed(outcols + 32);
+            /* The 32 (=4 bytes) is to allow writes beyond outrow data
+               end when scaleFactor is 2, 3, 5.  (max 4 bytes when
+               scaleFactor is 5)
+            */
 
-    outpam = inpam; 
-    outpam.file   = stdout;
-    outpam.width  = inpam.width * cmdline.scaleFactor;
-    outpam.height = inpam.height * cmdline.scaleFactor; 
+    pbm_writepbminit(ofP, outcols, outrows, 0);
+    
+    for (row = 0; row < inpamP->height; ++row) {
+        unsigned int i;
+
+        pbm_readpbmrow_packed(inpamP->file, inrow, inpamP->width,
+                              inpamP->format);
+
+        if (inpamP->width % 8 > 0) {  /* clean final partial byte */ 
+            inrow[inColChars-1] >>= 8 - inpamP->width % 8;
+            inrow[inColChars-1] <<= 8 - inpamP->width % 8;
+        }
+
+        enlargePbmRowHorizontally(inpamP, inrow, inColChars, outColChars,
+                                  scaleFactor, outrow);
+
+        for (i = 0; i < scaleFactor; ++i)  
+            pbm_writepbmrow_packed(ofP, outrow, outcols, 0);
+    }
+    
+    if (outrow != inrow)
+        pbm_freerow(outrow);
+
+    pbm_freerow(inrow);
+}
+
+
+
+static void
+enlargeGeneral(struct pam * const inpamP,
+               unsigned int const scaleFactor,
+               FILE *       const ofP) {
+/*----------------------------------------------------------------------------
+   Enlarge the input image described by *pamP.
+
+   Assume the dimensions won't cause an arithmetic overflow.
+
+   This works on all kinds of images, but is slower than enlargePbm on
+   PBM.
+-----------------------------------------------------------------------------*/
+    struct pam outpam; 
+    tuple * tuplerow;
+    tuple * newtuplerow;
+    unsigned int row;
+
+    outpam = *inpamP; 
+    outpam.file   = ofP;
+    outpam.width  = inpamP->width  * scaleFactor;
+    outpam.height = inpamP->height * scaleFactor; 
 
     pnm_writepaminit(&outpam);
 
-    tuplerow = pnm_allocpamrow(&inpam);
+    tuplerow = pnm_allocpamrow(inpamP);
 
-    makeOutputRowMap(&newtuplerow, &outpam, &inpam, tuplerow);
+    makeOutputRowMap(&newtuplerow, &outpam, inpamP, tuplerow);
 
-    for (row = 0; row < inpam.height; ++row) {
-        pnm_readpamrow(&inpam, tuplerow);
-        pnm_writepamrowmult(&outpam, newtuplerow, cmdline.scaleFactor);
+    for (row = 0; row < inpamP->height; ++row) {
+        pnm_readpamrow(inpamP, tuplerow);
+        pnm_writepamrowmult(&outpam, newtuplerow, scaleFactor);
     }
 
     free(newtuplerow);
 
     pnm_freepamrow(tuplerow);
+}
+
+
+
+int
+main(int           argc, 
+     const char ** argv) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    struct pam inpam;
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFilespec);
+ 
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+    
+    validateComputableDimensions(inpam.width, inpam.height,
+                                 cmdline.scaleFactor); 
+    
+    if (PNM_FORMAT_TYPE(inpam.format) == PBM_TYPE)
+        enlargePbm(&inpam, cmdline.scaleFactor, stdout);
+    else
+        enlargeGeneral(&inpam, cmdline.scaleFactor, stdout);
 
     pm_close(ifP);
     pm_close(stdout);
diff --git a/editor/pamenlarge.test b/editor/pamenlarge.test
index a2221d4d..7584af01 100644
--- a/editor/pamenlarge.test
+++ b/editor/pamenlarge.test
@@ -1,6 +1,6 @@
 echo Test 1.  Should print 3424505894 913236
 ./pamenlarge 3 ../testimg.ppm | cksum
-echo Test 2.  Should print 2940246561 304422
+echo Test 2.  Should print 4152147096 304422
 ppmtopgm ../testimg.ppm | ./pamenlarge 3 | cksum
 echo Test 3.  Should print 3342398172 297
 ./pamenlarge 3 ../testgrid.pbm | cksum
diff --git a/editor/pamflip.c b/editor/pamflip.c
index 59b60b56..1c479e54 100644
--- a/editor/pamflip.c
+++ b/editor/pamflip.c
@@ -11,90 +11,57 @@
 */
 
 /*
-   transformGen() is the general transformation function.
+   transformNonPbmChunk() is the general transformation function.
+   It can tranform anything, albeit slowly and expensively.
    
    The following are enhancements for specific cases:
    
-     transformRowByRowPbm()
+     transformRowByRowPbm():
+       PBM image with left-right or null transformation
      transformRowsBottomTopPbm()
+       PBM image with bottom-top transformation
      transformRowByRowNonPbm()
+       non-PBM image with left-right or null transformation
+       (also works for PBM, but we don't use it)
      transformRowsBottomTopNonPbm()
+       non-PBM image with bottom-top transformation
+       (also works for PBM, but we don't use it)
      transformPbm()
-   
-   Although we use transformGen() only when none of the enhancement
-   functions apply, it is capable of handling all cases.  (Only that it
-   is slow, and uses more memory.)  In the same manner, transformPbm() is
-   capable of handling all pbm transformations and transformRowByRowNonPbm()
-   transformRowsBottomTomNonPbm() are capable of handling pbm.
-
-
-   There is some fancy virtual memory management in transformGen() to avoid
-   page thrashing when you flip a large image in a columns-for-rows
-   way (e.g. -transpose).
-   
-   The page thrashing we're trying to avoid could happen because the
-   output of the transformation is stored in an array of tuples in
-   virtual memory.  A tuple array is stored in column-first order,
-   meaning that all the columns of particular row are contiguous, the
-   next row is next to that, etc.  If you fill up that array by
-   filling in Column 0 sequentially in every row from top to bottom,
-   you will touch a lot of different virtual memory pages, and every
-   one has to be paged in as you touch it.
-
-   If the number of virtual memory pages you touch exceeds the amount
-   of real memory the process can get, then by the time you hit the bottom
-   of the tuple array, the pages that hold the top are already paged out.
-   So if you go back and do Column 1 from top to bottom, you will again
-   touch lots of pages and have to page in every one of them.  Do this 
-   for 100 columns, and you might page in every page in the array 100 times
-   each, putting a few bytes in the page each time.
-
-   That is very expensive.  Instead, you'd like to keep the same pages in
-   real memory as long as possible and fill them up as much as you can 
-   before paging them out and working on a new set of pages.  You can do
-   that by doing Column 0 from top to say Row 10, then Column 1 from top
-   to Row 10, etc. all the way across the image.  Assuming 10 rows fits
-   in real memory, you will keep the virtual memory for the first 10 rows
-   of the tuple array in real memory until you've filled them in completely.
-   Now you go back and do Column 0 from Row 11 to Row 20, then Column 1
-   from Row 11 to Row 20, and so on.
-
-   So why are we even trying to fill in column by column instead of just
-   filling in row by row?  Because we're reading an input image row by row
-   and transforming it in such a way that a row of the input becomes
-   a column of the output.  In order to fill in a whole row of the output,
-   we'd have to read a whole column of the input, and then we have the same
-   page thrashing problem in the input.
-
-   So the optimal procedure is to do N output rows in each pass, where
-   N is the largest number of rows we can fit in real memory.  In each
-   pass, we read certain columns of every row of the input and output
-   every column of certain rows of the output.  The output area for
-   the rows in the pass gets paged in once during the pass and then
-   never again.  Note that some pages of every row of the input get
-   paged in once in each pass too.  As each input page is referenced
-   only in one burst, input pages do not compete with output pages for
-   real memory -- the working set is the output pages, which get referenced
-   cyclically.
-
-   This all worked when we used the pnm xel format, but now that we
-   use the pam tuple format, there's an extra memory reference that
-   could be causing trouble.  Because tuples have varying depth, a pam
-   row is an array of pointers to the tuples.  To access a tuple, we
-   access the tuple pointer, then the tuple.  We could probably do better,
-   because the samples are normally in the memory immediately following
-   the tuple pointers, so we could compute where a tuple's samples live
-   without actually loading the tuple address from memory.  I.e. the 
-   address of the tuple for Column 5 of Row 9 of a 3-deep 100-wide
-   image is (void*)tuples[9] + 100 * sizeof(tuple*) + 5*(3*sizeof(sample)).
+       PBM image with column-for-row transformation
+       (also works for all other transformations, but we don't use it)
+     transformNonPbmWhole()
+       non-PBM column-for-row transformation
+       (also works for PBM, but we don't use it)
+
+   Except we don't use any of the above if we can't fit the entire image
+   into the amount of memory the user gave us to work with.  In that
+   case, we fall back to transformNonPbmChunk().
+
+   Note that while virtual memory can be limited (e.g. sometimes the
+   machine's 32 bit address space itself is a limit), real memory is
+   also a concern.  With a row-for-column transformation
+   (e.g. -transpose), if we can't fit the entire image into _real_
+   memory, we will page thrash within an inch our lives.  So we count
+   on the user to tell us how much real memory we should expect to
+   get -- his -memsize option is the lesser of the available virtual
+   and real memory.
+
+   Before Netpbm 10.42 (March 2008), we had a different method for
+   dealing with memory shortage.  We didn't worry about virtual memory
+   at all, and always kept the whole target image in memory.  We did
+   not use temporary files.  But to avoid page thrashing in a
+   column-for-row transformation, we transformed in column stripes of
+   the source image, reading the input image through multiple times.
 */
 
 #define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
 #define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
 
+#include <assert.h>
 #include <limits.h>
 #include <string.h>
 
+#include "pm_c_util.h"
 #include "pam.h"
 #include "shhopt.h"
 #include "mallocvar.h"
@@ -103,22 +70,6 @@
 
 enum xformType {LEFTRIGHT, TOPBOTTOM, TRANSPOSE};
 
-struct cmdlineInfo {
-    /* All the information the user supplied in the command line,
-       in a form easy for the program to use.
-    */
-    const char *inputFilespec;  /* Filespec of input file */
-    unsigned int xformCount;
-        /* Number of transforms in the 'xformType' array */
-    enum xformType xformList[10];
-        /* Array of transforms to be applied, in order */
-    unsigned int availableMemory;
-    unsigned int pageSize;
-    unsigned int verbose;
-};
-
-
-
 static void
 parseXformOpt(const char *     const xformOpt,
               unsigned int  *  const xformCountP,
@@ -164,6 +115,175 @@ parseXformOpt(const char *     const xformOpt,
 
 
 
+/* See transformPoint() for an explanation of the transform matrix types.
+   The difference between the two types is that 'xformCore' is particular
+   to the source image dimensions and can be used to do the transformation,
+   while 'xformCore' is independent of the source image and just
+   tells what kind of transformation.
+*/
+
+struct xformCore {
+    /* a b
+       c d
+    */
+    int a;  /* -1, 0, or 1 */
+    int b;  /* -1, 0, or 1 */
+    int c;  /* -1, 0, or 1 */
+    int d;  /* -1, 0, or 1 */
+};
+
+
+
+struct xformMatrix {
+    /* a b 0
+       c d 0
+       e f 1
+    */
+    int a;  /* -1, 0, or 1 */
+    int b;  /* -1, 0, or 1 */
+    int c;  /* -1, 0, or 1 */
+    int d;  /* -1, 0, or 1 */
+    int e;  /* 0 or maximum column number in target image */
+    int f;  /* 0 or maximum row number in target image */
+};
+
+
+
+static void
+leftright(struct xformCore * const xformP) {
+    xformP->a = - xformP->a;
+    xformP->c = - xformP->c;
+}
+
+
+
+static void
+topbottom(struct xformCore * const xformP) {
+    xformP->b = - xformP->b;
+    xformP->d = - xformP->d;
+}
+
+
+
+static void
+swap(int * const xP, int * const yP) {
+
+    int const t = *xP;
+
+    *xP = *yP;
+    *yP = t;
+}
+
+
+
+static void
+transpose(struct xformCore * const xformP) {
+    swap(&xformP->a, &xformP->b);
+    swap(&xformP->c, &xformP->d);
+}
+
+
+
+static void
+computeXformCore(unsigned int       const xformCount,
+                 enum xformType     const xformType[],
+                 struct xformCore * const xformP) {
+    
+    struct xformCore const nullTransform = {1, 0, 0, 1};
+
+    unsigned int i;
+
+    *xformP = nullTransform;   /* initial value */
+
+    for (i = 0; i < xformCount; ++i) {
+        switch (xformType[i]) {
+        case LEFTRIGHT: 
+            leftright(xformP);
+            break;
+        case TOPBOTTOM:
+            topbottom(xformP);
+            break;
+        case TRANSPOSE:
+            transpose(xformP);
+            break;
+        }
+    }
+}
+
+
+
+static void
+xformDimensions(struct xformCore const xform,
+                unsigned int     const inCols,
+                unsigned int     const inRows,
+                unsigned int *   const outColsP,
+                unsigned int *   const outRowsP) {
+/*----------------------------------------------------------------------------
+   Compute the output dimensions from the input dimensions given a
+   matrix that describes a transformation.
+
+   E.g. if it's a 90 degree rotation of a 10 x 20 image, the output is
+   a 20 x 10 image.
+-----------------------------------------------------------------------------*/
+    *outColsP = abs(xform.a * inCols + xform.c * inRows);
+    *outRowsP = abs(xform.b * inCols + xform.d * inRows);
+}
+
+
+
+static void
+computeXformMatrix(struct xformMatrix * const xformP, 
+                   unsigned int         const sourceCols,
+                   unsigned int         const sourceRows,
+                   struct xformCore     const xformCore) {
+
+    int colMax = xformCore.a * (sourceCols-1) + xformCore.c * (sourceRows-1);
+    int rowMax = xformCore.b * (sourceCols-1) + xformCore.d * (sourceRows-1);
+
+    xformP->a = xformCore.a;
+    xformP->b = xformCore.b;
+    xformP->c = xformCore.c;
+    xformP->d = xformCore.d;
+    xformP->e = colMax < 0 ? -colMax : 0;
+    xformP->f = rowMax < 0 ? -rowMax : 0;
+}
+
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFilespec;  /* Filespec of input file */
+    struct xformCore xform;
+        /* Handy mathematical representation of all of transform options */
+    size_t availableMemory;
+    unsigned int pageSize;
+    unsigned int verbose;
+};
+
+
+
+static void
+interpretMemorySize(unsigned int const memsizeSpec,
+                    unsigned int const memsizeOpt,
+                    size_t *     const availableMemoryP) {
+
+    size_t const sizeMax = (size_t)(-1);
+    unsigned int const Meg = 1024 * 1024;
+
+    if (memsizeSpec) {
+        if (memsizeOpt > sizeMax / Meg)
+            pm_error("-memsize value too large: %u MiB.  Maximum this program "
+                     "can handle is %u MiB", 
+                     memsizeOpt, sizeMax / Meg);
+        *availableMemoryP = memsizeOpt * Meg;
+    } else
+        *availableMemoryP = sizeMax;
+}
+
+
+
 static void
 parseCommandLine(int argc, char ** const argv,
                  struct cmdlineInfo * const cmdlineP) {
@@ -171,7 +291,7 @@ parseCommandLine(int argc, char ** const argv,
    Note that the file spec array we return is stored in the storage that
    was passed to us as the argv array.
 -----------------------------------------------------------------------------*/
-    optEntry *option_def = malloc(100*sizeof(optEntry));
+    optEntry * option_def;
         /* Instructions to OptParseOptions3 on how to parse our options.
          */
     optStruct3 opt;
@@ -181,7 +301,13 @@ parseCommandLine(int argc, char ** const argv,
     unsigned int lr, tb, xy, r90, r270, r180, null;
     unsigned int memsizeSpec, pagesizeSpec, xformSpec;
     unsigned int memsizeOpt;
-    const char *xformOpt;
+    const char * xformOpt;
+    unsigned int xformCount;
+        /* Number of transforms in the 'xformType' array */
+    enum xformType xformList[10];
+        /* Array of transforms to be applied, in order */
+
+    MALLOCARRAY(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENTRY */
     OPTENT3(0, "lr",        OPT_FLAG,    NULL, &lr,      0);
@@ -218,43 +344,38 @@ parseCommandLine(int argc, char ** const argv,
         pm_error("You may specify only one type of flip.");
     if (lr + tb + xy + r90 + r180 + r270 + null == 1) {
         if (lr) {
-            cmdlineP->xformCount = 1;
-            cmdlineP->xformList[0] = LEFTRIGHT;
+            xformCount = 1;
+            xformList[0] = LEFTRIGHT;
         } else if (tb) {
-            cmdlineP->xformCount = 1;
-            cmdlineP->xformList[0] = TOPBOTTOM;
+            xformCount = 1;
+            xformList[0] = TOPBOTTOM;
         } else if (xy) {
-            cmdlineP->xformCount = 1;
-            cmdlineP->xformList[0] = TRANSPOSE;
+            xformCount = 1;
+            xformList[0] = TRANSPOSE;
         } else if (r90) {
-            cmdlineP->xformCount = 2;
-            cmdlineP->xformList[0] = TRANSPOSE;
-            cmdlineP->xformList[1] = TOPBOTTOM;
+            xformCount = 2;
+            xformList[0] = TRANSPOSE;
+            xformList[1] = TOPBOTTOM;
         } else if (r180) {
-            cmdlineP->xformCount = 2;
-            cmdlineP->xformList[0] = LEFTRIGHT;
-            cmdlineP->xformList[1] = TOPBOTTOM;
+            xformCount = 2;
+            xformList[0] = LEFTRIGHT;
+            xformList[1] = TOPBOTTOM;
         } else if (r270) {
-            cmdlineP->xformCount = 2;
-            cmdlineP->xformList[0] = TRANSPOSE;
-            cmdlineP->xformList[1] = LEFTRIGHT;
+            xformCount = 2;
+            xformList[0] = TRANSPOSE;
+            xformList[1] = LEFTRIGHT;
         } else if (null) {
-            cmdlineP->xformCount = 0;
+            xformCount = 0;
         }
     } else if (xformSpec) 
-        parseXformOpt(xformOpt, &cmdlineP->xformCount, cmdlineP->xformList);
+        parseXformOpt(xformOpt, &xformCount, xformList);
     else
         pm_error("You must specify an option such as -topbottom to indicate "
                  "what kind of flip you want.");
 
-    if (memsizeSpec) {
-        if (memsizeOpt > UINT_MAX / 1024 / 1024)
-            pm_error("-memsize value too large: %u MiB.  Maximum this program "
-                     "can handle is %u MiB", 
-                     memsizeOpt, UINT_MAX / 1024 / 1024);
-        cmdlineP->availableMemory = memsizeOpt * 1024 *1024;
-    } else
-        cmdlineP->availableMemory = UINT_MAX;
+    computeXformCore(xformCount, xformList, &cmdlineP->xform);
+    
+    interpretMemorySize(memsizeSpec, memsizeOpt, &cmdlineP->availableMemory);
 
     if (!pagesizeSpec)
         cmdlineP->pageSize = 4*1024;         
@@ -270,83 +391,6 @@ parseCommandLine(int argc, char ** const argv,
 
 
 
-struct xformMatrix {
-    int a;
-    int b;
-    int c;
-    int d;
-    int e;
-    int f;
-};
-
-
-
-static void
-leftright(struct xformMatrix * const xformP) {
-    xformP->a = - xformP->a;
-    xformP->c = - xformP->c;
-    xformP->e = - xformP->e + 1;
-}
-
-
-
-static void
-topbottom(struct xformMatrix * const xformP) {
-    xformP->b = - xformP->b;
-    xformP->d = - xformP->d;
-    xformP->f = - xformP->f + 1;
-}
-
-
-
-static void
-swap(int * const xP, int * const yP) {
-
-    int const t = *xP;
-
-    *xP = *yP;
-    *yP = t;
-}
-
-
-
-static void
-transpose(struct xformMatrix * const xformP) {
-    swap(&xformP->a, &xformP->b);
-    swap(&xformP->c, &xformP->d);
-    swap(&xformP->e, &xformP->f);
-}
-
-
-
-static void
-computeXformMatrix(struct xformMatrix * const xformP, 
-                   unsigned int         const xformCount,
-                   enum xformType             xformType[]) {
-
-    struct xformMatrix const nullTransform = {1, 0, 0, 1, 0, 0};
-
-    unsigned int i;
-
-    *xformP = nullTransform;   /* initial value */
-
-    for (i = 0; i < xformCount; ++i) {
-        switch (xformType[i]) {
-        case LEFTRIGHT: 
-            leftright(xformP);
-            break;
-        case TOPBOTTOM:
-            topbottom(xformP);
-            break;
-        case TRANSPOSE:
-            transpose(xformP);
-            break;
-        }
-    }
-}
-
-
-
 static void
 bitOrderReverse(unsigned char * const bitrow, 
                 unsigned int    const cols) {
@@ -400,8 +444,9 @@ transformRowByRowPbm(struct pam * const inpamP,
                      struct pam * const outpamP,
                      bool         const reverse) {
 /*----------------------------------------------------------------------------
-  Transform a PBM image either by flipping it left for right, or just leaving
-  it alone, as indicated by 'reverse'.
+  Transform a PBM raster either by flipping it left for right, or just
+  leaving it alone, as indicated by 'reverse'.  Read the raster from
+  *inpamP; write the transformed raster to *outpamP.
 
   Process the image one row at a time and use fast packed PBM bit
   reverse algorithm (where required).
@@ -411,8 +456,6 @@ transformRowByRowPbm(struct pam * const inpamP,
 
     bitrow = pbm_allocrow_packed(outpamP->width); 
 
-    pbm_writepbminit(outpamP->file, outpamP->width, outpamP->height, 0);
-
     for (row = 0; row < inpamP->height; ++row) {
         pbm_readpbmrow_packed(inpamP->file,  bitrow, inpamP->width,
                               inpamP->format);
@@ -432,7 +475,8 @@ transformRowByRowNonPbm(struct pam * const inpamP,
                         struct pam * const outpamP,
                         bool         const reverse) {
 /*----------------------------------------------------------------------------
-  Flip an image left for right or leave it alone.
+  Flip a raster left for right or leave it alone.  Read the raster
+  from *inpamP; write the transformed raster to *outpamP.
 
   Process one row at a time.
 
@@ -466,8 +510,6 @@ transformRowByRowNonPbm(struct pam * const inpamP,
         scratchTuplerow = NULL;
         newtuplerow = tuplerow;
     }
-    pnm_writepaminit(outpamP);
-
     for (row = 0; row < inpamP->height ; ++row) {
         pnm_readpamrow(inpamP, tuplerow);
         pnm_writepamrow(outpamP, newtuplerow);
@@ -481,32 +523,12 @@ transformRowByRowNonPbm(struct pam * const inpamP,
 
 
 static void
-transformRowByRow(struct pam * const inpamP,
-                  struct pam * const outpamP,
-                  bool         const reverse,
-                  bool         const verbose) {
-
-    if (verbose)
-        pm_message("Transforming row by row, top to bottom");
-
-    switch (PNM_FORMAT_TYPE(inpamP->format)) {
-    case PBM_TYPE:
-        transformRowByRowPbm(inpamP, outpamP, reverse);
-        break;
-    default:
-        transformRowByRowNonPbm(inpamP, outpamP, reverse);
-        break;
-    }
-} 
-
-
-
-static void
 transformRowsBottomTopPbm(struct pam * const inpamP,
                           struct pam * const outpamP,
                           bool         const reverse) { 
 /*----------------------------------------------------------------------------
-  Flip a PBM image top for bottom.  Iff 'reverse', also flip it left for right.
+  Flip a PBM raster top for bottom; read the raster from *inpamP;
+  write the flipped raster to *outpamP.
 
   Read complete image into memory in packed PBM format; Use fast
   packed PBM bit reverse algorithm (where required).
@@ -514,7 +536,7 @@ transformRowsBottomTopPbm(struct pam * const inpamP,
     unsigned int const rows=inpamP->height;
 
     unsigned char ** bitrow;
-    int row;
+    unsigned int row;
         
     bitrow = pbm_allocarray_packed(outpamP->width, outpamP->height);
         
@@ -522,8 +544,6 @@ transformRowsBottomTopPbm(struct pam * const inpamP,
         pbm_readpbmrow_packed(inpamP->file, bitrow[row], 
                               inpamP->width, inpamP->format);
 
-    pbm_writepbminit(outpamP->file, outpamP->width, outpamP->height, 0);
-
     for (row = 0; row < rows; ++row) {
         if (reverse) 
             bitOrderReverse(bitrow[rows-row-1], inpamP->width);
@@ -538,82 +558,43 @@ transformRowsBottomTopPbm(struct pam * const inpamP,
 
 static void
 transformRowsBottomTopNonPbm(struct pam * const inpamP, 
-                             struct pam * const outpamP,
-                             bool         const reverse) {
+                             struct pam * const outpamP) {
 /*----------------------------------------------------------------------------
-  Read complete image into memory as a tuple array.
+  Do a simple vertical flip.  Read the raster from *inpamP; write the
+  flipped raster to *outpamP.
+
+  We do this faster than the more general subroutines because we just
+  move the row pointers.
+
+  But we must fit the entire image into memory at once.
 
-  This can do any transformation except a column-for-row transformation,
-  on any type of image, but is slower and uses more memory than the 
-  PBM-only transformRowsBottomTopPbm().
+  This works on PBM, but the PBM-specific subroutine is faster.
 -----------------------------------------------------------------------------*/
-    tuple** tuplerows;
-    tuple * scratchTuplerow;
-        /* This is not a full tuple row -- just an array of pointers to
-           the tuples in 'tuplerows'.
-        */
+    tuple ** tuplerows;
     unsigned int row;
 
-    if (reverse)
-        MALLOCARRAY_NOFAIL(scratchTuplerow, inpamP->width);
-    else
-        scratchTuplerow = NULL;
-  
     tuplerows = pnm_allocpamarray(outpamP);
 
     for (row = 0; row < inpamP->height ; ++row)
         pnm_readpamrow(inpamP, tuplerows[row]);
 
-    pnm_writepaminit(outpamP);
-
-    for (row = 0; row < inpamP->height ; ++row) {
-        tuple * newtuplerow;
+    for (row = 0; row < inpamP->height; ++row) {
         tuple * const tuplerow = tuplerows[inpamP->height - row - 1];
-        if (reverse) {
-            unsigned int col;
-            newtuplerow = scratchTuplerow;
-            for (col = 0; col < inpamP->width; ++col) 
-                newtuplerow[col] = tuplerow[inpamP->width - col - 1];
-        } else
-            newtuplerow = tuplerow;
-        pnm_writepamrow(outpamP, newtuplerow);
-    }
 
-    if (scratchTuplerow)
-        free(scratchTuplerow);
+        pnm_writepamrow(outpamP, tuplerow);
+    }
     
     pnm_freepamarray(tuplerows, outpamP);
 }
 
 
 
-static void
-transformRowsBottomTop(struct pam * const inpamP,
-                       struct pam * const outpamP,
-                       bool         const reverse,
-                       bool         const verbose) {
-
-    if (PNM_FORMAT_TYPE(inpamP->format) == PBM_TYPE) {
-        if (verbose)
-            pm_message("Transforming PBM row by row, bottom to top");
-        transformRowsBottomTopPbm(inpamP, outpamP, reverse);
-    } else {
-        if (verbose)
-            pm_message("Transforming non-PBM row by row, bottom to top");
-        transformRowsBottomTopNonPbm(inpamP, outpamP, reverse);
-    }
-}
-
-
-
 static void __inline__
 transformPoint(int                const col, 
-               int                const newcols,
                int                const row, 
-               int                const newrows, 
                struct xformMatrix const xform, 
-               int *              const newcolP, 
-               int *              const newrowP ) {
+               unsigned int *     const newcolP, 
+               unsigned int *     const newrowP ) {
 /*----------------------------------------------------------------------------
    Compute the location in the output of a pixel that is at row 'row',
    column 'col' in the input.  Assume the output image is 'newcols' by
@@ -626,17 +607,39 @@ transformPoint(int                const col,
                  [ a b 0 ]
        [ x y 1 ] [ c d 0 ] = [ x2 y2 1 ]
                  [ e f 1 ]
+
+       Where (x, y) is the source pixel location and (x2, y2) is the
+       target pixel location.
+
+       Note that this is more of a logical computation than an arithmetic
+       one: a, b, c, and d are -1, 0, or 1.  e is the maximum column number
+       in the target image or 0; f is the maximum row number or 0.
     */
-    *newcolP = xform.a * col + xform.c * row + xform.e * (newcols - 1);
-    *newrowP = xform.b * col + xform.d * row + xform.f * (newrows - 1);
+    *newcolP = xform.a * col + xform.c * row + xform.e * 1;
+    *newrowP = xform.b * col + xform.d * row + xform.f * 1;
+
+    assert(*newcolP >= 0);
+    assert(*newrowP >= 0);
+}
+
+
+
+static void
+writeRaster(struct pam *    const pamP,
+            tuple * const * const tuples) {
+
+    unsigned int outRow;
+
+    for (outRow = 0; outRow < pamP->height; ++ outRow)
+        pnm_writepamrow(pamP, tuples[outRow]);
 }
 
 
 
 static void
-transformPbm(struct pam *       const inpamP,
-             struct pam *       const outpamP,
-             struct xformMatrix const xform) { 
+transformPbmGen(struct pam *     const inpamP,
+                struct pam *     const outpamP,
+                struct xformCore const xformCore) { 
 /*----------------------------------------------------------------------------
    This is the same as transformGen, except that it uses less 
    memory, since the PBM buffer format uses one bit per pixel instead
@@ -646,37 +649,37 @@ transformPbm(struct pam *       const inpamP,
    memory than the more restricted transformRowByRowPbm() and
    transformRowsBottomTopPbm().
 -----------------------------------------------------------------------------*/
-    bit* bitrow;
-    bit** newbits;
-    int row;
+    bit * bitrow;
+    bit ** newbits;
+    struct xformMatrix xform;
+    unsigned int row;
             
+    computeXformMatrix(&xform, inpamP->width, inpamP->height, xformCore);
+    
     bitrow = pbm_allocrow(inpamP->width);
     newbits = pbm_allocarray(pbm_packed_bytes(outpamP->width), 
                              outpamP->height);
             
     /* Initialize entire array to zeroes.  One bits will be or'ed in later */
     for (row = 0; row < outpamP->height; ++row) {
-        int col;
+        unsigned int col;
         for (col = 0; col < pbm_packed_bytes(outpamP->width); ++col) 
              newbits[row][col] = 0; 
     }
     
     for (row = 0; row < inpamP->height; ++row) {
-        int col;
+        unsigned int col;
         pbm_readpbmrow(inpamP->file, bitrow, inpamP->width, inpamP->format);
         for (col = 0; col < inpamP->width; ++col) {
-            int newcol, newrow;
-            transformPoint(col, outpamP->width, row, outpamP->height, xform,
-                           &newcol, &newrow);
+            unsigned int newcol, newrow;
+            transformPoint(col, row, xform, &newcol, &newrow);
             newbits[newrow][newcol/8] |= bitrow[col] << (7 - newcol % 8);
                 /* Use of "|=" patterned after pbm_readpbmrow_packed. */
          }
     }
     
-    pbm_writepbminit(outpamP->file, outpamP->width, outpamP->height, 0);
     for (row = 0; row < outpamP->height; ++row)
-        pbm_writepbmrow_packed(outpamP->file, newbits[row], outpamP->width, 
-                               0);
+        pbm_writepbmrow_packed(outpamP->file, newbits[row], outpamP->width, 0);
     
     pbm_freearray(newbits, outpamP->height);
     pbm_freerow(bitrow);
@@ -684,131 +687,53 @@ transformPbm(struct pam *       const inpamP,
 
 
 
-static unsigned int 
-optimalSegmentSize(struct xformMatrix  const xform, 
-                   struct pam *        const pamP,
-                   unsigned int        const availableMemory,
-                   unsigned int        const pageSize) {
-/*----------------------------------------------------------------------------
-   Compute the maximum number of columns that can be transformed, one row
-   at a time, without causing page thrashing.
-
-   See comments at the top of this file for an explanation of the kind
-   of page thrashing using segments avoids.
-
-   'availableMemory' is the amount of real memory in bytes that this
-   process should expect to be able to use.
-
-   'pageSize' is the size of a page in bytes.  A page means the unit that
-   is paged in or out.
-   
-   'pamP' describes the storage required to represent a row of the
-   output array.
------------------------------------------------------------------------------*/
-    unsigned int segmentSize;
-
-    if (xform.b == 0)
-        segmentSize = pamP->width;
-    else {
-        unsigned int const otherNeeds = 200*1024;
-            /* A wild guess at how much real memory is needed by the program
-               for purposes other than the output tuple array.
-            */
-        if (otherNeeds > availableMemory)
-            segmentSize = pamP->width;  /* Can't prevent thrashing */
-        else {
-            unsigned int const availablePages = 
-                (availableMemory - otherNeeds) / pageSize;
-            if (availablePages <= 1)
-                segmentSize = pamP->width; /* Can't prevent thrashing */
-            else {
-                unsigned int const bytesPerRow = 
-                    pamP->width * pamP->depth * pamP->bytes_per_sample;
-                unsigned int rowsPerPage = 
-                    MAX(1, (pageSize + (pageSize/2)) / bytesPerRow);
-                    /* This is how many consecutive rows we can touch
-                       on average while staying within the same page.  
-                    */
-                segmentSize = availablePages * rowsPerPage;
-            }
-        }
-    }    
-    return segmentSize;
-}
-
-
-
 static void
-transformNonPbm(struct pam *       const inpamP,
-                struct pam *       const outpamP,
-                struct xformMatrix const xform,
-                unsigned int       const segmentSize,
-                bool               const verbose) {
+transformNonPbmWhole(struct pam *     const inpamP,
+                     struct pam *     const outpamP,
+                     struct xformCore const xformCore,
+                     bool             const verbose) {
 /*----------------------------------------------------------------------------
   Do the transform using "pam" library functions, as opposed to "pbm"
   ones.
 
+  Read the raster from *inpamP; write the transformed raster to *outpamP.
+
   Assume input file is positioned to the raster (just after the
   header).
 
-  'segmentSize' is the number of columns we are to process in each
-  pass.  We do each segment going from left to right.  For each
-  segment, we do each row, going from top to bottom.  For each row of
-  the segment, we do each column, going from left to right.  (The
-  reason Caller wants it done by segments is to improve virtual memory
-  reference locality.  See comments at the top of this file).
-
-  if 'segmentSize' is less than the whole image, ifP must be a seekable
-  file.
-
   This can do any transformation, but is slower and uses more memory
-  than the PBM-only transformPbm().
+  than some of the alternatives which are usable only for certain
+  cases.  But we do require a certain amount of virtual and real memory;
+  transformNonPbmChunk() is even more general in that it doesn't.
 -----------------------------------------------------------------------------*/
-    pm_filepos imagepos;
-        /* The input file position of the raster.  But defined only if
-           segment size is less than whole image.
-        */
-    tuple* tuplerow;
-    tuple** newtuples;
-    unsigned int startCol;
+    tuple * tuplerow;
+    tuple ** newtuples;
+    struct xformMatrix xform;
+    unsigned int row;
 
+    computeXformMatrix(&xform, inpamP->width, inpamP->height, xformCore);
+    
     tuplerow = pnm_allocpamrow(inpamP);
     newtuples = pnm_allocpamarray(outpamP);
     
-    if (segmentSize < inpamP->width)
-        pm_tell2(inpamP->file, &imagepos, sizeof(imagepos));
+    for (row = 0; row < inpamP->height; ++row) {
+        unsigned int col;
+        pnm_readpamrow(inpamP, tuplerow);
+        
+        for (col = 0; col < inpamP->width; ++col) {
+            unsigned int newcol, newrow;
 
-    for (startCol = 0; startCol < inpamP->width; startCol += segmentSize) {
-        /* Do one set of columns which is small enough not to cause
-           page thrashing.
-        */
-        unsigned int const endCol = MIN(inpamP->width, startCol + segmentSize);
-        unsigned int row;
+            transformPoint(col, row, xform, &newcol, &newrow);
 
-        if (verbose)
-            pm_message("Transforming Columns %u up to %u", 
-                       startCol, endCol);
-
-        if (startCol > 0)
-            /* Go back to read from Row 0 again */
-            pm_seek2(inpamP->file, &imagepos, sizeof(imagepos));
-
-        for (row = 0; row < inpamP->height; ++row) {
-            unsigned int col;
-            pnm_readpamrow(inpamP, tuplerow);
-
-            for (col = startCol; col < endCol; ++col) {
-                int newcol, newrow;
-                transformPoint(col, outpamP->width, row, outpamP->height, 
-                               xform,
-                               &newcol, &newrow);
-                pnm_assigntuple(inpamP, newtuples[newrow][newcol],
-                                tuplerow[col]);
-            }
+            assert(newcol < outpamP->width);
+            assert(newrow < outpamP->height);
+
+            pnm_assigntuple(inpamP, newtuples[newrow][newcol],
+                            tuplerow[col]);
         }
     }
     
-    pnm_writepam(outpamP, newtuples);
+    writeRaster(outpamP, newtuples);
     
     pnm_freepamarray(newtuples, outpamP);
     pnm_freepamrow(tuplerow);
@@ -816,40 +741,396 @@ transformNonPbm(struct pam *       const inpamP,
 
 
 
+typedef struct {
+/*----------------------------------------------------------------------------
+   A description of the quilt of cells that make up the output image.
+-----------------------------------------------------------------------------*/
+    unsigned int ranks, files;
+        /* Dimensions of the output image in cells */
+    struct pam ** pam;
+        /* pam[y][x] is the pam structure for the cell at rank y, file x
+           in the output.
+        */
+
+    /* Each of the cells corresponds to a temporary file; the 'file'
+       member of its pam structure identifies it.  But it is not a normal
+       Netpbm file; it contains only the raster portion.  The program
+       writes the raster to the file, starting at offset zero, then rewinds
+       and reads it out later.  The header is unnecessary because the pam
+       structure is still available at readback time.
+    */
+} outputMap;
+
+
+
 static void
-transformGen(struct pam *       const inpamP,
-             struct pam *       const outpamP,
-             struct xformMatrix const xform,
-             unsigned int       const availableMemory,
-             unsigned int       const pageSize,
-             bool               const verbose) {
+initOutCell(struct pam *     const outCellPamP,
+            unsigned int     const inCellWidth,
+            unsigned int     const inCellHeight,
+            struct pam *     const inpamP,
+            struct xformCore const xformCore) {
+
+    unsigned int outCellFiles, outCellRanks;
+
+    *outCellPamP = *inpamP;  /* initial value */
+
+    outCellPamP->len  = PAM_STRUCT_SIZE(tuple_type);
+
+    outCellPamP->file = pm_tmpfile();
+
+    xformDimensions(xformCore, inCellWidth, inCellHeight,
+                    &outCellFiles, &outCellRanks);
+
+    outCellPamP->width = outCellFiles;
+    outCellPamP->height = outCellRanks;
+}
+
+
+
+static outputMap *
+createOutputMap(struct pam *       const inpamP,
+                unsigned int       const maxRows,
+                struct xformMatrix const cellXform,
+                struct xformCore   const xformCore) {
+
+    unsigned int const inCellFiles = 1;
+    unsigned int const inCellRanks = (inpamP->height + maxRows - 1) / maxRows;
+
+    outputMap * mapP;
+    unsigned int outCellRank;
+    unsigned int inCellRank;
+
+    MALLOCVAR_NOFAIL(mapP);
+
+    xformDimensions(xformCore, inCellFiles, inCellRanks,
+                    &mapP->files, &mapP->ranks);
+
+    MALLOCARRAY(mapP->pam, mapP->ranks);
+    if (mapP->pam == NULL)
+        pm_error("Could not allocate a cell array for %u ranks of cells.",
+                 mapP->ranks);
+
+    for (outCellRank = 0; outCellRank < mapP->ranks; ++outCellRank) {
+
+        MALLOCARRAY(mapP->pam[outCellRank], mapP->files);
+
+        if (mapP->pam[outCellRank] == NULL)
+            pm_error("Failed to allocate rank %u of the cell array, "
+                     "%u cells wide", outCellRank, mapP->files);
+    }
+
+    for (inCellRank = 0; inCellRank < inCellRanks; ++inCellRank) {
+        unsigned int const inCellFile = 0;
+        unsigned int const inCellStartRow = inCellRank * maxRows;
+        unsigned int const inCellRows =
+            MIN(inpamP->height - inCellStartRow, maxRows);
+
+        unsigned int outCellFile, outCellRank;
+        transformPoint(inCellFile, inCellRank, cellXform,
+                       &outCellFile, &outCellRank);
+    
+        initOutCell(&mapP->pam[outCellRank][outCellFile],
+                    inpamP->width, inCellRows,
+                    inpamP, xformCore);
+    }
+    return mapP;
+}
+                
+
+
+static void
+destroyOutputMap(outputMap * const mapP) {
+
+    unsigned int outCellRank;
+
+    for (outCellRank = 0; outCellRank < mapP->ranks; ++outCellRank)
+        free(mapP->pam[outCellRank]);
+
+    free(mapP->pam);
+
+    free(mapP);
+}
+
+
+
+static void
+rewindCellFiles(outputMap * const outputMapP) {
+
+    unsigned int outCellRank;
+
+    for (outCellRank = 0; outCellRank < outputMapP->ranks; ++outCellRank) {
+        unsigned int outCellFile;
+        for (outCellFile = 0; outCellFile < outputMapP->files; ++outCellFile)
+            pm_seek(outputMapP->pam[outCellRank][outCellFile].file, 0);
+    }
+}
+
+
+
+static void
+closeCellFiles(outputMap * const outputMapP) {
+
+    unsigned int outCellRank;
+
+    for (outCellRank = 0; outCellRank < outputMapP->ranks; ++outCellRank) {
+        unsigned int outCellFile;
+        for (outCellFile = 0; outCellFile < outputMapP->files; ++outCellFile)
+            pm_close(outputMapP->pam[outCellRank][outCellFile].file);
+    }
+}
+
+
+
+static void
+transformCell(struct pam *     const inpamP,
+              struct pam *     const outpamP,
+              struct xformCore const xformCore) {
+
+    struct xformMatrix xform;
+    tuple * inTupleRow;
+    tuple ** outTuples;
+    unsigned int inRow;
+
+    computeXformMatrix(&xform, inpamP->width, inpamP->height, xformCore);
+
+    inTupleRow = pnm_allocpamrow(inpamP);
+
+    outTuples = pnm_allocpamarray(outpamP);
+
+    for (inRow = 0; inRow < inpamP->height; ++inRow) {
+        unsigned int inCol;
+
+        pnm_readpamrow(inpamP, inTupleRow);
+        
+        for (inCol = 0; inCol < inpamP->width; ++inCol) {
+            unsigned int outCol, outRow;
+
+            transformPoint(inCol, inRow, xform, &outCol, &outRow);
+
+            assert(outCol < outpamP->width);
+            assert(outRow < outpamP->height);
+
+            pnm_assigntuple(inpamP,
+                            outTuples[outRow][outCol], inTupleRow[inCol]);
+        }
+    }
+    pnm_freepamrow(inTupleRow);
+
+    writeRaster(outpamP, outTuples);
+
+    pnm_freepamarray(outTuples, outpamP);
+}
+
+
+
+static void
+stitchCellsToOutput(outputMap *  const outputMapP,
+                    struct pam * const outpamP) {
+
+    unsigned int outRank;
+    tuple * tupleRow;
+
+    tupleRow = pnm_allocpamrow(outpamP);
+
+    for (outRank = 0; outRank < outputMapP->ranks; ++outRank) {
+        unsigned int const cellRows = outputMapP->pam[outRank][0].height;
+            /* Number of rows in every cell in this rank */
+
+        unsigned int cellRow;
+
+        for (cellRow = 0; cellRow < cellRows; ++cellRow) {
+            unsigned int outFile;
+            unsigned int outCol;
+
+            outCol = 0;
+
+            for (outFile = 0; outFile < outputMapP->files; ++outFile) {
+                struct pam * const outCellPamP = 
+                    &outputMapP->pam[outRank][outFile];
+
+                assert(outCellPamP->height == cellRows);
+
+                assert(outCol < outpamP->width);
+                pnm_readpamrow(outCellPamP, &tupleRow[outCol]);
+
+                outCol += outCellPamP->width;
+            }
+
+            assert(outCol = outpamP->width);
+
+            pnm_writepamrow(outpamP, tupleRow);
+        }
+    }
+    pnm_freepamrow(tupleRow);
+}
+
+
+
+static void
+transformNonPbmChunk(struct pam *     const inpamP,
+                     struct pam *     const outpamP,
+                     struct xformCore const xformCore,
+                     unsigned int     const maxRows,
+                     bool             const verbose) {
 /*----------------------------------------------------------------------------
-  Produce the transformed output on Standard Output.
+  Same as transformNonPbmChunk(), except we read 'maxRows' rows of the
+  input into memory at a time, storing intermediate results in temporary
+  files, to limit our use of virtual and real memory.
 
   Assume input file is positioned to the raster (just after the
   header).
 
-  This can transform any image in any way, but is slower and uses more
-  memory than the more restricted transformRowByRow() and
-  transformRowsBottomTop().
+  We call the strip of 'maxRows' rows that we read a source cell.  We
+  transform that cell according to 'xformCore' to create to create a
+  target cell.  We store all the target cells in temporary files.
+  We consider the target cells to be arranged in a column matrix the
+  same as the source cells within the source image; we transform that
+  matrix according to 'xformCore'.  The resulting cell matrix is the
+  target image.
 -----------------------------------------------------------------------------*/
-    unsigned int const segmentSize = 
-        optimalSegmentSize(xform, outpamP, availableMemory, pageSize);
+    unsigned int const inCellFiles = 1;
+    unsigned int const inCellRanks = (inpamP->height + maxRows - 1) / maxRows;
+
+    struct xformMatrix cellXform;
+    unsigned int inCellRank;
+    outputMap * outputMapP;
+
+    if (verbose)
+        pm_message("Transforming in %u chunks, using temp files", inCellRanks);
+
+    computeXformMatrix(&cellXform, inCellFiles, inCellRanks, xformCore);
+
+    outputMapP = createOutputMap(inpamP, maxRows, cellXform, xformCore);
+
+    for (inCellRank = 0; inCellRank < inCellRanks; ++inCellRank) {
+        unsigned int const inCellFile = 0;
+        unsigned int const inCellStartRow = inCellRank * maxRows;
+        unsigned int const inCellRows =
+            MIN(inpamP->height - inCellStartRow, maxRows);
+
+        struct pam inCellPam;
+        struct pam * outCellPamP;
+        unsigned int outCellFile, outCellRank;
+
+        transformPoint(inCellFile, inCellRank, cellXform,
+                       &outCellFile, &outCellRank);
     
-    switch (PNM_FORMAT_TYPE(inpamP->format)) {
-    case PBM_TYPE: 
-        transformPbm(inpamP, outpamP, xform);
-        break;
-    default:
-        if (segmentSize < outpamP->width) {
-            if (verbose && xform.b !=0)
-                pm_message("Transforming %u columns of %u total at a time", 
-                           segmentSize, outpamP->width);
-            else
-                pm_message("Transforming entire image at once");
-        }
-        transformNonPbm(inpamP, outpamP, xform, segmentSize, verbose);
-        break;
+        outCellPamP = &outputMapP->pam[outCellRank][outCellFile];
+
+        /* Input cell is just the next 'inCellRows' rows of input image */
+        inCellPam = *inpamP;
+        inCellPam.height = inCellRows;
+
+        transformCell(&inCellPam, outCellPamP, xformCore);
+    }    
+
+    rewindCellFiles(outputMapP);
+
+    stitchCellsToOutput(outputMapP, outpamP);
+
+    closeCellFiles(outputMapP);
+
+    destroyOutputMap(outputMapP);
+}
+
+
+
+static unsigned int
+maxRowsThatFit(struct pam * const pamP,
+               size_t       const availableMemory) {
+
+    unsigned long const otherNeeds = 100*1024;
+        /* A wild guess at how much memory (from the same pool as the
+           input rows) is needed for things other than the input rows
+        */
+    unsigned long const availForRows =
+        availableMemory > otherNeeds ? availableMemory - otherNeeds : 0;
+    unsigned int const bytesPerTuple =
+        pamP->depth * sizeof(sample) + sizeof(tuple *);
+    unsigned int const bytesPerRow =
+        pamP->width * bytesPerTuple + sizeof(tuple **);
+
+    unsigned long const maxRows = availForRows / bytesPerRow;
+
+    if (maxRows < 1)
+        pm_error("You haven't allowed enough memory to fit even one row "
+                 "of the source image in memory.  The minimum chunk size "
+                 "is one row; we need at least %lu bytes.",
+                 otherNeeds + bytesPerRow);
+
+    return (unsigned int)MIN(maxRows, UINT_MAX);
+}
+
+
+
+static void
+transformPbm(struct pam *     const inpamP,
+             struct pam *     const outpamP,
+             struct xformCore const xform,
+             bool             const verbose) {
+
+    if (xform.b == 0 && xform.c == 0) {
+        /* Rows of input map to rows of target; no column-for-row */
+        if (xform.d == 1)
+            /* Row N of the output is based only on Row N of the
+               input, so we can transform row by row and avoid
+               in-memory buffering altogether.
+            */
+            transformRowByRowPbm(inpamP, outpamP, xform.a == -1);
+        else
+            /* Row N of the output is based only on Row ~N of the
+               input.  We need all the rows in memory, but have to pass
+               through them only twice, so there is no page thrashing concern.
+            */
+            transformRowsBottomTopPbm(inpamP, outpamP, xform.a == -1);
+    } else
+        /* This is a column-for-row type of transformation, which requires
+           complex traversal of an in-memory image.
+        */
+        transformPbmGen(inpamP, outpamP, xform);
+}
+
+
+
+static void
+transformNonPbm(struct pam *     const inpamP,
+                struct pam *     const outpamP,
+                struct xformCore const xform,
+                unsigned int     const availableMemory,
+                bool             const verbose) {
+
+    if (xform.b == 0 && xform.c == 0 && xform.d == 1) {
+        /* Row N of the output is based only on Row N of the
+           input, so we can transform row by row and avoid
+           in-memory buffering altogether.
+        */
+        if (verbose)
+            pm_message("Transforming row by row");
+        transformRowByRowNonPbm(inpamP, outpamP, xform.a == -1);
+    } else {
+        unsigned int const maxRows = maxRowsThatFit(inpamP, availableMemory);
+        if (maxRows >= inpamP->height) {
+            /* We can fit the whole image in memory at once and avoid
+               temporary files.
+            */
+            if (xform.b == 0 && xform.c == 0 && xform.d == -1 &&
+                xform.a == 1) {
+                /* This is just a vertical flip;  We can move whole rows
+                   instead of individual pixels and save time.
+                */
+                if (verbose)
+                    pm_message("Transforming whole rows, all in memory");
+
+                transformRowsBottomTopNonPbm(inpamP, outpamP);
+            } else {
+                if (verbose)
+                    pm_message("Transforming whole image at once, "
+                               "pixel by pixel");
+                transformNonPbmWhole(inpamP, outpamP, xform, verbose);
+            }
+        } else
+            /* No optimizations possible */
+            transformNonPbmChunk(inpamP, outpamP, xform, maxRows, verbose);
     }
 }
 
@@ -860,8 +1141,8 @@ main(int argc, char * argv[]) {
     struct cmdlineInfo cmdline;
     struct pam inpam;
     struct pam outpam;
-    FILE* ifP;
-    struct xformMatrix xform;
+    unsigned int cols, rows;
+    FILE * ifP;
 
     pnm_init(&argc, argv);
 
@@ -874,35 +1155,22 @@ main(int argc, char * argv[]) {
     
     pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
 
-    computeXformMatrix(&xform, cmdline.xformCount, cmdline.xformList);
-
     outpam = inpam;  /* initial value */
 
     outpam.file = stdout;
-    outpam.width  = abs(xform.a) * inpam.width + abs(xform.c) * inpam.height;
-    outpam.height = abs(xform.b) * inpam.width + abs(xform.d) * inpam.height;
-    
-    if (xform.b == 0 && xform.d == 1 && xform.f == 0)
-        /* In this case Row N of the output is based only on Row N of
-           the input, so we can transform row by row and avoid
-           in-memory buffering altogether.  
-        */
-        transformRowByRow(&inpam, &outpam, xform.a == -1, cmdline.verbose);
-    else if (xform.b == 0 && xform.c == 0) 
-        /* In this case, Row N of the output is based only on Row ~N of the
-           input.  We need all the rows in memory, but have to pass
-           through them only twice, so there is no page thrashing concern.
-        */
-        transformRowsBottomTop(&inpam, &outpam, xform.a == -1, 
-                               cmdline.verbose);
-    else
-        /* This is a colum-for-row type of transformation, which requires
-           complex traversal of an in-memory image.
-        */
-        transformGen(&inpam, &outpam, xform,
-                     cmdline.availableMemory, cmdline.pageSize, 
-                     cmdline.verbose);
+    xformDimensions(cmdline.xform, inpam.width, inpam.height, &cols, &rows);
+    outpam.width = cols; outpam.height = rows;
+
+    pnm_writepaminit(&outpam);
 
+    switch (PNM_FORMAT_TYPE(inpam.format)) {
+    case PBM_TYPE:
+        transformPbm(&inpam, &outpam, cmdline.xform, cmdline.verbose);
+        break;
+    default:
+        transformNonPbm(&inpam, &outpam, cmdline.xform,
+                        cmdline.availableMemory, cmdline.verbose);
+    }
     pm_close(inpam.file);
     pm_close(outpam.file);
     
diff --git a/editor/pamfunc.c b/editor/pamfunc.c
index dbb1ca70..b6e56e17 100644
--- a/editor/pamfunc.c
+++ b/editor/pamfunc.c
@@ -18,10 +18,25 @@
 
 ******************************************************************************/
 
-#include "pam.h"
+#include "pm_c_util.h"
+#include "mallocvar.h"
 #include "shhopt.h"
+#include "pam.h"
 
-enum function {FN_MULTIPLY, FN_DIVIDE, FN_ADD, FN_SUBTRACT, FN_MIN, FN_MAX};
+enum function {
+    FN_MULTIPLY,
+    FN_DIVIDE,
+    FN_ADD,
+    FN_SUBTRACT,
+    FN_MIN,
+    FN_MAX,
+    FN_AND,
+    FN_OR,
+    FN_XOR,
+    FN_NOT,
+    FN_SHIFTLEFT,
+    FN_SHIFTRIGHT
+};
 
 /* Note that when the user specifies a minimum, that means he's requesting
    a "max" function.
@@ -40,11 +55,30 @@ struct cmdlineInfo {
         int subtractor;
         unsigned int max;
         unsigned int min;
+        unsigned int mask;
+        unsigned int shiftCount;
     } u;
     unsigned int verbose;
 };
 
 
+
+static unsigned int
+parseHex(const char * const hexString) {
+
+    unsigned int retval;
+    char * tail;
+
+    retval = strtol(hexString, &tail, 16);
+
+    if (*tail != '\0')
+        pm_error("Invalid hex string '%s'.  Junk: '%s'", hexString, tail);
+
+    return retval;
+}
+
+         
+
 static void
 parseCommandLine(int argc, char ** const argv,
                  struct cmdlineInfo * const cmdlineP) {
@@ -52,29 +86,46 @@ parseCommandLine(int argc, char ** const argv,
    Note that the file spec array we return is stored in the storage that
    was passed to us as the argv array.
 -----------------------------------------------------------------------------*/
-    optEntry *option_def = malloc(100*sizeof(optEntry));
-        /* Instructions to OptParseOptions2 on how to parse our options.
-         */
+    optEntry * option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options. */
     optStruct3 opt;
 
     unsigned int option_def_index;
 
     unsigned int multiplierSpec, divisorSpec, adderSpec, subtractorSpec;
     unsigned int maxSpec, minSpec;
+    unsigned int andmaskSpec, ormaskSpec, xormaskSpec, notSpec;
+    unsigned int shiftleftSpec, shiftrightSpec;
+
+    const char * mask;
+
+    MALLOCARRAY(option_def, 100);
 
-    option_def_index = 0;   /* incremented by OPTENTRY */
+    option_def_index = 0;   /* incremented by OPTENT3 */
     OPTENT3(0,   "multiplier", OPT_FLOAT,  &cmdlineP->u.multiplier, 
             &multiplierSpec, 0);
     OPTENT3(0,   "divisor",    OPT_FLOAT,  &cmdlineP->u.divisor,
-            &divisorSpec, 0);
+            &divisorSpec,    0);
     OPTENT3(0,   "adder",      OPT_INT,    &cmdlineP->u.adder,
-            &adderSpec, 0);
+            &adderSpec,      0);
     OPTENT3(0,   "subtractor", OPT_INT,    &cmdlineP->u.subtractor,
             &subtractorSpec, 0);
     OPTENT3(0,   "min",        OPT_UINT,   &cmdlineP->u.min,
-            &minSpec, 0);
+            &minSpec,        0);
     OPTENT3(0,   "max",        OPT_UINT,   &cmdlineP->u.max,
-            &maxSpec, 0);
+            &maxSpec,        0);
+    OPTENT3(0,   "andmask",    OPT_STRING, &mask,
+            &andmaskSpec,    0);
+    OPTENT3(0,   "ormask",     OPT_STRING, &mask,
+            &ormaskSpec,     0);
+    OPTENT3(0,   "xormask",    OPT_STRING, &mask,
+            &xormaskSpec,    0);
+    OPTENT3(0,   "not",        OPT_FLAG,   NULL,
+            &notSpec,        0);
+    OPTENT3(0,   "shiftleft",  OPT_UINT,   &cmdlineP->u.shiftCount,
+            &shiftleftSpec,  0);
+    OPTENT3(0,   "shiftright", OPT_UINT,   &cmdlineP->u.shiftCount,
+            &shiftrightSpec, 0);
     OPTENT3(0,   "verbose",    OPT_FLAG,   NULL, &cmdlineP->verbose,       0);
 
     opt.opt_table = option_def;
@@ -85,9 +136,12 @@ parseCommandLine(int argc, char ** const argv,
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
     if (multiplierSpec + divisorSpec + adderSpec + subtractorSpec +
-        minSpec + maxSpec > 1)
+        minSpec + maxSpec + andmaskSpec + ormaskSpec + xormaskSpec + notSpec +
+        shiftleftSpec + shiftrightSpec > 1)
         pm_error("You may specify at most one of -multiplier, -divisor,"
-                 "-adder, -subtractor, -min, and -max");
+                 "-adder, -subtractor, -min, -max, "
+                 "-andmask, -ormask, -xormask, -not, "
+                 "-shiftleft, and -shiftright");
 
     if (multiplierSpec) {
         cmdlineP->function = FN_MULTIPLY;
@@ -107,9 +161,25 @@ parseCommandLine(int argc, char ** const argv,
         cmdlineP->function = FN_MAX;
     } else if (maxSpec) {
         cmdlineP->function = FN_MIN;
+    } else if (andmaskSpec) {
+        cmdlineP->function = FN_AND;
+        cmdlineP->u.mask = parseHex(mask);
+    } else if (ormaskSpec) {
+        cmdlineP->function = FN_OR;
+        cmdlineP->u.mask = parseHex(mask);
+    } else if (xormaskSpec) {
+        cmdlineP->function = FN_XOR;
+        cmdlineP->u.mask = parseHex(mask);
+    } else if (notSpec) {
+        cmdlineP->function = FN_NOT;
+    } else if (shiftleftSpec) {
+        cmdlineP->function = FN_SHIFTLEFT;
+    } else if (shiftrightSpec) {
+        cmdlineP->function = FN_SHIFTRIGHT;
     } else 
         pm_error("You must specify one of -multiplier, -divisor, "
-                 "-adder, -subtractor, -min, or -max");
+                 "-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.",
@@ -124,6 +194,70 @@ parseCommandLine(int argc, char ** const argv,
 
 
 
+static bool
+isDyadicMaskFunction(enum function const fn) {
+
+    return (fn == FN_AND || fn == FN_OR || fn == FN_XOR);
+}
+
+
+
+static bool
+isMaskFunction(enum function const fn) {
+
+    return (isDyadicMaskFunction(fn) || fn == FN_NOT);
+}
+
+
+
+static bool
+isShiftFunction(enum function const fn) {
+
+    return (fn == FN_SHIFTLEFT || fn == FN_SHIFTRIGHT);
+}
+
+
+
+static bool
+isBitstringFunction(enum function const fn) {
+
+    return isMaskFunction(fn) || isShiftFunction(fn);
+}
+
+
+
+static void
+validateFunction(struct cmdlineInfo const cmdline,
+                 const struct pam * const pamP) {
+
+    if (isBitstringFunction(cmdline.function)) {
+        if (pm_bitstomaxval(pm_maxvaltobits(pamP->maxval)) != pamP->maxval)
+            pm_error("For a bit string function, the maxval must be a full "
+                     "binary count, i.e. a power of two minus one such as "
+                     "0xff or 0x1.  You have 0x%x",
+                     (unsigned)pamP->maxval);
+
+        if (isDyadicMaskFunction(cmdline.function)) {
+            if ((cmdline.u.mask & pamP->maxval) != cmdline.u.mask)
+                pm_error("Your bit string mask 0x%x is wider than the samples "
+                         "of the image (%u bits, according to the maxval %lu",
+                         cmdline.u.mask, pm_maxvaltobits(pamP->maxval),
+                         pamP->maxval);
+        }
+
+        if (isShiftFunction(cmdline.function)) {
+            if (cmdline.u.shiftCount > pm_maxvaltobits(pamP->maxval))
+                pm_error("Your shift count (%u) is greater than the width "
+                         "of the samples of the image (%u bits, according "
+                         "to the maxval %lu)",
+                         cmdline.u.shiftCount, pm_maxvaltobits(pamP->maxval),
+                         pamP->maxval);
+        }
+    }
+}
+
+
+
 static void
 applyFunction(struct cmdlineInfo const cmdline,
               struct pam         const inpam,
@@ -168,6 +302,25 @@ applyFunction(struct cmdlineInfo const cmdline,
             case FN_MIN:
                 outSample = MIN(inSample, cmdline.u.max);
                 break;
+            case FN_AND:
+                outSample = inSample & cmdline.u.mask;
+                break;
+            case FN_OR:
+                outSample = inSample | cmdline.u.mask;
+                break;
+            case FN_XOR:
+                outSample = inSample ^ cmdline.u.mask;
+                break;
+            case FN_NOT:
+                outSample = ~inSample;
+                break;
+            case FN_SHIFTLEFT:
+                outSample =
+                    (inSample << cmdline.u.shiftCount) & outpam.maxval;
+                break;
+            case FN_SHIFTRIGHT:
+                outSample = inSample >> cmdline.u.shiftCount;
+                break;
             }
             outputRow[col][plane] = MIN(outpam.maxval, outSample);
         }
@@ -179,15 +332,15 @@ applyFunction(struct cmdlineInfo const cmdline,
 int
 main(int argc, char *argv[]) {
 
-    FILE* ifP;
-    tuple* inputRow;   /* Row from input image */
-    tuple* outputRow;  /* Row of output image */
+    FILE * ifP;
+    tuple * inputRow;   /* Row from input image */
+    tuple * outputRow;  /* Row of output image */
     int row;
     struct cmdlineInfo cmdline;
     struct pam inpam;   /* Input PAM image */
     struct pam outpam;  /* Output PAM image */
 
-    pnm_init( &argc, argv );
+    pnm_init(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
 
@@ -195,6 +348,8 @@ main(int argc, char *argv[]) {
 
     pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
 
+    validateFunction(cmdline, &inpam);
+
     inputRow = pnm_allocpamrow(&inpam);
 
     outpam = inpam;    /* Initial value -- most fields should be same */
@@ -216,6 +371,6 @@ main(int argc, char *argv[]) {
     pm_close(inpam.file);
     pm_close(outpam.file);
     
-    exit(0);
+    return 0;
 }
 
diff --git a/editor/pammasksharpen.c b/editor/pammasksharpen.c
index 87b928be..e61237ca 100644
--- a/editor/pammasksharpen.c
+++ b/editor/pammasksharpen.c
@@ -1,6 +1,7 @@
-#include "pam.h"
-#include "shhopt.h"
+#include "pm_c_util.h"
 #include "mallocvar.h"
+#include "shhopt.h"
+#include "pam.h"
 
 struct cmdlineInfo {
     /* All the information the user supplied in the command line,
@@ -62,7 +63,6 @@ parseCommandLine(int argc, char ** const argv,
         if (cmdlineP->threshold > 1.0)
             pm_error("-threshold greater than unity doesn't make sense.  "
                      "You specified %f", cmdlineP->threshold);
-        
     } else
         cmdlineP->threshold = 0.0;
 
diff --git a/editor/pammixinterlace.c b/editor/pammixinterlace.c
deleted file mode 100644
index 1421c7a2..00000000
--- a/editor/pammixinterlace.c
+++ /dev/null
@@ -1,173 +0,0 @@
-/******************************************************************************
-                             pammixinterlace
-*******************************************************************************
-  De-interlace an image by merging adjacent rows.
-   
-  Copyright (C) 2005 Bruce Guenter, FutureQuest, Inc.
-
-  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 "pam.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 *inputFilespec;  /* Filespecs of input files */
-};
-
-
-static void
-parseCommandLine(int argc, char ** argv,
-                 struct cmdlineInfo *cmdlineP) {
-/*----------------------------------------------------------------------------
-   Note that the file spec array we return is stored in the storage that
-   was passed to us as the argv array.
------------------------------------------------------------------------------*/
-    optStruct3 opt;  /* set by OPTENT3 */
-    optEntry *option_def;
-    unsigned int option_def_index;
-
-    MALLOCARRAY_NOFAIL(option_def, 100);
-
-    option_def_index = 0;   /* incremented by OPTENT3 */
-
-    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 */
-
-    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
-    /* Uses and sets argc, argv, and some of *cmdlineP and others. */
-
-    if (argc-1 < 1)
-        cmdlineP->inputFilespec = "-";
-    else if (argc-1 == 1)
-        cmdlineP->inputFilespec = argv[1];
-    else
-        pm_error("You specified too many arguments (%d).  The only "
-                 "argument is the optional input file specification.",
-                 argc-1);
-}
-
-
-
-static void
-allocateRowWindowBuffer(struct pam * const pamP,
-                        tuple **     const tuplerow) {
-
-    unsigned int row;
-
-    for (row = 0; row < 3; ++row)
-        tuplerow[row] = pnm_allocpamrow(pamP);
-}
-
-
-
-static void
-freeRowWindowBuffer(tuple ** const tuplerow) {
-
-    unsigned int row;
-
-    for (row = 0; row < 3; ++row)
-        pnm_freepamrow(tuplerow[row]);
-
-}
-
-
-
-static void
-slideWindowDown(tuple ** const tuplerow) {
-/*----------------------------------------------------------------------------
-  Slide the 3-line tuple row window tuplerow[] down one row by moving
-  pointers.
-
-  tuplerow[2] ends up an uninitialized buffer.
------------------------------------------------------------------------------*/
-    tuple * const oldrow0 = tuplerow[0];
-    tuplerow[0] = tuplerow[1];
-    tuplerow[1] = tuplerow[2];
-    tuplerow[2] = oldrow0;
-}
-
-
-
-int
-main(int argc, char *argv[]) {
-
-    FILE * ifP;
-    struct cmdlineInfo cmdline;
-    struct pam inpam;  
-    struct pam outpam;
-    tuple * tuplerow[3];
-    tuple * outputrow;
-    
-    pnm_init( &argc, argv );
-
-    parseCommandLine(argc, argv, &cmdline);
-
-    ifP = pm_openr(cmdline.inputFilespec);
-    
-    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
-
-    outpam = inpam;    /* Initial value -- most fields should be same */
-    outpam.file = stdout;
-
-    pnm_writepaminit(&outpam);
-
-    allocateRowWindowBuffer(&inpam, tuplerow);
-    outputrow = pnm_allocpamrow(&outpam);
-
-    if (inpam.height < 3) {
-        unsigned int row;
-        pm_message("WARNING: Image height less than 3.  No mixing done.");
-        for (row = 0; row < inpam.height; ++row) {
-            pnm_readpamrow(&inpam, tuplerow[0]);
-            pnm_writepamrow(&outpam, tuplerow[0]);
-        }
-    } else {
-        unsigned int row;
-
-        pnm_readpamrow(&inpam, tuplerow[0]);
-        pnm_readpamrow(&inpam, tuplerow[1]);
-
-        /* Pass through first row */
-        pnm_writepamrow(&outpam, tuplerow[0]);
-
-        for (row = 2; row < inpam.height; ++row) {
-            unsigned int col;
-            pnm_readpamrow(&inpam, tuplerow[2]);
-            for (col = 0; col < inpam.width; ++col) {
-                unsigned int plane;
-
-                for (plane = 0; plane < inpam.depth; ++plane) {
-                    outputrow[col][plane] =
-                        (tuplerow[0][col][plane]
-                         + tuplerow[1][col][plane] * 2
-                         + tuplerow[2][col][plane]) / 4;
-                }
-            }
-            pnm_writepamrow(&outpam, outputrow);
-            
-            slideWindowDown(tuplerow);
-        }
-
-        /* Pass through last row */
-        pnm_writepamrow(&outpam, tuplerow[1]);
-    }
-
-    freeRowWindowBuffer(tuplerow);
-    pnm_freepamrow(outputrow);
-    pm_close(inpam.file);
-    pm_close(outpam.file);
-    
-    return 0;
-}
diff --git a/editor/pamperspective.c b/editor/pamperspective.c
index a655443e..6bf8314e 100644
--- a/editor/pamperspective.c
+++ b/editor/pamperspective.c
@@ -20,12 +20,15 @@
 
 #define _BSD_SOURCE   /* Make sure strdup is int string.h */
 
+#include <assert.h>
+#include <stdlib.h>
 #include <math.h>
 #include <string.h>
 
-#include "pam.h"
-#include "shhopt.h"
+#include "pm_c_util.h"
 #include "mallocvar.h"
+#include "shhopt.h"
+#include "pam.h"
 
 typedef double number;
 
@@ -164,9 +167,9 @@ typedef struct {
          xw_ll, yw_ll, zw_ll,  xw_lr, yw_lr, zw_lr;
 
   /* Originally I planned to include the possibility to move the
-     centre of projection, that is the pixel the camera "looks at".  It
+     center of projection, that is the pixel the camera "looks at".  It
      turned out, maybe surprisingly, that this does not have any
-     effect. So now this centre is moved to (0,0).
+     effect. So now this center is moved to (0,0).
      
      Another original plan was to correct the output parameters
      depending on the lengths of the paralellograms sides or its
@@ -184,26 +187,36 @@ typedef struct {
 } world_data;
 
 
-/*
-  Internal infile buffer
-
-  This is a cyclic in random access out buffer, just large enough
-  to store all input lines that are still in use.
-*/
 
 typedef struct {
-
-  int num_rows, last_physical, last_logical;
-  tuple** rows;
-  const struct pam* inpam;
-
+/*----------------------------------------------------------------------------
+   A buffer of image input.  This holds a vertical window of the input.
+-----------------------------------------------------------------------------*/
+    unsigned int numRows;
+        /* Height of buffer window */
+    unsigned int nextImageRow;
+        /* Row number of the next image row that will go into the buffer.
+           The 'numRows' rows before (above) that are in the buffer now.
+        */
+    unsigned int nextBufferRow;
+        /* Row number in the physical buffer (index of rows[]) where
+           the next row read will go (hence where the oldest/highest
+           row in the buffer is now).
+        */
+    tuple ** rows;
+        /* The rows of the window, as a cyclic buffer */
+    const struct pam * inpamP;
+        /* The image from which we fill the buffer */
 } buffer;
 
 
 
+typedef void interpolateFn(tuple, number, number);
+
 /*
   The following are like MALLOCARRAY_NOFAIL and MALLOCVAR_NOFAIL,
-  but issue an error message instead of aborting.
+  but abort (fail) the program instead of killing the process with an
+  abort signal.
 */
 
 #define MALLOCARRAY_SAFE(handle,length) \
@@ -306,45 +319,53 @@ static int parse_enum (const char *const text,
 
 
 
-static number parse_float (char *const text)
+static number
+parseFloat(const char * const text) {
 /*----------------------------------------------------------------------------
   Parse an argument given to a float command line option.  We cannot
   just call strtod, because we want to parse fractions like "5/3"
 -----------------------------------------------------------------------------*/
-{
-  bool error;
-  char* end;
-  char* denstart;
-  number num,den;
-
-  error = FALSE;
-  num = strtod (text, &end);    /* try strtod anyway */
-  switch (*end) {
-  case 0:           /* It is a plain number */
-    break;
-  case '/':         /* It might be a fraction */
-    /* (Try to) parse the numerator */
-    *end = 0;
-    num = strtod (text, &end);
-    error = (*end) != 0;
-    if (!error) {
-      /* Undo the above change */
-      *end = '/';
-      /* (Try to) parse the denominator */
-      denstart = end+1;
-      den = strtod (denstart, &end);
-      error = (fabs(den)<eps) || ((*end) != 0);
-      if (!error)
-    num /= den;
+    bool error;
+    char * end;
+    number num;
+    char * buffer;
+    
+    buffer = strdup(text);
+    if (!buffer)
+        pm_error("Out of memory");
+
+    error = FALSE;
+    num = strtod(buffer, &end);    /* try strtod anyway */
+    switch(*end) {
+    case 0:           /* It is a plain number */
+        break;
+    case '/':         /* It might be a fraction */
+        /* (Try to) parse the numerator */
+        *end = 0;
+        num = strtod(text, &end);
+        error = (*end != '\0');
+        if (!error) {
+            char * const denStart = end + 1;
+            number denominator;
+
+            /* Undo the above change */
+            *end = '/';
+            /* (Try to) parse the denominator */
+            denominator = strtod(denStart, &end);
+            error = (fabs(denominator) < eps) || (*end != '\0');
+            if (!error)
+                num /= denominator;
+        };
+        break;
+    default:          /* It is no number format we know */
+        error = TRUE;
     };
-    break;
-  default:          /* It is no number format we know */
-    error = TRUE;
-  };
-  if (error)
-    pm_error ("Invalid number format: %s", text);
+    if (error)
+        pm_error("Invalid number format: %s", text);
 
-  return num;
+    free(buffer);
+
+    return num;
 }
 
 
@@ -373,8 +394,8 @@ static void parse_include_point(char * specification,
   if (*comma_seek == 0)
     pm_error ("Invalid format for --include point: '%s'", specification);
   *comma_seek = 0;      /* separate the two parts for parsing purposes */
-  new_point->xi = (number) parse_float(specification);
-  new_point->yi = (number) parse_float(comma_seek+1);
+  new_point->xi = (number) parseFloat(specification);
+  new_point->yi = (number) parseFloat(comma_seek+1);
   *comma_seek = ',';
 }
 
@@ -422,9 +443,12 @@ static void parse_include_points(const char * const include_opt,
 }
 
 
-static void parse_command_line (int argc, char* argv[], option *const options)
-{
-  char* float_text[num_float_options];
+
+static void
+parseCommandLine(int argc, const char * argv[],
+                 option *  const options) {
+
+  const char* float_text[num_float_options];
   unsigned int float_spec[num_float_options];
   char* enum_text[num_enum_options];
   unsigned int enum_spec[num_enum_options];
@@ -437,6 +461,8 @@ static void parse_command_line (int argc, char* argv[], option *const options)
   unsigned int option_def_index;
   optEntry* option_def;
 
+  set_command_line_defaults(options);
+
   /* Let shhopt try its best */
 
   option_def_index = 0;
@@ -460,7 +486,7 @@ static void parse_command_line (int argc, char* argv[], option *const options)
   opt.opt_table = option_def;
   opt.short_allowed = FALSE;
   opt.allowNegNum = TRUE;
-  optParseOptions3 (&argc, argv, opt, sizeof(opt), 0);
+  optParseOptions3 (&argc, (char **)argv, opt, sizeof(opt), 0);
 
   /* The non-option arguments are optionally all eight coordinates
      and optionally the input filename
@@ -493,7 +519,7 @@ static void parse_command_line (int argc, char* argv[], option *const options)
 
   for (i=0; i<num_float_options; i++)
     if (float_spec[i])
-      options->floats[i] = parse_float (float_text[i]);
+      options->floats[i] = parseFloat (float_text[i]);
 
   /* Parse enum options -- shhopt retrieved them as strings */
 
@@ -611,7 +637,7 @@ static bool solve_3_linear_equations (number* x1, number* x2, number* x3,
     a11*x1 + a12*x2 + a13*x3 = b1
     a21*x1 + a22*x2 + a23*x3 = b2
     a31*x1 + a32*x2 + a33*x3 = b3
-  The return value is wether the system is solvable
+  The return value is whether the system is solvable
 ----------------------------------------------------------------------------*/
 {
   number c11,c12,d1,c21,c22,d2,e,f;
@@ -706,18 +732,18 @@ static bool solve_3_linear_equations (number* x1, number* x2, number* x3,
 static void determine_world_parallelogram (world_data *const world,
                                            const option *const options)
 /*----------------------------------------------------------------------------
-  constructs xw_ul,...,zw_lr from xi_ul,...,yi_lr
+  Construct xw_ul,...,zw_lr from xi_ul,...,yi_lr
      
-  Actually this is a solution of a linear equation system.
+  This is a solution of a linear equation system.
   
-  We first solve 4 variables (the 4 z-coordinates) against 4
-  equations: Each z-coordinate determines the corresponding x- and
-  y-coordinates in a linear fashion, where the coefficients are taken
-  from the image coordinates. This corresponds to the fact that a
-  point of an image determines a line in the world.
+  We first solve 4 equations for 4 variables (the 4 z-coordinates):
+  Each z-coordinate determines the corresponding x- and y-coordinates
+  in a linear fashion, where the coefficients are taken from the image
+  coordinates.  This corresponds to the fact that a point of an image
+  determines a line in the world.
   
   3 equations state that the 4 points form a parallelogram.  The 4th
-  equation is for normalization and states, that the centre of the
+  equation is for normalization and states that the center of the
   parallelogram has a z-coordinate of 1.
 -----------------------------------------------------------------------------*/
 {
@@ -885,9 +911,11 @@ static void determine_world_parallelogram (world_data *const world,
 
 
 
-static int diff (int const a, int const b)
-{
-  return MAX (b-a, a-b);
+static unsigned int
+distance(unsigned int const a,
+         unsigned int const b) {
+
+    return a > b ? a - b : b - a;
 }
 
 
@@ -1024,8 +1052,8 @@ static void determine_coefficients_pixel (world_data *const world,
   Constructs ax,...,cz from xw_ul,...,zw_lr
      
   The calculations assume pixel coordinates, that is the point ul
-  corresponds to the centre of the pixel (0,0) and the point lr
-  corresponds to the centre of the pixel (width-1,height-1)
+  corresponds to the center of the pixel (0,0) and the point lr
+  corresponds to the center of the pixel (width-1,height-1)
 -----------------------------------------------------------------------------*/
 {
   number width,height;
@@ -1054,40 +1082,46 @@ static void determine_coefficients_pixel (world_data *const world,
 
 
 
-static void outpixel_to_inpixel (int const xo, int const yo, 
-                                 number* const xi, number* const yi,
-                                 const world_data *const world)
-{
-  number xof,yof,xw,yw,zw;
-
-  xof = (number) xo;
-  yof = (number) yo;
-  xw = world->ax + world->bx*xof + world->cx*yof;
-  yw = world->ay + world->by*xof + world->cy*yof;
-  zw = world->az + world->bz*xof + world->cz*yof;
-  *xi = xw/zw;
-  *yi = yw/zw;
+static void
+outpixelToInPos(int                const outCol,
+                int                const outRow, 
+                number *           const inColP,
+                number *           const inRowP,
+                const world_data * const worldP) {
+/*----------------------------------------------------------------------------
+   For a pixel of the output image at Column 'outCol', row 'outRow',
+   determine the position in the input image that corresponds to the
+   center of that pixel.
+
+   This position is not a pixel position -- it's a position in
+   continuous space, for example Row 9.2, Column 0.1.  And it isn't
+   necessarily within the input image, for example Column 600 even though
+   the input image is only 500 pixels wide, and a coordinate might even
+   be negative.
+-----------------------------------------------------------------------------*/
+    number const outColF = (number) outCol;
+    number const outRowF = (number) outRow;
+
+    number const xw = worldP->ax + worldP->bx * outColF + worldP->cx * outRowF;
+    number const yw = worldP->ay + worldP->by * outColF + worldP->cy * outRowF;
+    number const zw = worldP->az + worldP->bz * outColF + worldP->cz * outRowF;
+
+    *inColP = xw/zw;
+    *inRowP = yw/zw;
 }
 
-static int outpixel_to_iny (int xo, int yo, const world_data *const world)
-{
-  number xi,yi;
 
-  outpixel_to_inpixel (xo,yo,&xi,&yi,world);
 
-  return (int) yi;
-}
+static int
+outpixelToInRow(int                const outCol,
+                int                const outRow,
+                const world_data * const worldP) {
 
-static int clean_y (int const y,  const struct pam *const outpam)
-{
-  return MIN(MAX(0, y), outpam->height-1);
-}
+    number xi, yi;
 
-static unsigned int
-distance(unsigned int const a,
-         unsigned int const b) {
+    outpixelToInPos(outCol, outRow, &xi, &yi, worldP);
 
-    return a > b ? a - b : b - a;
+    return (int) yi;
 }
 
 
@@ -1101,6 +1135,71 @@ boundedRow(int                const unboundedRow,
 
 
 
+#if 0
+/* This is the original calculation of window height.  It's
+   mysterious, and doesn't work.  It looks like it basically wants to
+   take the greater of vertical displacement of the top edge of the
+   input quadrilateral and that of the bottom edge.  In simple
+   scenarios, that is in fact what it does, and I can see how those
+   edges might be where the most stretching takes place.  However, it
+   the calculation is obviously more complex than that.
+
+   It doesn't work because the actual image generation produces rows
+   in the middle that are derived from lines in the input quadrilateral
+   with greater slope than either the top or bottom edge.  I.e. to
+   compute one output row, it needs more rows of input than this 
+   calculation provides.
+
+   I don't know if that means the computation of the output is wrong
+   or the computation of the window height is wrong.  The code is too
+   opaque.  But just to make a viable computation, I replaced the
+   window height calculation with the brute force computation you
+   see below: it determines the vertical displacement of every line
+   of the input quadrilateral that is used to generate an output row
+   and takes the greatest of them for the window height.
+
+   - Bryan Henderson 08.07.27.
+*/
+   
+
+static unsigned int
+windowHeight(const world_data * const worldP,
+             const struct pam * const inpamP,
+             const struct pam * const outpamP,
+             const option *     const optionsP) {
+
+    unsigned int numRows;
+    int yul, yur, yll, ylr, y_min;
+
+    yul = outpixelToInRow(0, 0, worldP);
+    yur = outpixelToInRow(outpamP->width-1, 0, worldP);
+    yll = outpixelToInRow(0, outpamP->height-1, worldP);
+    ylr = outpixelToInRow(outpamP->width-1, outpamP->height-1, worldP);
+    
+    y_min = MIN(MIN(yul, yur), MIN(yll, ylr));
+    numRows = MAX(MAX(diff(yul, yur),
+                      diff(yll, ylr)),
+                  MAX(diff(boundedRow(yul, outpamP),
+                           boundedRow(y_min, outpamP)),
+                      diff(boundedRow(yur, outpamP),
+                           boundedRow(y_min, outpamP))))
+        + 2;
+    switch (optionsP->enums[3]) {  /* --interpolation */
+    case interp_nearest:
+        break;
+    case interp_linear:
+        numRows += 1;
+        break;
+    }
+    if (numRows > inpamP->height)
+        numRows = inpamP->height;
+
+    return numRows;
+}
+#endif
+
+
+
 static unsigned int
 windowHeight(const world_data * const worldP,
              const struct pam * const inpamP,
@@ -1116,9 +1215,9 @@ windowHeight(const world_data * const worldP,
         unsigned int const leftCol = 0;
         unsigned int const rghtCol = outpamP->width - 1;
         unsigned int const leftInRow =
-            boundedRow(outpixel_to_iny(leftCol, outRow, worldP), outpamP);
+            boundedRow(outpixelToInRow(leftCol, outRow, worldP), outpamP);
         unsigned int const rghtInRow =
-            boundedRow(outpixel_to_iny(rghtCol, outRow, worldP), outpamP);
+            boundedRow(outpixelToInRow(rghtCol, outRow, worldP), outpamP);
         
         unsigned int const rowWindowHeight = distance(leftInRow, rghtInRow);
 
@@ -1133,249 +1232,305 @@ windowHeight(const world_data * const worldP,
 
 
 static void
-init_buffer(buffer *           const bufferP,
+buffer_init(buffer *           const bufferP,
             const world_data * const worldP,
             const option *     const optionsP,
             const struct pam * const inpamP,
             const struct pam * const outpamP) {
 
-    unsigned int const num_rows =
+    unsigned int const numRows =
         windowHeight(worldP, inpamP, outpamP, optionsP);
 
-    MALLOCARRAY_SAFE(bufferP->rows, num_rows);
-    bufferP->num_rows = num_rows;
-    {
-        unsigned int row;
-        for (row = 0; row < num_rows; ++row) {
-            bufferP->rows[row] = pnm_allocpamrow(inpamP);
-            pnm_readpamrow(inpamP, bufferP->rows[row]);
-        }
+    unsigned int row;
+
+    MALLOCARRAY_SAFE(bufferP->rows, numRows);
+
+    for (row = 0; row < numRows; ++row) {
+        bufferP->rows[row] = pnm_allocpamrow(inpamP);
+        pnm_readpamrow(inpamP, bufferP->rows[row]);
     }
-    bufferP->last_logical = num_rows-1;
-    bufferP->last_physical = num_rows-1;
-    bufferP->inpam = inpamP;
+
+    bufferP->nextImageRow  = numRows;
+    bufferP->nextBufferRow = 0;
+    bufferP->numRows       = numRows;
+
+    bufferP->inpamP = inpamP;
 }
 
 
 
+static const tuple *
+buffer_getRow(buffer *     const bufferP,
+              unsigned int const imageRow) {
+/*----------------------------------------------------------------------------
+   Return row 'imageRow' of an image.
 
-static tuple* read_buffer (buffer *const b, int const logical_y)
-{
-  int y;
-
-  while (logical_y > b->last_logical) {
-    b->last_physical++;
-    if (b->last_physical == b->num_rows)
-      b->last_physical = 0;
-    pnm_readpamrow (b->inpam, b->rows[b->last_physical]);
-    b->last_logical++;
-  }
+   The return value is a pointer into storage that belongs to *bufferP.
 
-  y = logical_y - b->last_logical + b->last_physical;
-  if (y<0)
-    y += b->num_rows;
+   *bufferP remembers only a window of the image, and the window
+   cannot move up, so 'imageRow' cannot be higher in the image than
+   the lowest row read so far through *bufferP plus *bufferP's maximum
+   window height.  We assume that.
+-----------------------------------------------------------------------------*/
+    unsigned int bufferRow;
+        /* The row of the buffer that holds row 'imageRow' of the image */
+    unsigned int n;
+        /* Number of rows our row is before the bottom of the window */
+
+    assert(imageRow >= bufferP->nextImageRow - bufferP->numRows);
+        /* The requested row is not one that's already been bumped out
+           of the buffer.
+        */
+
+    while (imageRow >= bufferP->nextImageRow) {
+        pnm_readpamrow(bufferP->inpamP, bufferP->rows[bufferP->nextBufferRow]);
+
+        ++bufferP->nextBufferRow;
+        if (bufferP->nextBufferRow == bufferP->numRows)
+            bufferP->nextBufferRow = 0;
+
+        ++bufferP->nextImageRow;
+    }
+
+    n = bufferP->nextImageRow - imageRow;
+
+    assert(n <= bufferP->numRows);
+    
+    if (n <= bufferP->nextBufferRow)
+        bufferRow = bufferP->nextBufferRow - n;
+    else
+        bufferRow = bufferP->nextBufferRow + bufferP->numRows - n;
 
-  return b->rows[y];
+    assert(bufferRow < bufferP->numRows);
+
+    return bufferP->rows[bufferRow];
 }
 
-static void free_buffer (buffer *const b)
-{
-  int i;
 
-  /* We have to read through the end of the input image even if we
-     didn't use all the rows, because if the input is a pipe, the
-     guy writing into the pipe may require all the data to go
-     through.
-  */
-  
-  while (b->last_logical < b->inpam->height-1) {
-      pnm_readpamrow(b->inpam, b->rows[0]);
-      ++b->last_logical;
-  }
 
-  for (i=0; i<b->num_rows; i++)
-    pnm_freepamrow (b->rows[i]);
-  free (b->rows);
+static void
+buffer_term(buffer * const bufferP) {
+
+    unsigned int i;
+
+    /* We have to read through the end of the input image even if we
+       didn't use all the rows, because if the input is a pipe, the
+       guy writing into the pipe may require all the data to go
+       through.
+    */
+
+    while (bufferP->nextImageRow < bufferP->inpamP->height) {
+        pnm_readpamrow(bufferP->inpamP, bufferP->rows[0]);
+        ++bufferP->nextImageRow;
+    }
+
+    for (i = 0; i < bufferP->numRows; ++i)
+        pnm_freepamrow(bufferP->rows[i]);
+    
+    free(bufferP->rows);
 }
 
 
 
 
-/* The following variables are global for speed reasons.
-   In this way they do not have to be passed to each call of the
+struct interpContext {
+    tuple background;
+    buffer* indata;
+    int width,height,depth;
+};
+
+/* The following is global for speed reasons.
+   In this way it does not have to be passed to each call of the
    interpolation functions
 
    Think of this as Sch&ouml;nfinkeling (aka Currying).
 */
 
-static tuple background;
-static buffer* indata;
-static int width,height,depth;
+static struct interpContext ictx;
 
-static void init_interpolation_global_vars (buffer* const inbuffer,
-                                            const struct pam *const inpam,
-                                            const struct pam *const outpam)
-{
-  pnm_createBlackTuple (outpam, &background);
-  indata = inbuffer;
-  width = inpam->width;
-  height = inpam->height;
-  depth = outpam->depth;
+static void
+init_interpolation_global_vars(buffer *           const inbufferP,
+                               const struct pam * const inpamP,
+                               const struct pam * const outpamP) {
+
+    pnm_createBlackTuple(outpamP, &ictx.background);
+    ictx.indata = inbufferP;
+    ictx.width  = inpamP->width;
+    ictx.height = inpamP->height;
+    ictx.depth  = outpamP->depth;
 }
 
 
 
-static void clean_interpolation_global_vars (void)
-{
-  free (background);
+static void
+clean_interpolation_global_vars(void) {
+
+    free(ictx.background);
 }
 
 
 
 /* These functions perform the interpolation */
 
-static tuple attempt_read (int const x, int const y)
-{
-  if ((x<0) || (x>=width) || (y<0) || (y>=height))
-    return background;
-  else
-    return read_buffer(indata, y)[x];
+static tuple
+getPixel(int const col,
+         int const row) {
+/*----------------------------------------------------------------------------
+   Get the pixel at Row 'row', Column 'col' of the image which is the
+   context of the interpolation in which we are called.
+
+   Consider the image to go on forever in all directions (even negative
+   column/row numbers), being the background color everywhere outside
+   the actual image.
+-----------------------------------------------------------------------------*/
+    if ((col < 0) || (col >= ictx.width) || (row < 0) || (row >= ictx.height))
+        return ictx.background;
+    else
+        return buffer_getRow(ictx.indata, row)[col];
 }
 
 
 
-static void take_nearest (tuple const dest, number const x, number const y)
-{
-  int xx,yy,entry;
-  tuple p;
-
-  xx = (int)floor(x+0.5);
-  yy = (int)floor(y+0.5);
-  p = attempt_read (xx, yy);
-  for (entry=0; entry<depth; entry++) {
-    dest[entry]=p[entry];
-  }
-}
+static void
+takeNearest(tuple  const dest,
+            number const x,
+            number const y) {
 
+    int   const xx = (int)floor(x+0.5);
+    int   const yy = (int)floor(y+0.5);
+    tuple const p  = getPixel(xx, yy);
 
+    unsigned int entry;
 
-static void linear_interpolation (tuple const dest, 
-                                  number const x, number const y)
-{
-  int xx,yy,entry;
-  number xf,yf,a,b,c,d;
-  tuple p1,p2,p3,p4;
-
-  xx = (int)floor(x);
-  yy = (int)floor(y);
-  xf = x-(number)xx;
-  yf = y-(number)yy;
-  p1 = attempt_read (xx, yy);
-  p2 = attempt_read (xx+1, yy);
-  p3 = attempt_read (xx, yy+1);
-  p4 = attempt_read (xx+1, yy+1);
-  a = (1.0-xf)*(1.0-yf);
-  b = xf*(1.0-yf);
-  c = (1.0-xf)*yf;
-  d = xf*yf;
-  for (entry=0; entry<depth; entry++) {
-    dest[entry]=(sample) floor(
-      a*((number) p1[entry]) +
-      b*((number) p2[entry]) +
-      c*((number) p3[entry]) +
-      d*((number) p4[entry]) +
-      0.5);
-  }
+    for (entry = 0; entry < ictx.depth; ++entry) {
+        dest[entry] = p[entry];
+    }
 }
 
 
 
-int main (int argc, char* argv[])
-{
-  FILE* infp;
-  struct pam inpam;
-  buffer inbuffer;
-  FILE* outfp;
-  struct pam outpam;
-  tuple* outrow;
-  option options;
-  world_data world;
-  int row,col;
-  number xi,yi;
-  void (*interpolate) (tuple, number, number);
+static void
+linearInterpolation(tuple  const dest, 
+                    number const x,
+                    number const y) {
+
+    int    const xx = (int)floor(x);
+    int    const yy = (int)floor(y);
+    number const xf = x - (number)xx;
+    number const yf = y - (number)yy;
+    tuple  const p1 = getPixel(xx, yy);
+    tuple  const p2 = getPixel(xx+1, yy);
+    tuple  const p3 = getPixel(xx, yy+1);
+    tuple  const p4 = getPixel(xx+1, yy+1);
+    number const a  = (1.0-xf) * (1.0-yf);
+    number const b  = xf * (1.0-yf);
+    number const c  = (1.0-xf) * yf;
+    number const d  = xf * yf;
+
+    unsigned int entry;
+
+    for (entry=0; entry < ictx.depth; ++entry) {
+        dest[entry] = floor(
+            a * (number) p1[entry] +
+            b * (number) p2[entry] +
+            c * (number) p3[entry] +
+            d * (number) p4[entry] +
+            0.5);
+    }
+}
 
-  /* The usual initializations */
 
-  pnm_init (&argc, argv);
-  set_command_line_defaults (&options);
-  parse_command_line (argc, argv, &options);
-  infp = pm_openr (options.infilename);
-  pnm_readpaminit (infp, &inpam, PAM_STRUCT_SIZE(tuple_type));
 
-  /* Our own initializations */
+static void
+perspective(struct pam * const outpamP,
+            world_data * const worldP,
+            interpolateFn *    interpolater) {
 
-  init_world (&options, &inpam, &world);
-  determine_world_parallelogram (&world, &options);
-  determine_output_width_and_height (&world, &options);
-  switch (options.enums[1]) {   /* --output_system */
-  case lattice:
-    determine_coefficients_lattice (&world, &options);
-    break;
-  case pixel_s:
-    determine_coefficients_pixel (&world, &options);
-    break;
-  };
+    tuple * outrow;
+    unsigned int row;
 
-  /* Initialize outpam */
-
-  outfp = pm_openw ("-");
-  outpam.size = sizeof (outpam);
-  outpam.len = PAM_STRUCT_SIZE(bytes_per_sample);
-  outpam.file = outfp;
-  outpam.format = inpam.format;
-  outpam.plainformat = inpam.plainformat;
-  outpam.height = options.height;
-  outpam.width = options.width;
-  outpam.depth = inpam.depth;
-  outpam.maxval = inpam.maxval;
-  outpam.bytes_per_sample = inpam.bytes_per_sample;
-  pnm_writepaminit (&outpam);
-
-  /* Initialize the actual calculation */
-
-  init_buffer (&inbuffer, &world, &options, &inpam, &outpam);
-  outrow = pnm_allocpamrow (&outpam);
-  init_interpolation_global_vars (&inbuffer,&inpam,&outpam);
-  switch (options.enums[3]) {   /* --interpolation */
-  case interp_nearest:
-    interpolate = take_nearest;
-    break;
-  case interp_linear:
-    interpolate = linear_interpolation;
-    break;
-  };
+    outrow = pnm_allocpamrow(outpamP);
 
-  /* Perform the actual calculation */
+    for (row = 0; row < outpamP->height; ++row) {
+        unsigned int col;
 
-  for (row=0; row<outpam.height; row++) {
-    for (col=0; col<outpam.width; col++) {
-      outpixel_to_inpixel (col,row,&xi,&yi,&world);
-      interpolate(outrow[col],xi,yi);
+        for (col = 0; col < outpamP->width; ++col) {
+            number xi, yi;
+            outpixelToInPos(col, row, &xi, &yi, worldP);
+            interpolater(outrow[col], xi, yi);
+        }
+        pnm_writepamrow(outpamP, outrow);
     }
-    pnm_writepamrow (&outpam, outrow);
-  }
+    pnm_freepamrow(outrow);
+}
 
-  /* Close everything down nicely */
 
-  clean_interpolation_global_vars ();
-  free_buffer (&inbuffer);
-  pnm_freepamrow (outrow);
-  free_option (&options);
-  pm_close (infp);
-  pm_close (outfp);
-  return 0;
-}
 
+int
+main(int argc, const char * argv[]) {
+
+    FILE * ifP;
+    struct pam inpam;
+    buffer inbuffer;
+    struct pam outpam;
+    option options;
+    world_data world;
+    interpolateFn * interpolater;
+
+    pm_proginit(&argc, argv);
 
+    parseCommandLine(argc, argv, &options);
 
+    ifP = pm_openr(options.infilename);
 
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    /* Our own initializations */
+
+    init_world(&options, &inpam, &world);
+    determine_world_parallelogram(&world, &options);
+    determine_output_width_and_height(&world, &options);
+    switch (options.enums[1]) {   /* --output_system */
+    case lattice:
+        determine_coefficients_lattice(&world, &options);
+        break;
+    case pixel_s:
+        determine_coefficients_pixel(&world, &options);
+        break;
+    };
+
+    outpam.size             = sizeof(outpam);
+    outpam.len              = PAM_STRUCT_SIZE(bytes_per_sample);
+    outpam.file             = stdout;
+    outpam.format           = inpam.format;
+    outpam.plainformat      = FALSE;
+    outpam.height           = options.height;
+    outpam.width            = options.width;
+    outpam.depth            = inpam.depth;
+    outpam.maxval           = inpam.maxval;
+    outpam.bytes_per_sample = inpam.bytes_per_sample;
+    pnm_writepaminit(&outpam);
+
+    /* Initialize the actual calculation */
+
+    buffer_init(&inbuffer, &world, &options, &inpam, &outpam);
+    init_interpolation_global_vars(&inbuffer, &inpam, &outpam);
+    switch (options.enums[3]) {   /* --interpolation */
+    case interp_nearest:
+        interpolater = takeNearest;
+        break;
+    case interp_linear:
+        interpolater = linearInterpolation;
+        break;
+    };
+
+    perspective(&outpam, &world, interpolater);
+
+    clean_interpolation_global_vars();
+    buffer_term(&inbuffer);
+    free_option(&options);
+    pm_close(ifP);
+    pm_close(stdout);
+
+    return 0;
+}
diff --git a/editor/pamscale.c b/editor/pamscale.c
index 16bd8819..0abbc205 100644
--- a/editor/pamscale.c
+++ b/editor/pamscale.c
@@ -30,6 +30,7 @@
 #include <string.h>
 #include <assert.h>
 
+#include "pm_c_util.h"
 #include "pam.h"
 #include "shhopt.h"
 #include "mallocvar.h"
@@ -2054,6 +2055,9 @@ scaleWithoutMixing(const struct pam * const inpamP,
     int row;
     int rowInInput;
 
+    assert(outpamP->maxval == inpamP->maxval);
+    assert(outpamP->depth  == inpamP->depth);
+
     tuplerow = pnm_allocpamrow(inpamP); 
     rowInInput = -1;
 
@@ -2136,7 +2140,7 @@ main(int argc, char **argv ) {
         scaleWithoutMixing(&inpam, &outpam, xscale, yscale);
     } else if (!cmdline.filterFunction) {
         if (cmdline.verbose)
-            pm_message("Using regular rescaling method");
+            pm_message("Using simple pixel mixing rescaling method");
         scaleWithMixing(&inpam, &outpam, xscale, yscale, 
                         cmdline.linear, cmdline.verbose);
     } else {
diff --git a/editor/pamsistoaglyph.c b/editor/pamsistoaglyph.c
new file mode 100644
index 00000000..1c92658c
--- /dev/null
+++ b/editor/pamsistoaglyph.c
@@ -0,0 +1,421 @@
+/* ----------------------------------------------------------------------
+ *
+ * Convert a single-image stereogram to a red/cyan anaglyphic image
+ *
+ * By Scott Pakin <scott+pbm@pakin.org>
+ *
+ * ----------------------------------------------------------------------
+ *
+ * Copyright (C) 2009 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
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see http://www.gnu.org/licenses/.
+ *
+ * ----------------------------------------------------------------------
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+#include "mallocvar.h"
+#include "nstring.h"
+#include "shhopt.h"
+#include "pam.h"
+
+
+struct cmdlineInfo {
+    /* This structure represents all of the information the user
+       supplied in the command line but in a form easy for the program
+       to use.
+    */
+    int separation;
+        /* Exact separation in pixels between the left and right eye,
+           or -1
+        */
+    int minSeparation;
+        /* Minimum separation in pixels between the left and right eye */
+    gray maxGrayVal;
+        /* Maximum grayscale value to which to scale the image */
+    int swapEyes;
+        /* 0=left red, right cyan; 1=left cyan, right red */
+    const char *inputFilename;   /* '-' if stdin */
+};
+
+
+
+static void
+parseCommandLine( int argc, const char ** const argv,
+                  struct cmdlineInfo * const cmdlineP ) {
+/*--------------------------------------------------------------------
+  Parse the command line into a structure.
+----------------------------------------------------------------------*/
+
+    optEntry     * option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options */
+    optStruct3     opt;
+    unsigned int   option_def_index;
+    int            maxgrayval;
+
+    maxgrayval = 63;  /* default */
+
+    MALLOCARRAY(option_def, 100);
+    option_def_index = 0;          /* Incremented by OPTENTRY */
+    MEMSZERO(cmdlineP);
+    cmdlineP->separation = -1;
+
+    OPTENT3('s', "sep",    OPT_INT,  &cmdlineP->separation,    NULL, 0);
+    OPTENT3('g', "gray",   OPT_INT,  &maxgrayval,              NULL, 0);
+    OPTENT3('i', "invert", OPT_FLAG, &cmdlineP->swapEyes,      NULL, 0);
+    OPTENT3('m', "minsep", OPT_INT,  &cmdlineP->minSeparation, NULL, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = 1;
+    opt.allowNegNum = 0;
+
+    optParseOptions3( &argc, (char **)argv, opt, sizeof(opt), 0 );
+
+    if (argc-1 < 1)
+        cmdlineP->inputFilename = "-";
+    else {
+        cmdlineP->inputFilename = argv[1];
+        if (argc-1 > 1)
+            pm_error("Too many arguments: %u.  The only argument is the "
+                     "optional input file name", argc-1);
+    }
+    cmdlineP->maxGrayVal = (gray) maxgrayval;
+}
+
+
+
+static gray **
+readAsGray( const char * const fileName,
+            gray         const maxGrayVal,
+            struct pam * const pamP) {
+/*--------------------------------------------------------------------
+  Read the input image and convert it to grayscale to reduce the
+  number of "almost but not quite" equal pixels.  Return the
+  grayscale array and the initialized PAM structure.
+  ----------------------------------------------------------------------*/
+
+    FILE       *  fileP;
+    tuple      *  tuplerow;
+    gray       ** grayArray;
+    unsigned int  row;
+
+    fileP = pm_openr( fileName );
+
+    pnm_readpaminit( fileP, pamP, PAM_STRUCT_SIZE(tuple_type) );
+
+    tuplerow = pnm_allocpamrow( pamP );
+
+    grayArray = pgm_allocarray( pamP->width, pamP->height );
+
+    for (row = 0; row < pamP->height; ++row) {
+        unsigned int col;
+        pnm_readpamrow( pamP, tuplerow );
+        for (col = 0; col < pamP->width; ++col) {
+            double YP, CbP, CrP;
+
+            pnm_YCbCrtuple( tuplerow[col], &YP, &CbP, &CrP );
+            grayArray[row][col] = (gray)
+                (YP * maxGrayVal / (double)pamP->maxval);
+        }
+    }
+    pnm_freepamrow( tuplerow );
+    pm_close( fileP );
+    return grayArray;
+}
+
+
+
+static int
+bestEyeSepWeEncountered(int const bestSeparation[3],
+                        int const altBestSeparation) {
+
+    int i;
+
+    for (i = 2; i >= 0; --i) {
+        if (bestSeparation[i] != 0)
+            return bestSeparation[i];
+    }    
+    return altBestSeparation;
+}
+
+
+
+static int
+findRegionEyeSeparation( gray ** const grayArray,
+                         int     const width,
+                         int     const height ) {
+/*----------------------------------------------------------------------
+  Determine the number of pixels that corresponds to the separation
+  between the viewer's left eye and right eye.  We do this by counting
+  the number of pixels that match N pixels ahead in the image for all
+  N in [1, W/2].  The first big spike in the number of matched pixels
+  determines the N to use for the eye separation.  More specifically,
+  if a spike that exceeds 3*stdev+mean is found, the corresponding
+  value of N is taken as the eye separation; otherwise, a spike
+  exceeding 2*stdev+mean is used, then 1*stdev+mean, and finally, the
+  eye separation that produces the minimum average distance between
+  matched pixels.  A return value of zero indicates that no eye
+  separation could be determined.
+------------------------------------------------------------------------*/
+    int              bestSeparation[3];
+        /* Eye separation corresponding to spikes of N+1 standard deviations */
+    int              hShift;
+        /* Current horizontal shift */
+    double           sumMatches;
+        /* Sum of all matches seen so far */
+    double           sumSqMatches;
+        /* Sum of the squares of all matches seen so far */
+    double           meanMatches;
+        /* Mean of all matches seen so far */
+    double           stdMatches;
+        /* Standard deviation of all matches seen so far */
+    double           minAvgDist;
+        /* Min. average distance between matches */
+    int              altBestSeparation;
+        /* Shift distance corresponding to the above */
+    unsigned int     i;
+
+    /* Try in turn each horizontal shift value from 1 to width/2.  A
+       shift of 0 is defined to be a perfect match.  A shift of more
+       than width/2 implies that the right-eye image is truncated, which
+       is an unnatural way to construct a crosseyed stereogram.
+    */
+    for (i = 0; i < 3; ++i)
+        bestSeparation[i] = 0;
+
+    altBestSeparation = 0;
+    sumMatches = sumSqMatches = 0.0;
+    meanMatches = stdMatches = minAvgDist = width * height;
+
+    for (hShift = 1; hShift <= width/2; ++hShift) {
+        unsigned int row;
+        unsigned long numMatches;      /* Number of matched pixels */
+        double avgDist;                /* Average distance between matches */
+
+        numMatches = 0;  /* initial value */
+
+        /* Tally the number of matches for this shift distance. */
+        for (row = 0; row < height; ++row) {
+            unsigned int col;
+            for (col = 0; col < width - hShift; ++col)
+                if (grayArray[row][col] == grayArray[row][col + hShift])
+                    ++numMatches;
+
+            /* See if the number of matches exceeds the running mean plus N
+               standard deviations.  Also, keep track of the shortest average
+               distance between matches seen so far.
+            */
+            if (hShift > 1) {
+                int i;
+                for (i = 2; i >= 0; --i)
+                    if (bestSeparation[i] == 0 &&
+                        numMatches > meanMatches + (i+1)*stdMatches) {
+                        bestSeparation[i] = hShift;
+                        break;
+                    }
+            }
+            avgDist = (height * (width-hShift)) / (double)numMatches;
+            if (minAvgDist > avgDist) {
+                minAvgDist = avgDist;
+                altBestSeparation = hShift;
+            }
+
+            /* Compute the new mean and standard deviation. */
+            sumMatches   += (double)numMatches;
+            sumSqMatches += (double)numMatches * (double)numMatches;
+            meanMatches = sumMatches / (double)hShift;
+            stdMatches  = sqrt(sumSqMatches/hShift - meanMatches*meanMatches);
+        }
+    }
+
+    return bestEyeSepWeEncountered(bestSeparation, altBestSeparation);
+}
+
+
+
+static int
+compare_ints( const void * const firstP,
+              const void * const secondP ) {
+
+    int const first  = *(int *)firstP;
+    int const second = *(int *)secondP;
+
+    int retval;
+
+    if (first < second)
+        retval = -1;
+    else if (first > second)
+        retval = +1;
+    else
+        retval = 0;
+
+    return retval;
+}
+
+
+
+static int
+findEyeSeparation( struct pam *  const pamP,
+                   gray       ** const grayArray,
+                   int           const minSeparation ) {
+/*----------------------------------------------------------------------
+  Compute the eye separation for each row of the grayscale image.
+  Ignore rows for which the eye separation could not be determined and
+  return the median of the remaining rows, aborting with an error
+  message if there are no remaining rows.  Out of laziness we use
+  qsort() to help find the median; if this turns out to be a
+  performance problem, it should be replaced with a linear-time median
+  finder.
+------------------------------------------------------------------------*/
+    int bestSeparation;      /* Best eye separation found */
+
+    /* First attempt: Find the best eye separation across the image as a
+       whole.  This works well when the image consists of relatively
+       small foreground objects in front of a comparatively large
+       background plane.
+    */
+    bestSeparation =
+        findRegionEyeSeparation( grayArray, pamP->width, pamP->height );
+
+    /* Second attempt: Compute the best eye separation for each row
+       independently and return the median of the best eye
+       separations.
+    */
+    if (bestSeparation < minSeparation) {
+        int * rowSeparation;   /* Per-row best separation distance */
+        unsigned int numValidRows;
+            /* Number of entries in the above (<= #rows) */
+        unsigned int row;
+
+        numValidRows = 0;  /* initial value */
+
+        MALLOCARRAY_NOFAIL( rowSeparation, pamP->height );
+        for (row = 0; row < pamP->height; ++row) {
+            int const sep =
+                findRegionEyeSeparation( &grayArray[row], pamP->width, 1);
+            if (sep >= minSeparation)
+                rowSeparation[numValidRows++] = sep;
+        }
+        if (numValidRows > 0) {
+            qsort( rowSeparation, numValidRows, sizeof(int), compare_ints );
+            bestSeparation = rowSeparation[numValidRows/2];
+        }
+        free( rowSeparation );
+    }
+
+    if (bestSeparation < minSeparation)
+        pm_error("Failed to determine the separation between "
+                 "the left and right views");
+
+    return bestSeparation;
+}
+
+
+
+static void
+writeAnaglyph( FILE *       const ofP,
+               gray **      const grayArray,
+               gray         const maxGrayVal,
+               int          const eyeSep,
+               int          const swapEyes,
+               struct pam * const pamP) {
+/*----------------------------------------------------------------------
+  Output an anaglyphic stereogram from the given grayscale array and
+  eye-separation value.
+------------------------------------------------------------------------*/
+    struct pam   outPam;
+    tuple      * tuplerow;
+
+    outPam.size        = sizeof(struct pam);
+    outPam.len         = PAM_STRUCT_SIZE(tuple_type);
+    outPam.file        = ofP;
+    outPam.format      = PAM_FORMAT;
+    outPam.plainformat = 0;
+    outPam.height      = pamP->height;
+    outPam.width       = pamP->width - eyeSep;
+        /* Avoid color bands on the left/right edges. */
+    outPam.depth       = 3;
+    outPam.maxval      = (sample) maxGrayVal;
+    strcpy(outPam.tuple_type, PAM_PPM_TUPLETYPE);
+
+    pnm_writepaminit( &outPam );
+
+    tuplerow = pnm_allocpamrow( &outPam );
+
+    if (swapEyes) {
+        unsigned int row;
+
+        for (row = 0; row < outPam.height; ++row) {
+            unsigned int col;
+            for (col = 0; col < outPam.width; ++col) {
+                tuplerow[col][PAM_RED_PLANE] = grayArray[row][col+eyeSep];
+                tuplerow[col][PAM_GRN_PLANE] = grayArray[row][col];
+                tuplerow[col][PAM_BLU_PLANE] = grayArray[row][col];
+            }
+            pnm_writepamrow( &outPam, tuplerow );
+        }
+    } else {
+        unsigned int row;
+        for (row = 0; row < outPam.height; ++row) {
+            unsigned int col;
+            for (col = 0; col < outPam.width; ++col) {
+                tuplerow[col][PAM_RED_PLANE] = grayArray[row][col];
+                tuplerow[col][PAM_GRN_PLANE] = grayArray[row][col+eyeSep];
+                tuplerow[col][PAM_BLU_PLANE] = grayArray[row][col+eyeSep];
+            }
+            pnm_writepamrow( &outPam, tuplerow );
+        }
+    }
+    pnm_freepamrow( tuplerow );
+}
+
+
+
+int
+main(int argc, const char *argv[]) {
+    struct pam            inPam;
+    gray               ** inImage;
+    int                   eyeSep;
+    struct cmdlineInfo    cmdline;
+
+    pm_proginit( &argc, argv );
+    parseCommandLine( argc, argv, &cmdline );
+
+    inImage = readAsGray( cmdline.inputFilename, cmdline.maxGrayVal, &inPam );
+
+    if (cmdline.separation >= 0)
+        eyeSep = cmdline.separation;
+    else {
+        int const minSeparation =
+            cmdline.minSeparation > 0
+            ? cmdline.minSeparation : inPam.width / 10;
+
+            /* Minimum separation in pixels between eyes.
+               Heuristic: Eye separation must be at least 10% of image width.
+            */
+        eyeSep = findEyeSeparation ( &inPam, inImage, minSeparation );
+    }
+
+    pm_message( "Separation between left/right views = %d pixels", eyeSep );
+
+    writeAnaglyph ( stdout, inImage, cmdline.maxGrayVal,
+                    eyeSep, cmdline.swapEyes,
+                    &inPam );
+
+    return 0;
+}
+
diff --git a/editor/pamstretch-gen b/editor/pamstretch-gen
index cd59a36b..ba0e8188 100755
--- a/editor/pamstretch-gen
+++ b/editor/pamstretch-gen
@@ -32,12 +32,12 @@ if [ "$1" = "" ]; then
 fi
 
 tempdir="${TMPDIR-/tmp}/pamstretch-gen.$$"
-mkdir $tempdir || { echo "Could not create temporary file. Exiting."; exit 1;}
-chmod 700 $tempdir
-tempfile=$tempdir/pnmig
-
+mkdir -m 0700 $tempdir || \
+  { echo "Could not create temporary file. Exiting."; exit 1;}
 trap 'rm -rf $tempdir' 0 1 3 15
 
+tempfile=$tempdir/pnmig
+
 if ! cat $2 >$tempfile 2>/dev/null; then
   echo 'pamstretch-gen: error reading file' 1>&2
   exit 1
diff --git a/editor/pamstretch.c b/editor/pamstretch.c
index 0e9e6abf..87c105f9 100644
--- a/editor/pamstretch.c
+++ b/editor/pamstretch.c
@@ -24,6 +24,8 @@
 #include <string.h>
 #include <stdlib.h>
 #include <ctype.h>
+
+#include "pm_c_util.h"
 #include "pam.h"
 #include "shhopt.h"
 
diff --git a/editor/pamthreshold.c b/editor/pamthreshold.c
index a5635790..c5f48147 100644
--- a/editor/pamthreshold.c
+++ b/editor/pamthreshold.c
@@ -23,6 +23,7 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include "pm_c_util.h"
 #include "mallocvar.h"
 #include "nstring.h"
 #include "shhopt.h"
@@ -44,10 +45,18 @@ struct cmdlineInfo {
         /* geometry of local subimage.  Defined only if 'local' or 'dual'
            is true.
         */
+    unsigned int verbose;
 };
 
 
 
+static __inline__ bool
+betweenZeroAndOne(float const arg) {
+    return (arg >= 0.0 && arg <= 1.0);
+}
+
+
+
 struct range {
     /* A range of sample values, normalized to [0, 1] */
     samplen min;
@@ -146,6 +155,8 @@ parseCommandLine(int                 argc,
             &thresholdSpec,         0);
     OPTENT3(0, "contrast",  OPT_FLOAT,  &cmdlineP->contrast,
             &contrastSpec,          0);
+    OPTENT3(0, "verbose",    OPT_FLAG,   NULL,               
+            &cmdlineP->verbose,     0);
 
     /* set the defaults */
     cmdlineP->width = cmdlineP->height = 0U;
@@ -212,6 +223,21 @@ parseCommandLine(int                 argc,
 
 
 
+static void
+thresholdPixel(struct pam * const outpamP,
+               tuplen       const inTuplen,
+               tuple        const outTuple,
+               float        const threshold) {
+
+    outTuple[0] = inTuplen[0] >= threshold ? PAM_BW_WHITE : PAM_BLACK;
+    if (outpamP->depth > 1) {
+        /* Do alpha */
+        outTuple[1] = inTuplen[1] > 0.5 ? 1 : 0;
+    }
+}
+
+
+
 /* simple thresholding (the same as in pamditherbw) */
 
 static void
@@ -230,9 +256,9 @@ thresholdSimple(struct pam * const inpamP,
     for (row = 0; row < inpamP->height; ++row) {
         unsigned int col;
         pnm_readpamrown(inpamP, inrow);
-        for (col = 0; col < inpamP->width; ++col)
-            outrow[col][0] =
-                inrow[col][0] >= threshold ? PAM_BW_WHITE : PAM_BLACK;
+        for (col = 0; col < inpamP->width; ++col) {
+            thresholdPixel(outpamP, inrow[col], outrow[col], threshold);
+        }
         pnm_writepamrow(outpamP, outrow);
     }
 
@@ -244,6 +270,7 @@ thresholdSimple(struct pam * const inpamP,
 
 static void
 analyzeDistribution(struct pam *          const inpamP,
+                    bool                  const verbose,
                     const unsigned int ** const histogramP,
                     struct range *        const rangeP) {
 /*----------------------------------------------------------------------------
@@ -255,7 +282,8 @@ analyzeDistribution(struct pam *          const inpamP,
    distribution as *histogramP, an array such that histogram[i] is the
    number of pixels that have sample value i.
 
-   Leave the file positioned to the raster.
+   Assume the file is positioned to the raster upon entry and leave
+   it positioned at the same place.
 -----------------------------------------------------------------------------*/
     unsigned int row;
     tuple * inrow;
@@ -295,6 +323,10 @@ analyzeDistribution(struct pam *          const inpamP,
     pnm_freepamrown(inrown);
 
     pm_seek2(inpamP->file, &rasterPos, sizeof(rasterPos));
+
+    if (verbose)
+        pm_message("Pixel values range from %f to %f",
+                   rangeP->min, rangeP->max);
 }
 
 
@@ -342,9 +374,7 @@ computeGlobalThreshold(struct pam *         const inpamP,
                        float *              const thresholdP) {
 /*----------------------------------------------------------------------------
    Compute the proper threshold to use for the image described by
-   *inpamP, whose file is positioned to the raster.
-
-   For our convenience:
+   *inpamP, and:
 
      'histogram' describes the frequency of occurence of the various sample
      values in the image.
@@ -475,6 +505,7 @@ thresholdLocalRow(struct pam *       const inpamP,
                   struct cmdlineInfo const cmdline,
                   struct range       const globalRange,
                   samplen            const globalThreshold,
+                  struct pam *       const outpamP,
                   tuple *            const outrow) {
 
     tuplen * const inrow = inrows[row % windowHeight];
@@ -494,7 +525,7 @@ thresholdLocalRow(struct pam *       const inpamP,
                           cmdline.threshold, minSpread, globalThreshold,
                           &threshold);
         
-        outrow[col][0] = inrow[col][0] >= threshold ? PAM_BW_WHITE : PAM_BLACK;
+        thresholdPixel(outpamP, inrow[col], outrow[col], threshold);
     }
 }
 
@@ -552,9 +583,16 @@ thresholdLocal(struct pam *       const inpamP,
 
     windowHeight = MIN(oddLocalHeight, inpamP->height);
 
-    analyzeDistribution(inpamP, &histogram, &globalRange);
-
-    computeGlobalThreshold(inpamP, histogram, globalRange, &globalThreshold);
+    /* global information is needed for dual thresholding */
+    if (cmdline.dual) {
+        analyzeDistribution(inpamP, cmdline.verbose, &histogram, &globalRange);
+        computeGlobalThreshold(inpamP, histogram, globalRange,
+                               &globalThreshold);
+    } else {
+        histogram = NULL;
+        initRange(&globalRange);
+        globalThreshold = 1.0;
+    }
 
     outrow = pnm_allocpamrow(outpamP);
 
@@ -574,7 +612,8 @@ thresholdLocal(struct pam *       const inpamP,
 
     for (row = 0; row < inpamP->height; ++row) {
         thresholdLocalRow(inpamP, inrows, oddLocalWidth, windowHeight, row,
-                          cmdline, globalRange, globalThreshold, outrow);
+                          cmdline, globalRange, globalThreshold,
+                          outpamP, outrow);
 
         pnm_writepamrow(outpamP, outrow);
         
@@ -595,13 +634,14 @@ thresholdLocal(struct pam *       const inpamP,
 
 static void
 thresholdIterative(struct pam * const inpamP,
-                   struct pam * const outpamP) {
+                   struct pam * const outpamP,
+                   bool         const verbose) {
 
     const unsigned int * histogram;
     struct range globalRange;
     samplen threshold;
 
-    analyzeDistribution(inpamP, &histogram, &globalRange);
+    analyzeDistribution(inpamP, verbose, &histogram, &globalRange);
 
     computeGlobalThreshold(inpamP, histogram, globalRange, &threshold);
 
@@ -624,17 +664,17 @@ main(int argc, char **argv) {
 
     parseCommandLine(argc, argv, &cmdline);
 
-    if (cmdline.simple)
+    if (cmdline.simple || cmdline.local)
         ifP = pm_openr(cmdline.inputFileName);
     else
         ifP = pm_openr_seekable(cmdline.inputFileName);
 
-    /* threshold each image in the PAM file */
+    /* Threshold each image in the PAM file */
     eof = FALSE;
     while (!eof) {
         pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
 
-        /* set output image parameters for a bilevel image */
+        /* Set output image parameters for a bilevel image */
         outpam.size        = sizeof(outpam);
         outpam.len         = PAM_STRUCT_SIZE(tuple_type);
         outpam.file        = stdout;
@@ -642,21 +682,27 @@ main(int argc, char **argv) {
         outpam.plainformat = 0;
         outpam.height      = inpam.height;
         outpam.width       = inpam.width;
-        outpam.depth       = 1;
         outpam.maxval      = 1;
         outpam.bytes_per_sample = 1;
-        strcpy(outpam.tuple_type, "BLACKANDWHITE");
+
+        if (inpam.depth > 1) {
+            strcpy(outpam.tuple_type, "BLACKANDWHITE_ALPHA");
+            outpam.depth = 2;
+        } else {
+            strcpy(outpam.tuple_type, "BLACKANDWHITE");
+            outpam.depth = 1;
+        }
 
         pnm_writepaminit(&outpam);
 
-        /* do the thresholding */
+        /* Do the thresholding */
 
         if (cmdline.simple)
             thresholdSimple(&inpam, &outpam, cmdline.threshold);
         else if (cmdline.local || cmdline.dual)
             thresholdLocal(&inpam, &outpam, cmdline);
         else
-            thresholdIterative(&inpam, &outpam);
+            thresholdIterative(&inpam, &outpam, cmdline.verbose);
 
         pnm_nextimage(ifP, &eof);
     }
diff --git a/editor/pamundice.c b/editor/pamundice.c
new file mode 100644
index 00000000..89d8b6b5
--- /dev/null
+++ b/editor/pamundice.c
@@ -0,0 +1,702 @@
+/*****************************************************************************
+                                  pamundice
+******************************************************************************
+  Assemble a grid of images into one.
+
+  By Bryan Henderson, San Jose CA 2001.01.31
+
+  Contributed to the public domain.
+
+******************************************************************************/
+
+#include <assert.h>
+#include <string.h>
+
+#include "pm_c_util.h"
+#include "pam.h"
+#include "shhopt.h"
+#include "nstring.h"
+#include "mallocvar.h"
+
+#define MAXFILENAMELEN 80
+    /* Maximum number of characters we accept in filenames */
+
+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 verbose;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP ) {
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   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 optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+    
+    unsigned int acrossSpec, downSpec;
+    unsigned int hoverlapSpec, voverlapSpec;
+    unsigned int option_def_index;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "across",      OPT_UINT,    &cmdlineP->across,
+            &acrossSpec,                      0);
+    OPTENT3(0, "down",        OPT_UINT,    &cmdlineP->down,
+            &downSpec,                        0);
+    OPTENT3(0, "hoverlap",    OPT_UINT,    &cmdlineP->hoverlap,
+            &hoverlapSpec,                    0);
+    OPTENT3(0, "voverlap",    OPT_UINT,    &cmdlineP->voverlap,
+            &voverlapSpec,                    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 */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0 );
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (!acrossSpec)
+        cmdlineP->across = 1;
+    
+    if (!downSpec)
+        cmdlineP->down = 1;
+
+    if (!hoverlapSpec)
+        cmdlineP->hoverlap = 0;
+
+    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);
+    }
+}
+
+
+
+/*------------------ string buffer -----------------------------------*/
+struct buffer {
+    char * string;
+    unsigned int allocSize;
+    unsigned int length;
+};
+
+
+static void
+buffer_init(struct buffer * const bufferP) {
+
+    bufferP->length = 0;
+    bufferP->allocSize = 1024;
+    MALLOCARRAY(bufferP->string, bufferP->allocSize);
+
+    if (bufferP->string == NULL)
+        pm_error("Out of memory allocating buffer to compute file name");
+}
+
+
+
+static void
+buffer_term(struct buffer * const bufferP) {
+    
+    free(bufferP->string);
+}
+
+
+
+static void
+buffer_addChar(struct buffer * const bufferP,
+               char            const newChar) {
+
+    if (bufferP->length + 1 + 1 > bufferP->allocSize)
+        pm_error("Ridiculously long input file name.");
+    else {
+        bufferP->string[bufferP->length++] = newChar;
+        bufferP->string[bufferP->length] = '\0';
+    }
+}
+
+
+
+static void
+buffer_addString(struct buffer * const bufferP,
+                 const char *    const newString) {
+
+    if (bufferP->length + 1 + strlen(newString) > bufferP->allocSize)
+        pm_error("Ridiculously long input file name.");
+    else {
+        strcat(&bufferP->string[bufferP->length], newString);
+        bufferP->length += strlen(newString);
+    }
+}
+/*------------------ end of string buffer ----------------------------*/
+
+
+
+/*------------------ computeInputFileName ----------------------------*/
+static unsigned int
+digitValue(char const digitChar) {
+
+    return digitChar - '0';
+}
+
+
+
+static void
+getPrecision(const char *   const pattern,
+             unsigned int   const startInCursor,
+             unsigned int * const precisionP,
+             unsigned int * const newInCursorP) {
+
+    unsigned int precision;
+    unsigned int inCursor;
+
+    inCursor = startInCursor;  /* Start right after the '%' */
+
+    precision = 0;
+                
+    while (isdigit(pattern[inCursor])) {
+        precision = 10 * precision + digitValue(pattern[inCursor]);
+        ++inCursor;
+    }
+
+    if (precision == 0)
+        pm_error("Zero (or no) precision in substitution "
+                 "specification in file name pattern '%s'.  "
+                 "A proper substitution specification is like "
+                 "'%%3a'.", pattern);
+
+    *precisionP = precision;
+    *newInCursorP = inCursor;
+}
+
+
+
+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 inCursor;
+
+    inCursor = startInCursor;  /* Start right after the '%' */
+
+    if (pattern[inCursor] == '%') {
+        buffer_addChar(bufferP, '%');
+        ++inCursor;
+    } else {
+        unsigned int precision;
+        
+        getPrecision(pattern, inCursor, &precision, &inCursor);
+
+        if (pattern[inCursor] == '\0')
+            pm_error("No format character follows '%%' in input "
+                     "file name pattern '%s'.  A proper substitution "
+                     "specification is like '%%3a'", pattern);
+        else {
+            const char * substString;
+            const char * desc;
+
+            switch (pattern[inCursor]) {
+            case 'a':
+                asprintfN(&substString, "%0*u", precision, file);
+                asprintfN(&desc, "file (across)");
+                break;
+            case 'd':
+                asprintfN(&substString, "%0*u", precision, rank);
+                asprintfN(&desc, "rank (down)");
+                break;
+            default:
+                pm_error("Unknown format specifier '%c' in input file "
+                         "pattern '%s'.  Recognized format specifier s are "
+                         "'%%a' (across) and '%%d (down)'",
+                         pattern[inCursor], pattern);
+            }
+            if (strlen(substString) > precision)
+                pm_error("%s number %u is wider than "
+                         "the %u characters specified in the "
+                         "input file pattern",
+                         desc, strlen(substString), precision);
+            else
+                buffer_addString(bufferP, substString);
+            
+            strfree(desc);
+            strfree(substString);
+
+            ++inCursor;
+        }
+    }
+    *newInCursorP = inCursor;
+}
+
+
+
+static void
+computeInputFileName(const char *  const pattern,
+                     unsigned int  const rank,
+                     unsigned int  const file,
+                     const char ** const fileNameP) {
+
+    struct buffer buffer;
+    unsigned int inCursor, outCursor;
+
+    buffer_init(&buffer);
+
+    inCursor = 0;
+    outCursor = 0;
+
+    while (pattern[inCursor] != '\0') {
+        if (pattern[inCursor] == '%') {
+            ++inCursor;
+
+            doSubstitution(pattern, inCursor, rank, file, &buffer, &inCursor);
+
+        } else
+            buffer_addChar(&buffer, pattern[inCursor++]);
+    }
+
+    asprintfN(fileNameP, "%s", buffer.string);
+
+    buffer_term(&buffer);
+}
+/*------------------ end of computeInputFileName ------------------------*/
+
+
+
+static void
+getCommonInfo(const char *   const inputFilePattern,
+              int *          const formatP,
+              unsigned int * const depthP,
+              sample *       const maxvalP,
+              char *         const tupleType) {
+/*----------------------------------------------------------------------------
+   Get from the top left input image all the information which is common
+   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);
+
+    pnm_readpaminit(ifP, &inpam00, PAM_STRUCT_SIZE(tuple_type));
+
+    *formatP = inpam00.format;
+    *depthP  = inpam00.depth;
+    *maxvalP = inpam00.maxval;
+    strcpy(tupleType, inpam00.tuple_type);
+
+    pm_close(ifP);
+
+    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);
+    
+    strfree(fileName);
+
+    return retval;
+}
+
+               
+
+static void
+getImageInfo(const char * const inputFilePattern,
+             unsigned int const rank,
+             unsigned int const file,
+             struct pam * const pamP) {
+
+    FILE * ifP;
+
+    ifP = openInputImage(inputFilePattern, rank, file);
+
+    pnm_readpaminit(ifP, pamP, PAM_STRUCT_SIZE(tuple_type));
+
+    pm_close(ifP);
+    pamP->file = NULL;  /* for robustness */
+}
+
+
+
+static void
+getOutputWidth(const char * const inputFilePattern,
+               unsigned int const nFile,
+               unsigned int const hoverlap,
+               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.
+-----------------------------------------------------------------------------*/
+    unsigned int totalWidth;
+    unsigned int file;
+
+    for (file = 0, totalWidth = 0; file < nFile; ++file) {
+        struct pam inpam;
+
+        getImageInfo(inputFilePattern, 0, file, &inpam);
+
+        if (inpam.width < 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);
+        else {
+            totalWidth += inpam.width;
+
+            if (file < nFile-1)
+                totalWidth -= hoverlap;
+        }
+    }
+    *widthP = totalWidth;
+}
+
+
+
+static void
+getOutputHeight(const char *  const inputFilePattern,
+                unsigned int  const nRank,
+                unsigned int  const voverlap,
+                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.
+-----------------------------------------------------------------------------*/
+    unsigned int totalHeight;
+    unsigned int rank;
+
+    for (rank = 0, totalHeight = 0; rank < nRank; ++rank) {
+        struct pam inpam;
+
+        getImageInfo(inputFilePattern, rank, 0, &inpam);
+
+        if (inpam.height < 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);
+        
+        totalHeight += inpam.height;
+        
+        if (rank < nRank-1)
+            totalHeight -= voverlap;
+    }
+    *heightP = totalHeight;
+}
+
+
+
+static void
+initOutpam(const char * const inputFilePattern,
+           unsigned int const nFile,
+           unsigned int const nRank,
+           unsigned int const hoverlap,
+           unsigned int const voverlap,
+           FILE *       const ofP,
+           bool         const verbose,
+           struct pam * const outpamP) {
+/*----------------------------------------------------------------------------
+   Figure out the attributes of the output image and return them as
+   *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'.
+
+   In computing dimensions, assume 'hoverlap' pixels of horizontal
+   overlap and '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
+   the top rank have different heights, we use just one of them.
+
+   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);
+
+    outpamP->size        = sizeof(*outpamP);
+    outpamP->len         = PAM_STRUCT_SIZE(tuple_type);
+    outpamP->file        = ofP;
+    outpamP->plainformat = 0;
+    
+    getCommonInfo(inputFilePattern, &outpamP->format, &outpamP->depth,
+                  &outpamP->maxval, outpamP->tuple_type);
+
+    getOutputWidth(inputFilePattern, nFile, hoverlap, &outpamP->width);
+
+    getOutputHeight(inputFilePattern, nRank, voverlap, &outpamP->height);
+
+    if (verbose) {
+        pm_message("Output width = %u pixels", outpamP->width);
+        pm_message("Output height = %u pixels", outpamP->height);
+    }
+}
+
+
+
+static void
+openInStreams(struct pam         inpam[],
+              unsigned int const rank,
+              unsigned int const fileCount,
+              char         const inputFilePattern[]) {
+/*----------------------------------------------------------------------------
+   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.
+-----------------------------------------------------------------------------*/
+    unsigned int file;
+
+    for (file = 0; file < fileCount; ++file) {
+        FILE * const ifP = openInputImage(inputFilePattern, rank, file);
+
+        pnm_readpaminit(ifP, &inpam[file], PAM_STRUCT_SIZE(tuple_type));
+    }        
+}
+
+
+
+static void
+closeInFiles(struct pam         pam[],
+             unsigned int const fileCount) {
+
+    unsigned int file;
+    
+    for (file = 0; file < fileCount; ++file)
+        pm_close(pam[file].file);
+}
+
+
+
+static void
+assembleRow(tuple              outputRow[], 
+            struct pam         inpam[], 
+            unsigned int const fileCount,
+            unsigned int const hOverlap) {
+/*----------------------------------------------------------------------------
+   Assemble the row outputRow[] from the 'fileCount' input files
+   described out inpam[].
+
+   'hOverlap', which is meaningful only when fileCount is greater than 1,
+   is the amount by which files overlap each other.  We assume every
+   input image is at least that wide.
+
+   We assume that outputRow[] is allocated wide enough to contain the
+   entire assembly.
+-----------------------------------------------------------------------------*/
+    tuple * inputRow;
+    unsigned int file;
+
+    for (file = 0, inputRow = &outputRow[0]; 
+         file < fileCount; 
+         ++file) {
+
+        unsigned int const overlap = file == fileCount - 1 ? 0 : hOverlap;
+
+        assert(hOverlap <= inpam[file].width);
+
+        pnm_readpamrow(&inpam[file], inputRow);
+
+        inputRow += inpam[file].width - overlap;
+    }
+}
+
+
+
+static void
+allocInpam(unsigned int  const rankCount,
+           struct pam ** const inpamArrayP) {
+
+    struct pam * inpamArray;
+
+    MALLOCARRAY(inpamArray, rankCount);
+
+    if (inpamArray == NULL)
+        pm_error("Unable to allocate array for %u input pam structures.",
+                 rankCount);
+
+    *inpamArrayP = inpamArray;
+}
+
+
+
+static void
+verifyRankFileAttributes(struct pam *       const inpam,
+                         unsigned int       const nFile,
+                         const struct pam * const outpamP,
+                         unsigned int       const hoverlap,
+                         unsigned int       const rank) {
+/*----------------------------------------------------------------------------
+   Verify that the 'nFile' images that make up a rank, which are described
+   by inpam[], are consistent with the properties of the assembled image
+   *outpamP.
+
+   I.e. verify that each image has the depth, maxval, format, and tuple
+   type of *outpamP and their total width is the width given by
+   *outpamP.
+
+   Also verify that every image has the same height.
+
+   Abort the program if verification fails.
+-----------------------------------------------------------------------------*/
+    unsigned int file;
+    unsigned int totalWidth;
+
+    for (file = 0, totalWidth = 0; file < nFile; ++file) {
+        struct pam * const inpamP = &inpam[file];
+
+        if (inpamP->depth != outpamP->depth)
+            pm_error("Rank %u, File %u image has depth %u, "
+                     "which differs from others (%u)",
+                     rank, file, inpamP->depth, outpamP->depth);
+        else if (inpamP->maxval != outpamP->maxval)
+            pm_error("Rank %u, File %u image has maxval %lu, "
+                     "which differs from others (%lu)",
+                     rank, file, inpamP->maxval, outpamP->maxval);
+        else if (inpamP->format != outpamP->format)
+            pm_error("Rank %u, File %u image has format 0x%x, "
+                     "which differs from others (0x%x)",
+                     rank, file, inpamP->format, outpamP->format);
+        else if (!streq(inpamP->tuple_type, outpamP->tuple_type))
+            pm_error("Rank %u, File %u image has tuple type '%s', "
+                     "which differs from others ('%s')",
+                     rank, file, inpamP->tuple_type, outpamP->tuple_type);
+
+        else if (inpamP->height != inpam[0].height)
+            pm_error("Rank %u, File %u image has height %u, "
+                     "which differs from that of File 0 in the same rank (%u)",
+                     rank, file, inpamP->height, inpam[0].height);
+        else {
+            totalWidth += inpamP->width;
+        
+            if (file < nFile-1)
+                totalWidth -= hoverlap;
+        }
+    }
+
+    if (totalWidth != outpamP->width)
+        pm_error("Rank %u has a total width (%u) different from that of "
+                 "other ranks (%u)", rank, totalWidth, outpamP->width);
+}
+
+
+
+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,
+              struct pam         inpam[],
+              tuple *      const tuplerow) {
+
+    unsigned int rank;
+        /* Number of the current rank (horizontal slice).  Ranks are numbered
+           sequentially starting at 0.
+        */
+    
+    for (rank = 0; rank < nRank; ++rank) {
+        unsigned int row;
+        unsigned int rankHeight;
+
+        openInStreams(inpam, rank, nFile, inputFilePattern);
+
+        verifyRankFileAttributes(inpam, nFile, outpamP, hoverlap, rank);
+
+        rankHeight = inpam[0].height - (rank == nRank-1 ? 0 : voverlap);
+
+        for (row = 0; row < rankHeight; ++row) {
+            assembleRow(tuplerow, inpam, nFile, hoverlap);
+
+            pnm_writepamrow(outpamP, tuplerow);
+        }
+        closeInFiles(inpam, nFile);
+    }
+}
+
+
+
+int
+main(int argc, char ** argv) {
+
+    struct cmdlineInfo cmdline;
+    struct pam outpam;
+    struct pam * inpam;
+        /* malloc'ed.  inpam[x] is the pam structure that controls the
+           current rank of file x. 
+        */
+    tuple * tuplerow;
+
+    pnm_init(&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);
+    
+    tuplerow = pnm_allocpamrow(&outpam);
+
+    pnm_writepaminit(&outpam);
+
+    assembleTiles(&outpam,
+                  cmdline.inputFilePattern, cmdline.across, cmdline.down,
+                  cmdline.hoverlap, cmdline.voverlap, inpam, tuplerow);
+
+    pnm_freepamrow(tuplerow);
+
+    free(inpam);
+
+    return 0;
+}
diff --git a/editor/pbmclean.c b/editor/pbmclean.c
index 3ae3acfc..65b53e1c 100644
--- a/editor/pbmclean.c
+++ b/editor/pbmclean.c
@@ -6,6 +6,8 @@
  */
 
 #include <stdio.h>
+
+#include "pm_c_util.h"
 #include "pbm.h"
 #include "shhopt.h"
 
diff --git a/editor/pbmpscale.c b/editor/pbmpscale.c
index 63f203ed..2e24f3cd 100644
--- a/editor/pbmpscale.c
+++ b/editor/pbmpscale.c
@@ -81,119 +81,125 @@ void nextrow_pscale(ifd, row)
 
 }
 
+
+
 int
-main(argc, argv)
-     int argc;
-     char *argv[];
-{
-   FILE *ifd;
-   register bit *outrow;
-   register int row, col, i, k;
-   int scale, cutoff, ucutoff ;
-   unsigned char *flags;
-
-   pbm_init( &argc, argv );
-
-   if (argc < 2)
-      pm_usage("scale [pbmfile]");
-
-   scale = atoi(argv[1]);
-   if (scale < 1)
-      pm_perror("bad scale (< 1)");
-
-   if (argc == 3)
-      ifd = pm_openr(argv[2]);
-   else
-      ifd = stdin ;
-
-   inrow[0] = inrow[1] = inrow[2] = NULL;
-   pbm_readpbminit(ifd, &columns, &rows, &format) ;
-
-   outrow = pbm_allocrow(columns*scale) ;
-   MALLOCARRAY(flags, columns);
-   if (flags == NULL) 
-       pm_error("out of memory") ;
-
-   pbm_writepbminit(stdout, columns*scale, rows*scale, 0) ;
-
-   cutoff = scale / 2;
-   ucutoff = scale - 1 - cutoff;
-   nextrow_pscale(ifd, 0);
-   for (row = 0; row < rows; row++) {
-      nextrow_pscale(ifd, row+1);
-      for (col = 0; col < columns; col++) {
-         flags[col] = 0 ;
-         for (i = 0; i != 8; i += 2) {
-            int vec = inrow[thisrow][col] != PBM_WHITE;
-            for (k = 0; k < 7; k++) {
-               int x = col + xd_pscale[(k+i)&7] ;
-               int y = thisrow + yd_pscale[(k+i)&7] ;
-               vec <<= 1;
-               if (x >=0 && x < columns && inrow[y])
-                  vec |= (inrow[y][x] != PBM_WHITE) ;
+main(int argc, char ** argv) {
+
+    FILE * ifP;
+    bit * outrow;
+    unsigned int row;
+    int scale, cutoff, ucutoff ;
+    unsigned char *flags;
+
+    pbm_init( &argc, argv );
+
+    if (argc < 2)
+        pm_usage("scale [pbmfile]");
+
+    scale = atoi(argv[1]);
+    if (scale < 1)
+        pm_error("Scale argument must be at least one.  You specified '%s'",
+                 argv[1]);
+
+    if (argc == 3)
+        ifP = pm_openr(argv[2]);
+    else
+        ifP = stdin ;
+
+    inrow[0] = inrow[1] = inrow[2] = NULL;
+    pbm_readpbminit(ifP, &columns, &rows, &format) ;
+
+    outrow = pbm_allocrow(columns*scale) ;
+    MALLOCARRAY(flags, columns);
+    if (flags == NULL) 
+        pm_error("out of memory") ;
+
+    pbm_writepbminit(stdout, columns*scale, rows*scale, 0) ;
+
+    cutoff = scale / 2;
+    ucutoff = scale - 1 - cutoff;
+    nextrow_pscale(ifP, 0);
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        unsigned int i;
+        nextrow_pscale(ifP, row+1);
+        for (col = 0; col < columns; ++col) {
+            unsigned int i;
+            flags[col] = 0 ;
+            for (i = 0; i != 8; i += 2) {
+                int vec = inrow[thisrow][col] != PBM_WHITE;
+                unsigned int k;
+                for (k = 0; k < 7; ++k) {
+                    int x = col + xd_pscale[(k+i)&7] ;
+                    int y = thisrow + yd_pscale[(k+i)&7] ;
+                    vec <<= 1;
+                    if (x >=0 && x < columns && inrow[y])
+                        vec |= (inrow[y][x] != PBM_WHITE) ;
+                }
+                flags[col] |= corner(vec)<<i ;
             }
-            flags[col] |= corner(vec)<<i ;
-         }
-      }
-      for (i = 0; i < scale; i++) {
-         bit *ptr = outrow ;
-         int zone = (i > ucutoff) - (i < cutoff) ;
-         int cut = (zone < 0) ? (cutoff - i) :
-                   (zone > 0) ? (i - ucutoff) : 0 ;
-
-         for (col = 0; col < columns; col++) {
-            int pix = inrow[thisrow][col] ;
-            int flag = flags[col] ;
-            int cutl, cutr ;
-
-            switch (zone) {
-            case -1:
-               switch (NW(flag)) {
-               case 0: cutl = 0; break;
-               case 1: cutl = cut; break;
-               case 2: cutl = cut ? cut-1 : 0; break;
-               case 3: cutl = (cut && cutoff > 1) ? cut-1 : cut; break;
-               default: cutl = 0;  /* Should never reach here */
-               }
-               switch (NE(flag)) {
-               case 0: cutr = 0; break;
-               case 1: cutr = cut; break;
-               case 2: cutr = cut ? cut-1 : 0; break;
-               case 3: cutr = (cut && cutoff > 1) ? cut-1 : cut; break;
-               default: cutr = 0;  /* Should never reach here */
-              }
-               break;
-            case 0:
-               cutl = cutr = 0;
-               break ;
-            case 1:
-               switch (SW(flag)) {
-               case 0: cutl = 0; break;
-               case 1: cutl = cut; break;
-               case 2: cutl = cut ? cut-1 : 0; break;
-               case 3: cutl = (cut && cutoff > 1) ? cut-1 : cut; break;
-               default: cutl = 0;  /* should never reach here */
-               }
-               switch (SE(flag)) {
-               case 0: cutr = 0; break;
-               case 1: cutr = cut; break;
-               case 2: cutr = cut ? cut-1 : 0; break;
-               case 3: cutr = (cut && cutoff > 1) ? cut-1 : cut; break;
-               default: cutr = 0;  /* should never reach here */
-               }
-               break;
-             default: cutl = 0; cutr = 0;  /* Should never reach here */
+        }
+        for (i = 0; i < scale; i++) {
+            bit *ptr = outrow ;
+            int zone = (i > ucutoff) - (i < cutoff) ;
+            int cut = (zone < 0) ? (cutoff - i) :
+                (zone > 0) ? (i - ucutoff) : 0 ;
+
+            for (col = 0; col < columns; ++col) {
+                int pix = inrow[thisrow][col] ;
+                int flag = flags[col] ;
+                int cutl, cutr;
+                unsigned int k;
+
+                switch (zone) {
+                case -1:
+                    switch (NW(flag)) {
+                    case 0: cutl = 0; break;
+                    case 1: cutl = cut; break;
+                    case 2: cutl = cut ? cut-1 : 0; break;
+                    case 3: cutl = (cut && cutoff > 1) ? cut-1 : cut; break;
+                    default: cutl = 0;  /* Should never reach here */
+                    }
+                    switch (NE(flag)) {
+                    case 0: cutr = 0; break;
+                    case 1: cutr = cut; break;
+                    case 2: cutr = cut ? cut-1 : 0; break;
+                    case 3: cutr = (cut && cutoff > 1) ? cut-1 : cut; break;
+                    default: cutr = 0;  /* Should never reach here */
+                    }
+                    break;
+                case 0:
+                    cutl = cutr = 0;
+                    break ;
+                case 1:
+                    switch (SW(flag)) {
+                    case 0: cutl = 0; break;
+                    case 1: cutl = cut; break;
+                    case 2: cutl = cut ? cut-1 : 0; break;
+                    case 3: cutl = (cut && cutoff > 1) ? cut-1 : cut; break;
+                    default: cutl = 0;  /* should never reach here */
+                    }
+                    switch (SE(flag)) {
+                    case 0: cutr = 0; break;
+                    case 1: cutr = cut; break;
+                    case 2: cutr = cut ? cut-1 : 0; break;
+                    case 3: cutr = (cut && cutoff > 1) ? cut-1 : cut; break;
+                    default: cutr = 0;  /* should never reach here */
+                    }
+                    break;
+                default: cutl = 0; cutr = 0;  /* Should never reach here */
+                }
+                for (k = 0; k < cutl; ++k) /* left part */
+                    *ptr++ = !pix ;
+                for (k = 0; k < scale-cutl-cutr; ++k)  /* center part */
+                    *ptr++ = pix ;
+                for (k = 0; k < cutr; ++k) /* right part */
+                    *ptr++ = !pix ;
             }
-            for (k = 0; k < cutl; k++) /* left part */
-               *ptr++ = !pix ;
-            for (k = 0; k < scale-cutl-cutr; k++)  /* centre part */
-               *ptr++ = pix ;
-            for (k = 0; k < cutr; k++) /* right part */
-               *ptr++ = !pix ;
-         }
-         pbm_writepbmrow(stdout, outrow, scale*columns, 0) ;
-      }
-   }
-   pm_close(ifd);
-   exit(0);
+            pbm_writepbmrow(stdout, outrow, scale*columns, 0) ;
+        }
+    }
+    pm_close(ifP);
+    return 0;
 }
diff --git a/editor/pbmreduce.c b/editor/pbmreduce.c
index 15ec2a1b..f49c8d9a 100644
--- a/editor/pbmreduce.c
+++ b/editor/pbmreduce.c
@@ -91,21 +91,22 @@ main( argc, argv )
     pbm_writepbminit( stdout, newcols, newrows, 0 );
     newbitrow = pbm_allocrow( newcols );
 
-    if ( halftone == QT_FS ) {
+    if (halftone == QT_FS) {
+        unsigned int col;
         /* Initialize Floyd-Steinberg. */
         MALLOCARRAY(thiserr, newcols + 2);
         MALLOCARRAY(nexterr, newcols + 2);
-        if ( thiserr == NULL || nexterr == NULL )
-          pm_error( "out of memory" );
+        if (thiserr == NULL || nexterr == NULL)
+            pm_error("out of memory");
 
-        srand( (int) ( time( 0 ) ^ getpid( ) ) );
-        for ( col = 0; col < newcols + 2; ++col )
-          thiserr[col] = ( rand( ) % SCALE - HALFSCALE ) / 4;
+        srand(pm_randseed());
+        for (col = 0; col < newcols + 2; ++col)
+            thiserr[col] = (rand() % SCALE - HALFSCALE) / 4;
 	    /* (random errors in [-SCALE/8 .. SCALE/8]) */
 	} else {
         /* These variables are meaningless in this case, and the values
            should never be used.
-           */
+        */
         thiserr = NULL;
         nexterr = NULL;
     }
diff --git a/editor/pgmbentley.c b/editor/pgmbentley.c
deleted file mode 100644
index a008ed84..00000000
--- a/editor/pgmbentley.c
+++ /dev/null
@@ -1,68 +0,0 @@
-/* pgmbentley.c - read a portable graymap and smear it according to brightness
-**
-** Copyright (C) 1990 by Wilson Bent (whb@hoh-2.att.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 "pgm.h"
-
-int
-main( argc, argv )
-    int argc;
-    char* argv[];
-    {
-    FILE* ifp;
-    gray maxval;
-    gray** gin;
-    gray** gout;
-    int argn, rows, cols, row;
-    register int brow, col;
-    const char* const usage = "[pgmfile]";
-
-
-    pgm_init( &argc, argv );
-
-    argn = 1;
-
-    if ( argn < argc )
-	{
-	ifp = pm_openr( argv[argn] );
-	++argn;
-	}
-    else
-	ifp = stdin;
-
-    if ( argn != argc )
-	pm_usage( usage );
-
-    gin = pgm_readpgm( ifp, &cols, &rows, &maxval );
-    pm_close( ifp );
-    gout = pgm_allocarray( cols, rows );
-
-#define N 4
-    for (row = 0; row < rows; ++row)
-        for (col = 0; col < cols; ++col)
-            gout[row][col] = 0;
-
-    for ( row = 0; row < rows; ++row )
-	for ( col = 0; col < cols; ++col )
-	    {
-	    brow = row + (int) (gin[row][col]) / N;
-	    if ( brow >= rows )
-		brow = rows - 1;
-	    gout[brow][col] = gin[row][col];
-	    }
-
-    pgm_writepgm( stdout, gout, cols, rows, maxval, 0 );
-    pm_close( stdout );
-    pgm_freearray( gout, rows );
-
-    exit( 0 );
-    }
diff --git a/editor/pgmdeshadow.c b/editor/pgmdeshadow.c
index 05eda944..948c6cba 100644
--- a/editor/pgmdeshadow.c
+++ b/editor/pgmdeshadow.c
@@ -85,7 +85,7 @@ parseCommandLine(int argc, char ** argv,
 
     option_def_index = 0;   /* incremented by OPTENT3 */
 
-    option_def[0].type = OPT_END;
+    OPTENTINIT;
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
diff --git a/editor/pgmmedian.c b/editor/pgmmedian.c
index 65d2eeb0..f911475d 100644
--- a/editor/pgmmedian.c
+++ b/editor/pgmmedian.c
@@ -26,6 +26,7 @@
 */
 
 
+#include "pm_c_util.h"
 #include "pgm.h"
 #include "shhopt.h"
 #include "mallocvar.h"
@@ -52,7 +53,6 @@ static int format;
 static gray maxval;
 static gray **grays;
 static gray *grayrow;
-static gray **rowptr;
 static int ccolso2, crowso2;
 static int row;
 
@@ -102,9 +102,9 @@ parseCommandLine(int argc, char ** argv,
         cmdlineP->cutoff = 250;
 
     if (typeSpec) {
-        if (STREQ(type, "histogram_sort"))
+        if (streq(type, "histogram_sort"))
             cmdlineP->type = HISTOGRAM_SORT_MEDIAN;
-        else if (STREQ(type, "select"))
+        else if (streq(type, "select"))
             cmdlineP->type = SELECT_MEDIAN;
         else
             pm_error("Invalid value '%s' for -type.  Valid values are "
@@ -125,10 +125,10 @@ parseCommandLine(int argc, char ** argv,
 
 
 static void
-select_489(gray * const a,
-           int *  const parray,
-           int    const n,
-           int    const k) {
+select489(gray * const a,
+          int *  const parray,
+          int    const n,
+          int    const k) {
 
     gray t;
     int i, j, l, r;
@@ -180,206 +180,220 @@ select_489(gray * const a,
 
 
 static void
-select_median(FILE * const ifp,
-              int    const ccols,
-              int    const crows,
-              int    const cols,
-              int    const rows,
-              int    const median) {
-
-    int ccol, col;
-    int crow;
-    int rownum, irow, temprow;
-    gray *temprptr;
-    int i, leftcol;
-    int num_values;
-    gray *garray;
-
-    int *parray;
-    int addcol;
-    int *subcol;
-    int tsum;
-
-    /* Allocate storage for array of the current gray values. */
-    garray = pgm_allocrow( crows * ccols );
+selectMedian(FILE *       const ifP,
+             unsigned int const ccols,
+             unsigned int const crows,
+             unsigned int const cols,
+             unsigned int const rows,
+             unsigned int const median) {
+
+    unsigned int const numValues = crows * ccols;
+
+    unsigned int col;
+    gray * garray;
+        /* Array of the currenty gray values */
+    int * parray;
+    int * subcol;
+    gray ** rowptr;
+    
+    garray = pgm_allocrow(numValues);
 
-    num_values = crows * ccols;
+    MALLOCARRAY(rowptr, crows);
+    MALLOCARRAY(parray, numValues);
+    MALLOCARRAY(subcol, cols);
 
-    parray = (int *) pm_allocrow( crows * ccols, sizeof(int) );
-    subcol = (int *) pm_allocrow( cols, sizeof(int) );
+    if (rowptr == NULL || parray == NULL || subcol == NULL)
+        pm_error("Unable to allocate memory");
 
-    for ( i = 0; i < cols; ++i )
-        subcol[i] = ( i - (ccolso2 + 1) ) % ccols;
+    for (col = 0; col < cols; ++col)
+        subcol[col] = (col - (ccolso2 + 1)) % ccols;
 
     /* Apply median to main part of image. */
-    for ( ; row < rows; ++row ) {
-        temprow = row % crows;
-        pgm_readpgmrow( ifp, grays[temprow], cols, maxval, format );
+    for ( ; row < rows; ++row) {
+        int crow;
+        int rownum, irow, temprow;
+        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;
+        temprow = (row + 1) % crows;
         rownum = 0;
-        for ( irow = temprow; irow < crows; ++rownum, ++irow )
+        for (irow = temprow; irow < crows; ++rownum, ++irow)
             rowptr[rownum] = grays[irow];
-        for ( irow = 0; irow < temprow; ++rownum, ++irow )
+        for (irow = 0; irow < temprow; ++rownum, ++irow)
             rowptr[rownum] = grays[irow];
 
-        for ( col = 0; col < cols; ++col ) {
-            if ( col < ccolso2 || col >= cols - ccolso2 ) {
+        for (col = 0; col < cols; ++col) {
+            if (col < ccolso2 || col >= cols - ccolso2) {
                 grayrow[col] = rowptr[crowso2][col];
-            } else if ( col == ccolso2 ) {
-                leftcol = col - ccolso2;
+            } else if (col == ccolso2) {
+                unsigned int const leftcol = col - ccolso2;
+                unsigned int i;
                 i = 0;
-                for ( crow = 0; crow < crows; ++crow ) {
-                    temprptr = rowptr[crow] + leftcol;
-                    for ( ccol = 0; ccol < ccols; ++ccol ) {
-                        garray[i] = *( temprptr + ccol );
+                for (crow = 0; crow < crows; ++crow) {
+                    gray * const temprptr = rowptr[crow] + leftcol;
+                    unsigned int ccol;
+                    for (ccol = 0; ccol < ccols; ++ccol) {
+                        garray[i] = *(temprptr + ccol);
                         parray[i] = i;
                         ++i;
                     }
                 }
-                select_489( garray, parray, num_values, median );
+                select489(garray, parray, numValues, median);
                 grayrow[col] = garray[parray[median]];
             } else {
-                addcol = col + ccolso2;
+                unsigned int const addcol = col + ccolso2;
+                unsigned int crow;
+                unsigned int tsum;
                 for (crow = 0, tsum = 0; crow < crows; ++crow, tsum += ccols)
                     garray[tsum + subcol[col]] = *(rowptr[crow] + addcol );
-                select_489( garray, parray, num_values, median );
+                select489( garray, parray, numValues, median );
                 grayrow[col] = garray[parray[median]];
             }
         }
         pgm_writepgmrow( stdout, grayrow, cols, maxval, forceplain );
     }
 
-    /* Write out remaining unchanged rows. */
-    for ( irow = crowso2 + 1; irow < crows; ++irow )
-        pgm_writepgmrow( stdout, rowptr[irow], cols, maxval, forceplain );
-
-    pgm_freerow( garray );
-    pm_freerow( (char *) parray );
-    pm_freerow( (char *) subcol );
+    {
+        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);
+    pgm_freerow(garray);
 }
 
 
 
 static void
-histogram_sort_median(FILE * const ifp,
-                      int    const ccols,
-                      int    const crows,
-                      int    const cols,
-                      int    const rows,
-                      int    const median) {
+histogramSortMedian(FILE *       const ifP,
+                    unsigned int const ccols,
+                    unsigned int const crows,
+                    unsigned int const cols,
+                    unsigned int const rows,
+                    unsigned int const median) {
 
-    int const histmax = maxval + 1;
+    unsigned int const histmax = maxval + 1;
 
-    int *hist;
-    int mdn, ltmdn;
-    gray *left_col, *right_col;
+    unsigned int * hist;
+    unsigned int mdn, ltmdn;
+    gray * leftCol;
+    gray * rghtCol;
+    gray ** rowptr;
 
-    hist = (int *) pm_allocrow( histmax, sizeof( int ) );
-    left_col = pgm_allocrow( crows );
-    right_col = pgm_allocrow( crows );
+    MALLOCARRAY(rowptr, crows);
+    MALLOCARRAY(hist, histmax);
+
+    if (rowptr == 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 ) {
-        int col;
-        int temprow;
-        int rownum;
-        int irow;
-        int i;
+    for ( ; 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 )
+        for (i = 0; i < histmax; ++i)
             hist[i] = 0;
 
-        temprow = row % crows;
-        pgm_readpgmrow( ifp, grays[temprow], cols, maxval, format );
+        pgm_readpgmrow(ifP, grays[row % crows], cols, maxval, format);
 
         /* Rotate pointers to rows, so rows can be accessed in order. */
-        temprow = ( row + 1 ) % crows;
+        temprow = (row + 1) % crows;
         rownum = 0;
-        for ( irow = temprow; irow < crows; ++rownum, ++irow )
+        for (irow = temprow; irow < crows; ++rownum, ++irow)
             rowptr[rownum] = grays[irow];
-        for ( irow = 0; irow < temprow; ++rownum, ++irow )
+        for (irow = 0; irow < temprow; ++rownum, ++irow)
             rowptr[rownum] = grays[irow];
 
-        for ( col = 0; col < cols; ++col ) {
-            if ( col < ccolso2 || col >= cols - ccolso2 )
+        for (col = 0; col < cols; ++col) {
+            if (col < ccolso2 || col >= cols - ccolso2)
                 grayrow[col] = rowptr[crowso2][col];
-            else if ( col == ccolso2 ) {
-                int crow;
-                int const leftcol = col - ccolso2;
+            else if (col == ccolso2) {
+                unsigned int crow;
+                unsigned int const leftcol = col - ccolso2;
                 i = 0;
-                for ( crow = 0; crow < crows; ++crow ) {
-                    int ccol;
+                for (crow = 0; crow < crows; ++crow) {
+                    unsigned int ccol;
                     gray * const temprptr = rowptr[crow] + leftcol;
-                    for ( ccol = 0; ccol < ccols; ++ccol ) {
-                        gray const g = *( temprptr + ccol );
+                    for (ccol = 0; ccol < ccols; ++ccol) {
+                        gray const g = *(temprptr + ccol);
                         ++hist[g];
                         ++i;
                     }
                 }
                 ltmdn = 0;
-                for ( mdn = 0; ltmdn <= median; ++mdn )
+                for (mdn = 0; ltmdn <= median; ++mdn)
                     ltmdn += hist[mdn];
-                mdn--;
-                if ( ltmdn > median ) 
+                --mdn;
+                if (ltmdn > median) 
                     ltmdn -= hist[mdn];
 
                 grayrow[col] = mdn;
             } else {
-                int crow;
-                int const subcol = col - ( ccolso2 + 1 );
-                int const addcol = col + ccolso2;
-                for ( crow = 0; crow < crows; ++crow ) {
-                    left_col[crow] = *( rowptr[crow] + subcol );
-                    right_col[crow] = *( rowptr[crow] + addcol );
+                unsigned int crow;
+                unsigned int const subcol = col - (ccolso2 + 1);
+                unsigned int const addcol = col + ccolso2;
+                for (crow = 0; crow < crows; ++crow) {
+                    leftCol[crow] = *(rowptr[crow] + subcol);
+                    rghtCol[crow] = *(rowptr[crow] + addcol);
                 }
-                for ( crow = 0; crow < crows; ++crow ) {
+                for (crow = 0; crow < crows; ++crow) {
                     {
-                        gray const g = left_col[crow];
-                        hist[(int) g]--;
-                        if ( (int) g < mdn )
-                            ltmdn--;
+                        gray const g = leftCol[crow];
+                        --hist[(unsigned int) g];
+                        if ((unsigned int) g < mdn)
+                            --ltmdn;
                     }
                     {
-                        gray const g = right_col[crow];
-                        hist[(int) g]++;
-                        if ( (int) g < mdn )
-                            ltmdn++;
+                        gray const g = rghtCol[crow];
+                        ++hist[(unsigned int) g];
+                        if ((unsigned int) g < mdn)
+                            ++ltmdn;
                     }
                 }
-                if ( ltmdn > median )
+                if (ltmdn > median)
                     do {
-                        mdn--;
+                        --mdn;
                         ltmdn -= hist[mdn];
-                    } while ( ltmdn > median );
+                    } while (ltmdn > median);
                 else {
                     /* This one change from Pitas algorithm can reduce run
                     ** time by up to 10%.
                     */
-                    while ( ltmdn <= median ) {
+                    while (ltmdn <= median) {
                         ltmdn += hist[mdn];
-                        mdn++;
+                        ++mdn;
                     }
-                    mdn--;
-                    if ( ltmdn > median ) 
+                    --mdn;
+                    if (ltmdn > median) 
                         ltmdn -= hist[mdn];
                 }
                 grayrow[col] = mdn;
             }
         }
-        pgm_writepgmrow( stdout, grayrow, cols, maxval, forceplain );
+        pgm_writepgmrow(stdout, grayrow, cols, maxval, forceplain);
     }
 
     {
         /* Write out remaining unchanged rows. */
-        int irow;
-        for ( irow = crowso2 + 1; irow < crows; ++irow )
-            pgm_writepgmrow( stdout, rowptr[irow], cols, maxval, forceplain );
+        unsigned int irow;
+        for (irow = crowso2 + 1; irow < crows; ++irow)
+            pgm_writepgmrow(stdout, rowptr[irow], cols, maxval, forceplain);
     }
-    pm_freerow( (char *) hist );
-    pgm_freerow( left_col );
-    pgm_freerow( right_col );
+    pgm_freerow(leftCol);
+    pgm_freerow(rghtCol);
+    free(hist);
+    free(rowptr);
 }
 
 
@@ -410,9 +424,6 @@ main(int    argc,
     grays = pgm_allocarray(cols, cmdline.height);
     grayrow = pgm_allocrow(cols);
 
-    /* Allocate pointers to mask row buffer. */
-    rowptr = (gray **) pm_allocrow(cmdline.height, sizeof(gray *));
-
     /* 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);
@@ -434,12 +445,12 @@ main(int    argc,
 
     switch (medianMethod) {
     case SELECT_MEDIAN:
-        select_median(ifP, cmdline.width, cmdline.height, cols, rows, median);
+        selectMedian(ifP, cmdline.width, cmdline.height, cols, rows, median);
         break;
         
     case HISTOGRAM_SORT_MEDIAN:
-        histogram_sort_median(ifP, cmdline.width, cmdline.height,
-                              cols, rows, median);
+        histogramSortMedian(ifP, cmdline.width, cmdline.height,
+                            cols, rows, median);
         break;
     case MEDIAN_UNSPECIFIED:
         pm_error("INTERNAL ERROR: median unspecified");
@@ -450,13 +461,6 @@ main(int    argc,
 
     pgm_freearray(grays, cmdline.height);
     pgm_freerow(grayrow);
-    pm_freerow(rowptr);
 
     return 0;
 }
-
-
-
-
-
-
diff --git a/editor/pnmcat.c b/editor/pnmcat.c
index 20dbf34d..3cf443bb 100644
--- a/editor/pnmcat.c
+++ b/editor/pnmcat.c
@@ -1,4 +1,4 @@
-/* pnmcat.c - concatenate portable anymaps
+/* pnmcat.c - concatenate PNM images
 **
 ** Copyright (C) 1989, 1991 by Jef Poskanzer.
 **
@@ -10,22 +10,41 @@
 ** implied warranty.
 */
 
-#include "pnm.h"
+#include <assert.h>
+
+#include "pm_c_util.h"
 #include "mallocvar.h"
 #include "shhopt.h"
+#include "bitarith.h"
+#include "pnm.h"
 
+#define LEFTBITS pm_byteLeftBits
+#define RIGHTBITS pm_byteRightBits
 
-enum backcolor {BACK_BLACK, BACK_WHITE, BACK_AUTO};
+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;  
+    const char ** inputFilespec;
     unsigned int nfiles;
     enum backcolor backcolor;
     enum orientation orientation;
@@ -35,22 +54,24 @@ struct cmdlineInfo {
 
 
 static void
-parseCommandLine(int argc, char ** const argv,
+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 = malloc(100*sizeof(optEntry));
-        /* Instructions to OptParseOptions2 on how to parse our options.
+    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);
@@ -68,7 +89,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 */
 
-    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+    optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
     if (leftright + topbottom > 1)
@@ -134,28 +155,25 @@ parseCommandLine(int argc, char ** const argv,
         unsigned int i;
 
         MALLOCARRAY_NOFAIL(cmdlineP->inputFilespec, argc-1);
-        
+
         for (i = 0; i < argc-1; ++i)
             cmdlineP->inputFilespec[i] = argv[1+i];
         cmdlineP->nfiles = argc-1;
     }
-}        
+}
 
 
 
 static void
 computeOutputParms(unsigned int     const nfiles,
                    enum orientation const orientation,
-                   int                    cols[], 
-                   int                    rows[],
-                   xelval                 maxval[],
-                   int                    format[],
-                   int *            const newcolsP,
-                   int *            const newrowsP,
+                   struct imgInfo   const img[],
+                   unsigned int *   const newcolsP,
+                   unsigned int *   const newrowsP,
                    xelval *         const newmaxvalP,
                    int *            const newformatP) {
 
-    int newcols, newrows;
+    double newcols, newrows;
     int newformat;
     xelval newmaxval;
 
@@ -164,31 +182,43 @@ computeOutputParms(unsigned int     const nfiles,
     newcols = 0;
     newrows = 0;
 
-    for (i = 0; i < nfiles; ++i)	{
+    for (i = 0; i < nfiles; ++i) {
+        const struct imgInfo * const imgP = &img[i];
+
         if (i == 0) {
-            newmaxval = maxval[i];
-            newformat = format[i];
-	    } else {
-            if (PNM_FORMAT_TYPE(format[i]) > PNM_FORMAT_TYPE(newformat))
-                newformat = format[i];
-            if (maxval[i] > newmaxval)
-                newmaxval = maxval[i];
-	    }
+            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 += cols[i];
-            if (rows[i] > newrows)
-                newrows = rows[i];
+            newcols += imgP->cols;
+            if (imgP->rows > newrows)
+                newrows = imgP->rows;
             break;
         case TOPBOTTOM:
-            newrows += rows[i];
-            if (cols[i] > newcols)
-                newcols = cols[i];
+            newrows += imgP->rows;
+            if (imgP->cols > newcols)
+                newcols = imgP->cols;
             break;
-	    }
-	}
-    *newrowsP   = newrows;
-    *newcolsP   = newcols;
+        }
+    }
+
+    /* 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;
 }
@@ -196,231 +226,631 @@ computeOutputParms(unsigned int     const nfiles,
 
 
 static void
-concatenateLeftRight(FILE *             const ofp,
-                     unsigned int       const nfiles,
-                     int                const newcols,
-                     int                const newrows,
-                     xelval             const newmaxval,
-                     int                const newformat,
-                     enum justification const justification,
-                     FILE *                   ifp[],
-                     int                      cols[],
-                     int                      rows[],
-                     xelval                   maxval[],
-                     int                      format[],
-                     xel *                    xelrow[],
-                     xel                      background[]) {
+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(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;
-    
-    xel * const newxelrow = pnm_allocrow(newcols);
+
+    getPbmImageInfo(img, nfiles, newrows, justification, backcolor, &img2);
 
     for (row = 0; row < newrows; ++row) {
-        unsigned int new;
         unsigned int i;
 
-        new = 0;
         for (i = 0; i < nfiles; ++i) {
-            int padtop;
 
+            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;
+    }
+    free(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:
-                padtop = 0;
+                img2[i].padtop = 0;
                 break;
             case JUST_MAX:
-                padtop = newrows - rows[i];
+                img2[i].padtop = newrows - img[i].rows;
                 break;
             case JUST_CENTER:
-                padtop = ( newrows - rows[i] ) / 2;
+                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;
             }
-            if (row < padtop || row >= padtop + rows[i]) {
+        }
+    }
+    *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 < cols[i]; ++col)
-                    newxelrow[new+col] = background[i];
+                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 {
-                if (row != padtop) {
-                    /* first row already read */
-                    pnm_readpnmrow(
-                        ifp[i], xelrow[i], cols[i], maxval[i], format[i] );
-                    pnm_promoteformatrow(
-                        xelrow[i], cols[i], maxval[i], format[i],
-                        newmaxval, newformat );
-                }
-                {
-                    unsigned int col;
-                    for (col = 0; col < cols[i]; ++col)
-                        newxelrow[new+col] = xelrow[i][col];
-                }
+                /* It's a row of padding, so outrow[] is already set
+                   appropriately.
+                */
             }
-            new += cols[i];
         }
-        pnm_writepnmrow(ofp, newxelrow, newcols, newmaxval, newformat, 0);
+        pnm_writepnmrow(ofP, outrow, newcols, newmaxval, newformat, 0);
     }
+    pnm_freerow(outrow);
 }
 
 
 
 static void
-concatenateTopBottom(FILE *             const ofp,
-                     unsigned int       const nfiles,
-                     int                const newcols,
-                     int                const newrows,
-                     xelval             const newmaxval,
-                     int                const newformat,
-                     enum justification const justification,
-                     FILE *                   ifp[],
-                     int                      cols[],
-                     int                      rows[],
-                     xelval                   maxval[],
-                     int                      format[],
-                     xel *                    xelrow[],
-                     xel                      background[]) {
-
-    int new;
+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);
-    int padleft;
+    xel * inrow;
+    unsigned int padleft;
     unsigned int i;
-    unsigned int row;
-    
-    i = 0;
-    switch (justification) {
-    case JUST_MIN:
-        padleft = 0;
-        break;
-    case JUST_MAX:
-        padleft = newcols - cols[i];
-        break;
-    case JUST_CENTER:
-        padleft = (newcols - cols[i]) / 2;
-        break;
+    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;
     }
 
-    new = 0;
-
-    for (row = 0; row < newrows; ++row) {
-        if (row - new >= rows[i]) {
-            new += rows[i];
-            ++i;
-            if (i >= nfiles)
-                pm_error("INTERNAL ERROR: i > nfiles");
+    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 - cols[i];
-                break;
-            case JUST_CENTER:
-                padleft = (newcols - cols[i]) / 2;
-                break;
+            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 (row - new > 0) {
-            pnm_readpnmrow(
-                ifp[i], xelrow[i], cols[i], maxval[i], format[i]);
+
+        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(
-                xelrow[i], cols[i], maxval[i], format[i],
+                inrow, img[i].cols, img[i].maxval, img[i].format,
                 newmaxval, newformat);
+
+            pnm_writepnmrow(ofP, newxelrow, newcols, newmaxval, newformat, 0);
         }
-        {
-            unsigned int col;
-
-            for (col = 0; col < padleft; ++col)
-                newxelrow[col] = background[i];
-            for (col = 0; col < cols[i]; ++col)
-                newxelrow[padleft+col] = xelrow[i][col];
-            for (col = padleft + cols[i]; col < newcols; ++col)
-                newxelrow[col] = background[i];
-        }
-        pnm_writepnmrow(ofp,
-                        newxelrow, newcols, newmaxval, newformat, 0);
-	}
+    }
+    pnm_freerow(newxelrow);
 }
 
 
 
 int
-main(int argc, char ** argv) {
+main(int           argc,
+     const char ** argv) {
 
     struct cmdlineInfo cmdline;
-    FILE** ifp;
-    xel** xelrow;
-    xel* background;
-    xelval* maxval;
+    struct imgInfo * img;  /* malloc'ed array */
     xelval newmaxval;
-    int* rows;
-    int* cols;
-    int* format;
     int newformat;
     unsigned int i;
-    int newrows, newcols;
+    unsigned int newrows, newcols;
 
-    pnm_init( &argc, argv );
+    pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
-    
-    MALLOCARRAY_NOFAIL(ifp,        cmdline.nfiles);
-    MALLOCARRAY_NOFAIL(xelrow,     cmdline.nfiles);
-    MALLOCARRAY_NOFAIL(background, cmdline.nfiles);
-    MALLOCARRAY_NOFAIL(maxval,     cmdline.nfiles);
-    MALLOCARRAY_NOFAIL(rows,       cmdline.nfiles);
-    MALLOCARRAY_NOFAIL(cols,       cmdline.nfiles);
-    MALLOCARRAY_NOFAIL(format,     cmdline.nfiles);
+
+    MALLOCARRAY_NOFAIL(img, cmdline.nfiles);
 
     for (i = 0; i < cmdline.nfiles; ++i) {
-        ifp[i] = pm_openr(cmdline.inputFilespec[i]);
-        pnm_readpnminit(ifp[i], &cols[i], &rows[i], &maxval[i], &format[i]);
-        xelrow[i] = pnm_allocrow(cols[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,
-                       cols, rows, maxval, format,
+    computeOutputParms(cmdline.nfiles, cmdline.orientation, img,
                        &newcols, &newrows, &newmaxval, &newformat);
 
-    for (i = 0; i < cmdline.nfiles; ++i) {
-        /* Read first row just to get a good guess at the background. */
-        pnm_readpnmrow(ifp[i], xelrow[i], cols[i], maxval[i], format[i]);
-        pnm_promoteformatrow(
-            xelrow[i], cols[i], maxval[i], format[i], newmaxval, newformat);
-        switch (cmdline.backcolor) {
-        case BACK_AUTO:
-            background[i] =
-                pnm_backgroundxelrow(
-                    xelrow[i], cols[i], 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;
-        case BACK_BLACK:
-            background[i] = pnm_blackxel(newmaxval, newformat);
+        }
+    } else {
+        switch (cmdline.orientation) {
+        case LEFTRIGHT:
+            concatenateLeftRightGen(stdout, cmdline.nfiles,
+                                    newcols, newrows, newmaxval, newformat,
+                                    cmdline.justification, img,
+                                    cmdline.backcolor);
             break;
-        case BACK_WHITE:
-            background[i] = pnm_whitexel(newmaxval, newformat);
+        case TOPBOTTOM:
+            concatenateTopBottomGen(stdout, cmdline.nfiles,
+                                    newcols, newrows, newmaxval, newformat,
+                                    cmdline.justification, img,
+                                    cmdline.backcolor);
             break;
         }
-	}
-
-    pnm_writepnminit(stdout, newcols, newrows, newmaxval, newformat, 0);
-
-    switch (cmdline.orientation) {
-    case LEFTRIGHT:
-        concatenateLeftRight(stdout, cmdline.nfiles,
-                             newcols, newrows, newmaxval, newformat,
-                             cmdline.justification,
-                             ifp, cols, rows, maxval, format, xelrow,
-                             background);
-        break;
-    case TOPBOTTOM:
-        concatenateTopBottom(stdout, cmdline.nfiles,
-                             newcols, newrows, newmaxval, newformat,
-                             cmdline.justification,
-                             ifp, cols, rows, maxval, format, xelrow,
-                             background);
-        break;
     }
-    free(cmdline.inputFilespec);
-
     for (i = 0; i < cmdline.nfiles; ++i)
-        pm_close(ifp[i]);
-
+        pm_close(img[i].ifP);
+    free(cmdline.inputFilespec);
     pm_close(stdout);
 
     return 0;
diff --git a/editor/pnmcomp.c b/editor/pnmcomp.c
index 5a8b1d55..04bb5365 100644
--- a/editor/pnmcomp.c
+++ b/editor/pnmcomp.c
@@ -19,6 +19,7 @@
 #define _BSD_SOURCE    /* Make sure strcasecmp() is in string.h */
 #include <string.h>
 
+#include "pm_c_util.h"
 #include "pnm.h"
 #include "shhopt.h"
 #include "mallocvar.h"
diff --git a/editor/pnmconvol.c b/editor/pnmconvol.c
index 651b9049..2033a1a3 100644
--- a/editor/pnmconvol.c
+++ b/editor/pnmconvol.c
@@ -19,6 +19,7 @@
 
 #include <assert.h>
 
+#include "pm_c_util.h"
 #include "pnm.h"
 #include "shhopt.h"
 #include "mallocvar.h"
@@ -34,7 +35,7 @@ struct cmdlineInfo {
 
 static void
 parseCommandLine(int argc, char ** argv,
-                 struct cmdlineInfo *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.  
@@ -85,23 +86,23 @@ parseCommandLine(int argc, char ** argv,
 /* Macros to verify that r,g,b values are within proper range */
 
 #define CHECK_GRAY \
-    if ( tempgsum < 0L ) g = 0; \
-    else if ( tempgsum > maxval ) g = maxval; \
+    if (tempgsum < 0L) g = 0; \
+    else if (tempgsum > maxval) g = maxval; \
     else g = tempgsum;
 
 #define CHECK_RED \
-    if ( temprsum < 0L ) r = 0; \
-    else if ( temprsum > maxval ) r = maxval; \
+    if (temprsum < 0L) r = 0; \
+    else if (temprsum > maxval) r = maxval; \
     else r = temprsum;
 
 #define CHECK_GREEN \
-    if ( tempgsum < 0L ) g = 0; \
-    else if ( tempgsum > maxval ) g = maxval; \
+    if (tempgsum < 0L) g = 0; \
+    else if (tempgsum > maxval) g = maxval; \
     else g = tempgsum;
 
 #define CHECK_BLUE \
-    if ( tempbsum < 0L ) b = 0; \
-    else if ( tempbsum > maxval ) b = maxval; \
+    if (tempbsum < 0L) b = 0; \
+    else if (tempbsum > maxval) b = maxval; \
     else b = tempbsum;
 
 struct convolveType {
@@ -111,7 +112,7 @@ struct convolveType {
     void (*pgmConvolver)(const float ** const weights);
 };
 
-static FILE* ifp;
+static FILE * ifp;
 static int crows, ccols, ccolso2, crowso2;
 static int cols, rows;
 static xelval maxval;
diff --git a/editor/pnmcrop.c b/editor/pnmcrop.c
index 9ce388ab..1abfc7d4 100644
--- a/editor/pnmcrop.c
+++ b/editor/pnmcrop.c
@@ -25,19 +25,42 @@
 #include <errno.h>
 #include <assert.h>
 
+#include "pm_c_util.h"
 #include "pnm.h"
 #include "shhopt.h"
 #include "mallocvar.h"
 
 enum bg_choice {BG_BLACK, BG_WHITE, BG_DEFAULT, BG_SIDES};
 
+typedef enum { LEFT = 0, RIGHT = 1, TOP = 2, BOTTOM = 3} edgeLocation;
+
+static const char * const edgeName[] = {
+    "left",
+    "right",
+    "top",
+    "bottom"
+};
+
+typedef struct {
+    unsigned int size[4];
+} borderSet;
+
+typedef enum {
+    /* A position in a PNM image file stream */
+    FILEPOS_BEG,
+        /* Immediately before the raster */
+    FILEPOS_END
+        /* Immediately after the raster */
+} imageFilePos;
+
 struct cmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
     const char * inputFilespec;
     enum bg_choice background;
-    unsigned int left, right, top, bottom;
+    bool wantCrop[4];
+        /* User wants crop of left, right, top, bottom, resp. */
     unsigned int verbose;
     unsigned int margin;
     const char * borderfile;  /* NULL if none */
@@ -46,7 +69,7 @@ struct cmdlineInfo {
 
 
 static void
-parseCommandLine(int argc, char ** argv,
+parseCommandLine(int argc, const char ** argv,
                  struct cmdlineInfo *cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that the file spec array we return is stored in the storage that
@@ -59,6 +82,7 @@ parseCommandLine(int argc, char ** argv,
 
     unsigned int blackOpt, whiteOpt, sidesOpt;
     unsigned int marginSpec, borderfileSpec;
+    unsigned int leftOpt, rightOpt, topOpt, bottomOpt;
     
     unsigned int option_def_index;
 
@@ -68,10 +92,10 @@ parseCommandLine(int argc, char ** argv,
     OPTENT3(0, "black",      OPT_FLAG, NULL, &blackOpt,            0);
     OPTENT3(0, "white",      OPT_FLAG, NULL, &whiteOpt,            0);
     OPTENT3(0, "sides",      OPT_FLAG, NULL, &sidesOpt,            0);
-    OPTENT3(0, "left",       OPT_FLAG, NULL, &cmdlineP->left,      0);
-    OPTENT3(0, "right",      OPT_FLAG, NULL, &cmdlineP->right,     0);
-    OPTENT3(0, "top",        OPT_FLAG, NULL, &cmdlineP->top,       0);
-    OPTENT3(0, "bottom",     OPT_FLAG, NULL, &cmdlineP->bottom,    0);
+    OPTENT3(0, "left",       OPT_FLAG, NULL, &leftOpt,             0);
+    OPTENT3(0, "right",      OPT_FLAG, NULL, &rightOpt,            0);
+    OPTENT3(0, "top",        OPT_FLAG, NULL, &topOpt,              0);
+    OPTENT3(0, "bottom",     OPT_FLAG, NULL, &bottomOpt,           0);
     OPTENT3(0, "verbose",    OPT_FLAG, NULL, &cmdlineP->verbose,   0);
     OPTENT3(0, "margin",     OPT_UINT,   &cmdlineP->margin,    
             &marginSpec,     0);
@@ -82,8 +106,10 @@ 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 */
 
-    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+    optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+        
+    free(option_def);
 
     if (argc-1 == 0)
         cmdlineP->inputFilespec = "-";  /* stdin */
@@ -106,12 +132,16 @@ parseCommandLine(int argc, char ** argv,
     else
         cmdlineP->background = BG_DEFAULT;
 
-    if (!cmdlineP->left && !cmdlineP->right && !cmdlineP->top
-        && !cmdlineP->bottom) {
-        cmdlineP->left = cmdlineP->right = cmdlineP->top 
-            = cmdlineP->bottom = TRUE;
+    if (!leftOpt && !rightOpt && !topOpt && !bottomOpt) {
+        unsigned int i;
+        for (i = 0; i < 4; ++i)
+            cmdlineP->wantCrop[i] = true;
+    } else {
+        cmdlineP->wantCrop[LEFT]   = !!leftOpt;
+        cmdlineP->wantCrop[RIGHT]  = !!rightOpt;
+        cmdlineP->wantCrop[TOP]    = !!topOpt;
+        cmdlineP->wantCrop[BOTTOM] = !!bottomOpt;
     }
-
     if (!marginSpec)
         cmdlineP->margin = 0;
 
@@ -121,6 +151,27 @@ parseCommandLine(int argc, char ** argv,
 
 
 
+typedef struct {
+/*----------------------------------------------------------------------------
+   This describes a cropping operation of a single border (top, bottom,
+   left, or right).
+
+   Our definition of cropping includes padding to make a margin as well as
+   chopping stuff out.
+-----------------------------------------------------------------------------*/
+    unsigned int removeSize;
+        /* Size in pixels of the border to remove */
+    unsigned int padSize;
+        /* Size in pixels of the border to add */
+} cropOp;
+
+
+typedef struct {
+    cropOp op[4];
+} cropSet;
+
+
+
 static xel
 background3Corners(FILE * const ifP,
                    int    const rows,
@@ -188,8 +239,7 @@ computeBackground(FILE *         const ifP,
                   int            const rows,
                   xelval         const maxval,
                   int            const format,
-                  enum bg_choice const backgroundChoice,
-                  int            const verbose) {
+                  enum bg_choice const backgroundChoice) {
 /*----------------------------------------------------------------------------
    Determine what color is the background color of the image in file
    *ifP, which is described by 'cols', 'rows', 'maxval', and 'format'.
@@ -204,10 +254,10 @@ computeBackground(FILE *         const ifP,
     
     switch (backgroundChoice) {
     case BG_WHITE:
-	    background = pnm_whitexel(maxval, format);
+        background = pnm_whitexel(maxval, format);
         break;
     case BG_BLACK:
-	    background = pnm_blackxel(maxval, format);
+        background = pnm_blackxel(maxval, format);
         break;
     case BG_SIDES: 
         background = 
@@ -219,11 +269,6 @@ computeBackground(FILE *         const ifP,
         break;
     }
 
-    if (verbose) {
-        pixel const backgroundPixel = pnm_xeltopixel(background, format);
-        pm_message("Background color is %s", 
-                   ppm_colorname(&backgroundPixel, maxval, TRUE /*hexok*/));
-    }
     return(background);
 }
 
@@ -236,22 +281,18 @@ findBordersInImage(FILE *         const ifP,
                    xelval         const maxval,
                    int            const format,
                    xel            const backgroundColor,
-                   bool           const verbose, 
                    bool *         const hasBordersP,
-                   unsigned int * const leftP,
-                   unsigned int * const rightP, 
-                   unsigned int * const topP,
-                   unsigned int * const bottomP) {
+                   borderSet *    const borderSizeP) {
 /*----------------------------------------------------------------------------
    Find the left, right, top, and bottom borders in the image 'ifP'.
-   Return their sizes in pixels as *leftP, *rightP, *topP, and *bottomP.
+   Return their sizes in pixels as borderSize[n].
    
    Iff the image is all background, *hasBordersP == FALSE.
 
    Expect the input file to be positioned to the beginning of the
    image raster and leave it positioned arbitrarily.
 -----------------------------------------------------------------------------*/
-    xel* xelrow;        /* A row of the input image */
+    xel * xelrow;        /* A row of the input image */
     int row;
     bool gottop;
     int left, right, bottom, top;
@@ -260,9 +301,9 @@ findBordersInImage(FILE *         const ifP,
     xelrow = pnm_allocrow(cols);
     
     left   = cols;  /* initial value */
-    right  = -1;   /* initial value */
-    top    = rows;   /* initial value */
-    bottom = -1;  /* initial value */
+    right  = -1;    /* initial value */
+    top    = rows;  /* initial value */
+    bottom = -1;    /* initial value */
 
     gottop = FALSE;
     for (row = 0; row < rows; ++row) {
@@ -273,7 +314,7 @@ findBordersInImage(FILE *         const ifP,
         pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
         
         col = 0;
-        while (PNM_EQUAL(xelrow[col], backgroundColor) && col < cols)
+        while (col < cols && PNM_EQUAL(xelrow[col], backgroundColor))
             ++col;
         thisRowLeft = col;
 
@@ -295,92 +336,97 @@ findBordersInImage(FILE *         const ifP,
             bottom = row + 1;   /* New candidate */
         }
     }
+    
+    free(xelrow);
 
     if (right == -1)
         *hasBordersP = FALSE;
     else {
         *hasBordersP = TRUE;
         assert(right <= cols); assert(bottom <= rows);
-        *leftP       = left - 0;
-        *rightP      = cols - right;
-        *topP        = top - 0;
-        *bottomP     = rows - bottom;
+        borderSizeP->size[LEFT]   = left - 0;
+        borderSizeP->size[RIGHT]  = cols - right;
+        borderSizeP->size[TOP]    = top - 0;
+        borderSizeP->size[BOTTOM] = rows - bottom;
     }
 }
 
 
 
 static void
-findBordersInFile(const char *   const borderFileName,
-                  xel            const backgroundColor,
-                  bool           const verbose, 
-                  bool *         const hasBordersP,
-                  unsigned int * const leftP,
-                  unsigned int * const rightP, 
-                  unsigned int * const topP,
-                  unsigned int * const bottomP) {
-
-    FILE * borderFileP;
-    int cols;
-    int rows;
-    xelval maxval;
-    int format;
-    
-    borderFileP = pm_openr(borderFileName);
-    
-    pnm_readpnminit(borderFileP, &cols, &rows, &maxval, &format);
-    
-    findBordersInImage(borderFileP, cols, rows, maxval, format, 
-                       backgroundColor, verbose, hasBordersP,
-                       leftP, rightP, topP, bottomP);
+analyzeImage(FILE *         const ifP,
+             unsigned int   const cols,
+             unsigned int   const rows,
+             xelval         const maxval,
+             int            const format,
+             enum bg_choice const backgroundReq,
+             imageFilePos   const newFilePos,
+             xel *          const backgroundColorP,
+             bool *         const hasBordersP,
+             borderSet *    const borderSizeP) {
+/*----------------------------------------------------------------------------
+   Analyze the PNM image on file stream *ifP to determine its borders
+   and the color of those borders (the assumed background color).
+
+   Return as *backgroundColorP the background color.
 
-    pm_close(borderFileP);
-} 
+   Return as *borderSizeP the set of border sizes (one for each of the
+   four edges).  But iff there are no borders, don't return anything as
+   *borderSizeP and return *hasBordersP == false.
 
+   Expect *ifP to be positioned right after the header and seekable.
+   Return with it positioned either before or after the raster, as
+   requested by 'newFilePos'.
+-----------------------------------------------------------------------------*/
+    pm_filepos rasterpos;
+    xel background;
 
+    pm_tell2(ifP, &rasterpos, sizeof(rasterpos));
+
+    background = computeBackground(ifP, cols, rows, maxval, format,
+                                   backgroundReq);
+
+    pm_seek2(ifP, &rasterpos, sizeof(rasterpos));
+
+    findBordersInImage(ifP, cols, rows, maxval, format, 
+                       background, hasBordersP, borderSizeP);
+
+    if (newFilePos == FILEPOS_BEG)
+        pm_seek2(ifP, &rasterpos, sizeof(rasterpos));
+
+    *backgroundColorP = background;
+}
 
-static void
-reportOneEdge(unsigned int const oldBorderSize,
-              unsigned int const newBorderSize,
-              const char * const place) {
-
-#define ending(n) (((n) > 1) ? "s" : "")
-
-    if (newBorderSize > oldBorderSize)
-        pm_message("Adding %u pixel%s to the %u-pixel %s border",
-                   newBorderSize - oldBorderSize,
-                   ending(newBorderSize - oldBorderSize),
-                   oldBorderSize, place);
-    else if (newBorderSize < oldBorderSize)
-        pm_message("Cropping %u pixel%s from the %u-pixel %s border",
-                   oldBorderSize - newBorderSize,
-                   ending(oldBorderSize - newBorderSize),
-                   oldBorderSize, place);
-    else
-        pm_message("Leaving %s border unchanged at %u pixel%s",
-                   place, oldBorderSize, ending(oldBorderSize));
-}        
+
+
+static const char *
+ending(unsigned int const n) {
+
+    return n > 1 ? "s" : "";
+}
 
 
 
 static void
-reportCroppingParameters(unsigned int const oldLeftBorderSize,
-                         unsigned int const oldRightBorderSize,
-                         unsigned int const oldTopBorderSize,
-                         unsigned int const oldBottomBorderSize,
-                         unsigned int const newLeftBorderSize,
-                         unsigned int const newRightBorderSize,
-                         unsigned int const newTopBorderSize,
-                         unsigned int const newBottomBorderSize) {
-
-    if (oldLeftBorderSize == 0 && oldRightBorderSize == 0 &&
-        oldTopBorderSize == 0 && oldBottomBorderSize == 0)
-        pm_message("No Border found.");
-
-    reportOneEdge(oldLeftBorderSize,   newLeftBorderSize,   "left"   );
-    reportOneEdge(oldRightBorderSize,  newRightBorderSize,  "right"  );
-    reportOneEdge(oldTopBorderSize,    newTopBorderSize,    "top"    );
-    reportOneEdge(oldBottomBorderSize, newBottomBorderSize, "bottom" );
+reportCroppingParameters(cropSet const crop) {
+
+    unsigned int i;
+
+    for (i = 0; i < 4; ++i) {
+        if (crop.op[i].removeSize == 0 && crop.op[i].padSize == 0)
+            pm_message("Not cropping %s edge", edgeName[i]);
+        else {
+            if (crop.op[i].padSize > 0)
+                pm_message("Adding %u pixel%s to the %s border",
+                           crop.op[i].padSize, ending(crop.op[i].padSize),
+                           edgeName[i]);
+            if (crop.op[i].removeSize > 0)
+                pm_message("Cropping %u pixel%s from the %s border",
+                           crop.op[i].removeSize,
+                           ending(crop.op[i].removeSize),
+                           edgeName[i]);
+        }
+    }
 }
 
 
@@ -400,21 +446,60 @@ fillRow(xel *        const xelrow,
 
 
 static void
-writeCropped(FILE *       const ifP,
-             unsigned int const cols,
-             unsigned int const rows,
-             xelval       const maxval,
-             int          const format,
-             unsigned int const oldLeftBorder,
-             unsigned int const oldRightBorder,
-             unsigned int const oldTopBorder,
-             unsigned int const oldBottomBorder,
-             unsigned int const newLeftBorder,
-             unsigned int const newRightBorder,
-             unsigned int const newTopBorder,
-             unsigned int const newBottomBorder,
-             xel          const backgroundColor,
-             FILE *       const ofP) {
+readOffBorderNonPbm(unsigned int const height,
+                    FILE *       const ifP,
+                    unsigned int const cols,
+                    xelval       const maxval,
+                    int          const format) {
+
+    xel * xelrow;
+    unsigned int i;
+
+    xelrow = pnm_allocrow(cols);
+
+    for (i = 0; i < height; ++i)
+        pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
+
+    pnm_freerow(xelrow);
+}
+
+
+
+static void
+outputNewBorderNonPbm(unsigned int const height,
+                      unsigned int const width,
+                      xel          const color,
+                      FILE *       const ofP,
+                      xelval       const maxval,
+                      int          const format) {
+/*----------------------------------------------------------------------------
+   Output to 'ofP' a horizontal border (i.e. top or bottom)
+   of color 'backgroundColor', height 'height', width 'width'.
+-----------------------------------------------------------------------------*/
+    xel * xelrow;
+    unsigned int i;
+
+    xelrow = pnm_allocrow(width);
+
+    fillRow(xelrow, width, color);
+
+    for (i = 0; i < height; ++i)
+        pnm_writepnmrow(ofP, xelrow, width, maxval, format, 0);
+    
+    pnm_freerow(xelrow);
+}
+
+
+
+static void
+writeCroppedNonPbm(FILE *       const ifP,
+                   unsigned int const cols,
+                   unsigned int const rows,
+                   xelval       const maxval,
+                   int          const format,
+                   cropSet      const crop,
+                   xel          const backgroundColor,
+                   FILE *       const ofP) {
 
     /* In order to do cropping, padding or both at the same time, we have
        a rather complicated row buffer:
@@ -423,6 +508,11 @@ writeCropped(FILE *       const ifP,
        the foreground pixels, the original border pixels, and the new
        border pixels.
 
+       We're calling foreground everything that isn't being cropped out
+       or padded in.  So the "foreground" may include some of what is really
+       a background border in the original image -- because the user can
+       choose to retain part of that border as a margin.
+
        The foreground pixels are in the center of the
        buffer, starting at Column 'foregroundLeft' and going to
        'foregroundRight'.
@@ -445,72 +535,68 @@ writeCropped(FILE *       const ifP,
 
        That's for the middle rows.  For the top and bottom, we just use
        the left portion of xelrow[], starting at 0.
+
+       This is the general case.  Enhancement for PBM appears below.
+       (Logic works for PBM).
     */
 
     unsigned int const foregroundCols =
-        cols - oldLeftBorder - oldRightBorder;
+        cols - crop.op[LEFT].removeSize - crop.op[RIGHT].removeSize;
     unsigned int const outputCols     = 
-        foregroundCols + newLeftBorder + newRightBorder;
+        foregroundCols + crop.op[LEFT].padSize + crop.op[RIGHT].padSize;
     unsigned int const foregroundRows =
-        rows - oldTopBorder - oldBottomBorder;
+        rows - crop.op[TOP].removeSize - crop.op[BOTTOM].removeSize;
     unsigned int const outputRows     =
-        foregroundRows + newTopBorder + newBottomBorder;
+        foregroundRows + crop.op[TOP].padSize + crop.op[BOTTOM].padSize;
 
-    unsigned int const foregroundLeft  = MAX(oldLeftBorder, newLeftBorder);
+    unsigned int const foregroundLeft  =
+        MAX(crop.op[LEFT].removeSize, crop.op[LEFT].padSize);
         /* Index into xelrow[] of leftmost pixel of foreground */
     unsigned int const foregroundRight = foregroundLeft + foregroundCols;
         /* Index into xelrow[] just past rightmost pixel of foreground */
 
     unsigned int const allocCols =
-        foregroundRight + MAX(oldRightBorder, newRightBorder);
+        foregroundRight + MAX(crop.op[RIGHT].removeSize,
+                              crop.op[RIGHT].padSize);
 
-    xel *xelrow;
+    xel * xelrow;
     unsigned int i;
 
-    assert(outputCols == newLeftBorder + foregroundCols + newRightBorder);
-    assert(outputRows == newTopBorder + foregroundRows + newBottomBorder);
-    
     pnm_writepnminit(ofP, outputCols, outputRows, maxval, format, 0);
 
     xelrow = pnm_allocrow(allocCols);
 
-    /* Read off existing top border */
-    for (i = 0; i < oldTopBorder; ++i)
-        pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
+    readOffBorderNonPbm(crop.op[TOP].removeSize, ifP, cols, maxval, format);
 
+    outputNewBorderNonPbm(crop.op[TOP].padSize, outputCols, backgroundColor,
+                          ofP, maxval, format);
 
-    /* Output new top border */
-    fillRow(xelrow, outputCols, backgroundColor);
-    for (i = 0; i < newTopBorder; ++i)
-        pnm_writepnmrow(ofP, xelrow, outputCols, maxval, format, 0);
+    /* Set left border pixels */
+    fillRow(&xelrow[foregroundLeft - crop.op[LEFT].padSize],
+            crop.op[LEFT].padSize,
+            backgroundColor);
 
+    /* Set right border pixels */
+    fillRow(&xelrow[foregroundRight], crop.op[RIGHT].padSize, backgroundColor);
 
     /* Read and output foreground rows */
     for (i = 0; i < foregroundRows; ++i) {
-        /* Set left border pixels */
-        fillRow(&xelrow[foregroundLeft - newLeftBorder], newLeftBorder,
-                backgroundColor);
-
+ 
         /* Read foreground pixels */
-        pnm_readpnmrow(ifP, &(xelrow[foregroundLeft - oldLeftBorder]), cols,
-                       maxval, format);
-
-        /* Set right border pixels */
-        fillRow(&xelrow[foregroundRight], newRightBorder, backgroundColor);
+        pnm_readpnmrow(ifP,
+                       &(xelrow[foregroundLeft - crop.op[LEFT].removeSize]),
+                       cols, maxval, format);
         
         pnm_writepnmrow(ofP,
-                        &(xelrow[foregroundLeft - newLeftBorder]), outputCols,
-                        maxval, format, 0);
+                        &(xelrow[foregroundLeft - crop.op[LEFT].padSize]),
+                        outputCols, maxval, format, 0);
     }
 
-    /* Read off existing bottom border */
-    for (i = 0; i < oldBottomBorder; ++i)
-        pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
-
-    /* Output new bottom border */
-    fillRow(xelrow, outputCols, backgroundColor);
-    for (i = 0; i < newBottomBorder; ++i)
-        pnm_writepnmrow(ofP, xelrow, outputCols, maxval, format, 0);
+    readOffBorderNonPbm(crop.op[BOTTOM].removeSize, ifP, cols, maxval, format);
+    
+    outputNewBorderNonPbm(crop.op[BOTTOM].padSize, outputCols,
+                          backgroundColor,
+                          ofP, maxval, format);
 
     pnm_freerow(xelrow);
 }
@@ -518,97 +604,308 @@ writeCropped(FILE *       const ifP,
 
 
 static void
-determineNewBorders(struct cmdlineInfo const cmdline,
-                    unsigned int       const leftBorderSize,
-                    unsigned int       const rightBorderSize,
-                    unsigned int       const topBorderSize,
-                    unsigned int       const bottomBorderSize,
-                    unsigned int *     const newLeftSizeP,
-                    unsigned int *     const newRightSizeP,
-                    unsigned int *     const newTopSizeP,
-                    unsigned int *     const newBottomSizeP) {
-
-    *newLeftSizeP   = cmdline.left   ? cmdline.margin : leftBorderSize   ;
-    *newRightSizeP  = cmdline.right  ? cmdline.margin : rightBorderSize  ;
-    *newTopSizeP    = cmdline.top    ? cmdline.margin : topBorderSize    ;
-    *newBottomSizeP = cmdline.bottom ? cmdline.margin : bottomBorderSize ;
-}
+fillRowPBM(unsigned char * const bitrow,
+           unsigned int    const cols,
+           unsigned int    const blackWhite) {
+/*----------------------------------------------------------------------------
+   Fill the packed PBM row buffer bitrow[] with 'cols' columns of
+   black or white: black if 'blackWhite' is 1; white if it is '0'.
+   'blackWhite' cannot be anything else.
+-----------------------------------------------------------------------------*/
+    unsigned int const colChars = pbm_packed_bytes(cols);
+    unsigned int i;
+
+    assert(blackWhite == 0 || blackWhite == 1);
+    
+    for (i = 0; i < colChars; ++i)
+        bitrow[i] = blackWhite * 0xff;
         
+    if (cols % 8 > 0)
+        bitrow[colChars-1] <<= 8 - cols % 8;
+}
 
 
-int
-main(int argc, char *argv[]) {
 
-    struct cmdlineInfo cmdline;
-    FILE * ifP;   
-        /* The program's regular input file.  Could be a seekable copy of it
-           in a temporary file.
+static void
+readOffBorderPbm(unsigned int const height,
+                 FILE *       const ifP,
+                 unsigned int const cols,
+                 int          const format) {
+
+    unsigned char * bitrow;
+    unsigned int i;
+
+    bitrow = pbm_allocrow_packed(cols);
+
+    for (i = 0; i < height; ++i)
+        pbm_readpbmrow_packed(ifP, bitrow, cols, format);
+
+    pbm_freerow_packed(bitrow);
+}
+
+
+
+static void
+outputNewBorderPbm(unsigned int const height,
+                   unsigned int const width,
+                   unsigned int const blackWhite,
+                   FILE *       const ofP) {
+/*----------------------------------------------------------------------------
+   Output to 'ofP' a horizontal border (i.e. top or bottom)
+   of height 'height', width 'width'.  Make it black if 'blackWhite' is
+   1; white if 'blackWhite' is 0.  'blackWhite' can't be anything else.
+-----------------------------------------------------------------------------*/
+    unsigned char * bitrow;
+    unsigned int i;
+
+    bitrow = pbm_allocrow_packed(width);
+
+    fillRowPBM(bitrow, width, blackWhite);
+
+    for (i = 0; i < height; ++i)
+        pbm_writepbmrow_packed(ofP, bitrow, width, 0);
+    
+    pbm_freerow_packed(bitrow);
+}
+
+
+
+static void
+writeCroppedPBM(FILE *       const ifP,
+                unsigned int const cols,
+                unsigned int const rows,
+                int          const format,
+                cropSet      const crop,
+                xel          const backgroundColor,
+                FILE *       const ofP) {
+    
+    /* See comments for writeCroppedNonPBM(), which uses identical logic flow. 
+       Uses pbm functions instead of general pnm functions.
+    */
+
+    unsigned int const foregroundCols =
+        cols - crop.op[LEFT].removeSize - crop.op[RIGHT].removeSize;
+    unsigned int const outputCols     = 
+        foregroundCols + crop.op[LEFT].padSize + crop.op[RIGHT].padSize;
+    unsigned int const foregroundRows =
+        rows - crop.op[TOP].removeSize - crop.op[BOTTOM].removeSize;
+    unsigned int const outputRows     =
+        foregroundRows + crop.op[TOP].padSize + crop.op[BOTTOM].padSize;
+
+    unsigned int const foregroundLeft  =
+        MAX(crop.op[LEFT].removeSize, crop.op[LEFT].padSize);
+    unsigned int const foregroundRight = foregroundLeft + foregroundCols;
+
+    unsigned int const allocCols =
+        foregroundRight + 
+        MAX(crop.op[RIGHT].removeSize, crop.op[RIGHT].padSize);
+
+    unsigned int const backgroundBlackWhite =
+        PNM_EQUAL(backgroundColor, pnm_whitexel(1, PBM_TYPE)) ? 0: 1;
+
+    unsigned int const readOffset    =
+        foregroundLeft - crop.op[LEFT].removeSize;
+    unsigned int const writeOffset   = foregroundLeft - crop.op[LEFT].padSize;
+    unsigned int const lastWriteChar = writeOffset/8 + (outputCols-1)/8;
+    unsigned char * bitrow;
+    unsigned int i;
+    
+    pbm_writepbminit(ofP, outputCols, outputRows, 0);
+
+    bitrow = pbm_allocrow_packed(allocCols);
+
+    readOffBorderPbm(crop.op[TOP].removeSize, ifP, cols, format);
+
+    outputNewBorderPbm(crop.op[TOP].padSize, outputCols, backgroundBlackWhite,
+                       ofP);
+
+    /* Prepare padding: left and/or right */
+    fillRowPBM(bitrow, allocCols, backgroundBlackWhite);
+
+    /* Read and output foreground rows */
+    for (i = 0; i < foregroundRows; ++i) {
+        /* Read foreground pixels */
+        pbm_readpbmrow_bitoffset(ifP, bitrow, cols, format, readOffset);
+  
+        pbm_writepbmrow_bitoffset(ofP,
+                                  bitrow, outputCols, format, writeOffset);
+                              
+        /* If there is right-side padding, repair the write buffer
+           distorted by pbm_writepbmrow_bitoffset() 
+           (No need to mend any left-side padding)
         */
+        if (crop.op[RIGHT].padSize > 0)    
+            bitrow[lastWriteChar] = backgroundBlackWhite * 0xff;
+    }
 
-    xelval maxval;
-    int format;
-    int rows, cols;   /* dimensions of input image */
-    bool hasBorders;
-    unsigned int oldLeftBorder, oldRightBorder, oldTopBorder, oldBottomBorder;
-        /* The sizes of the borders in the input image */
-    unsigned int newLeftBorder, newRightBorder, newTopBorder, newBottomBorder;
-        /* The sizes of the borders in the output image */
-    xel background;
-    pm_filepos rasterpos;
+    readOffBorderPbm(crop.op[BOTTOM].removeSize, ifP, cols, format);
 
-    pnm_init(&argc, argv);
+    outputNewBorderPbm(crop.op[BOTTOM].padSize, outputCols,
+                       backgroundBlackWhite,
+                       ofP);
 
-    parseCommandLine(argc, argv, &cmdline);
+    pbm_freerow_packed(bitrow);
+}
 
-    ifP = pm_openr_seekable(cmdline.inputFilespec);
 
-    pnm_readpnminit(ifP, &cols, &rows, &maxval, &format);
 
-    pm_tell2(ifP, &rasterpos, sizeof(rasterpos));
+static void
+determineCrops(struct cmdlineInfo const cmdline,
+               borderSet *        const oldBorderSizeP,
+               cropSet *          const cropP) {
+
+    edgeLocation i;
+
+    for (i = 0; i < 4; ++i) {
+        if (cmdline.wantCrop[i]) {
+            if (oldBorderSizeP->size[i] > cmdline.margin) {
+                cropP->op[i].removeSize =
+                    oldBorderSizeP->size[i] - cmdline.margin;
+                cropP->op[i].padSize    = 0;
+            } else {
+                cropP->op[i].removeSize = 0;
+                cropP->op[i].padSize    =
+                    cmdline.margin - oldBorderSizeP->size[i];
+            }
+        } else {
+            cropP->op[i].removeSize = 0;
+            cropP->op[i].padSize    = 0;
+        }
+    }
+}
 
-    background = computeBackground(ifP, cols, rows, maxval, format,
-                                   cmdline.background, cmdline.verbose);
 
-    if (cmdline.borderfile) {
-        findBordersInFile(cmdline.borderfile,
-                          background, cmdline.verbose, &hasBorders,
-                          &oldLeftBorder, &oldRightBorder,
-                          &oldTopBorder,  &oldBottomBorder);
-    } else {
-        pm_seek2(ifP, &rasterpos, sizeof(rasterpos));
 
-        findBordersInImage(ifP, cols, rows, maxval, format, 
-                           background, cmdline.verbose, &hasBorders,
-                           &oldLeftBorder, &oldRightBorder,
-                           &oldTopBorder,  &oldBottomBorder);
+static void
+validateComputableSize(unsigned int const cols,
+                       unsigned int const rows,
+                       cropSet      const crop) {
+
+    double const newcols =
+        (double)cols +
+        (double)crop.op[LEFT].padSize + (double)crop.op[RIGHT].padSize;
+
+    double const newrows =
+        (double)rows +
+        (double)crop.op[TOP].padSize + (double)crop.op[BOTTOM].padSize;
+
+    if (newcols > INT_MAX)
+       pm_error("Output width too large: %.0f.", newcols);
+    if (newrows > INT_MAX)
+       pm_error("Output height too large: %.0f.", newrows);
+}
+
+
+
+static void
+cropOneImage(struct cmdlineInfo const cmdline,
+             FILE *             const ifP,
+             FILE *             const bdfP,
+             FILE *             const ofP) {
+/*----------------------------------------------------------------------------
+   Crop the image to which the stream *ifP is presently 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
+   (the file that tells us where the existing borders are in the input
+   image).  Leave *ifP and *bdfP positioned after the image.
+
+   Both files are seekable.
+-----------------------------------------------------------------------------*/
+    xelval maxval, bmaxval;
+    int format, bformat;
+    int rows, cols, brows, bcols;
+    bool hasBorders;
+    borderSet oldBorder;
+        /* The sizes of the borders in the input image */
+    cropSet crop;
+        /* The crops we have to do on each side */
+    xel background;
+
+    pnm_readpnminit(ifP, &cols, &rows, &maxval, &format);
+
+    if (bdfP)
+        pnm_readpnminit(bdfP, &bcols, &brows, &bmaxval, &bformat);
+
+    if (bdfP)
+        analyzeImage(bdfP, bcols, brows, bmaxval, bformat, cmdline.background,
+                     FILEPOS_END,
+                     &background, &hasBorders, &oldBorder);
+    else
+        analyzeImage(ifP, cols, rows, maxval, format, cmdline.background,
+                     FILEPOS_BEG,
+                     &background, &hasBorders, &oldBorder);
+
+    if (cmdline.verbose) {
+        pixel const backgroundPixel = pnm_xeltopixel(background, format);
+        pm_message("Background color is %s", 
+                   ppm_colorname(&backgroundPixel, maxval, TRUE /*hexok*/));
     }
     if (!hasBorders)
         pm_error("The image is entirely background; "
                  "there is nothing to crop.");
 
-    determineNewBorders(cmdline, 
-                        oldLeftBorder, oldRightBorder,
-                        oldTopBorder,  oldBottomBorder,
-                        &newLeftBorder, &newRightBorder,
-                        &newTopBorder,  &newBottomBorder);
+    determineCrops(cmdline, &oldBorder, &crop);
+
+    validateComputableSize(cols, rows, crop);
 
     if (cmdline.verbose) 
-        reportCroppingParameters(oldLeftBorder, oldRightBorder,
-                                 oldTopBorder,  oldBottomBorder,
-                                 newLeftBorder, newRightBorder,
-                                 newTopBorder,  newBottomBorder);
+        reportCroppingParameters(crop);
 
-    pm_seek2(ifP, &rasterpos, sizeof(rasterpos));
+    if (PNM_FORMAT_TYPE(format) == PBM_TYPE)
+        writeCroppedPBM(ifP, cols, rows, format, crop, background, ofP);
+    else
+        writeCroppedNonPbm(ifP, cols, rows, maxval, format, crop,
+                           background, ofP);
+}
+
+
+
+int
+main(int argc, const char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;   
+        /* The program's regular input file.  Could be a seekable copy of
+           it in a temporary file.
+        */
+    FILE * bdfP;
+        /* The border file.  NULL if none. */
+    bool eof;    /* no more images in input stream */
+    bool beof;   /* no more images in borderfile stream */
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr_seekable(cmdline.inputFilespec);
 
-    writeCropped(ifP, cols, rows, maxval, format,
-                 oldLeftBorder, oldRightBorder,
-                 oldTopBorder,  oldBottomBorder,
-                 newLeftBorder, newRightBorder,
-                 newTopBorder,  newBottomBorder,
-                 background, stdout);
+    if (cmdline.borderfile)
+        bdfP = pm_openr(cmdline.borderfile);
+    else
+        bdfP = NULL;
+
+    eof = beof = FALSE;
+    while (!eof) {
+        cropOneImage(cmdline, ifP, bdfP, stdout);
+
+        pnm_nextimage(ifP, &eof);
+
+        if (bdfP) {
+            pnm_nextimage(bdfP, &beof);
+            
+            if (eof != beof) {
+                if (!eof)
+                    pm_error("Input file has more images than border file."); 
+                else
+                    pm_error("Border file has more images than image file.");
+            }
+        }
+    }
 
     pm_close(stdout);
     pm_close(ifP);
-    
+    if (bdfP)
+        pm_close(bdfP);
+
     return 0;
 }
diff --git a/editor/pnmcut.c b/editor/pnmcut.c
deleted file mode 100644
index a21fcffb..00000000
--- a/editor/pnmcut.c
+++ /dev/null
@@ -1,427 +0,0 @@
- /* pnmcut.c - cut a rectangle out of a portable anymap
-**
-** Copyright (C) 1989 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 <limits.h>
-#include "pnm.h"
-#include "shhopt.h"
-
-#define UNSPEC INT_MAX
-    /* UNSPEC is the value we use for an argument that is not specified
-       by the user.  Theoretically, the user could specify this value,
-       but we hope not.
-       */
-
-struct cmdline_info {
-    /* All the information the user supplied in the command line,
-       in a form easy for the program to use.
-    */
-    const char *input_filespec;  /* Filespecs of input files */
-
-    /* The following describe the rectangle the user wants to cut out. 
-       the value UNSPEC for any of them indicates that value was not
-       specified.  A negative value means relative to the far edge.
-       'width' and 'height' are not negative.  These specifications 
-       do not necessarily describe a valid rectangle; they are just
-       what the user said.
-       */
-    int left;
-    int right;
-    int top;
-    int bottom;
-    int width;
-    int height;
-    int pad;
-
-    int verbose;
-};
-
-
-
-static xel black_xel;  /* A black xel */
-
-
-static void
-parse_command_line(int argc, char ** argv,
-                   struct cmdline_info *cmdline_p) {
-/*----------------------------------------------------------------------------
-   Note that the file spec array we return is stored in the storage that
-   was passed to us as the argv array.
------------------------------------------------------------------------------*/
-    optStruct *option_def = malloc(100*sizeof(optStruct));
-        /* Instructions to OptParseOptions2 on how to parse our options.
-         */
-    optStruct2 opt;
-
-    unsigned int option_def_index;
-
-    option_def_index = 0;   /* incremented by OPTENTRY */
-    OPTENTRY(0,   "left",       OPT_INT,    &cmdline_p->left,           0);
-    OPTENTRY(0,   "right",      OPT_INT,    &cmdline_p->right,          0);
-    OPTENTRY(0,   "top",        OPT_INT,    &cmdline_p->top,            0);
-    OPTENTRY(0,   "bottom",     OPT_INT,    &cmdline_p->bottom,         0);
-    OPTENTRY(0,   "width",      OPT_INT,    &cmdline_p->width,          0);
-    OPTENTRY(0,   "height",     OPT_INT,    &cmdline_p->height,         0);
-    OPTENTRY(0,   "pad",        OPT_FLAG,   &cmdline_p->pad,            0);
-    OPTENTRY(0,   "verbose",    OPT_FLAG,   &cmdline_p->verbose,        0);
-
-    /* Set the defaults */
-    cmdline_p->left = UNSPEC;
-    cmdline_p->right = UNSPEC;
-    cmdline_p->top = UNSPEC;
-    cmdline_p->bottom = UNSPEC;
-    cmdline_p->width = UNSPEC;
-    cmdline_p->height = UNSPEC;
-    cmdline_p->pad = FALSE;
-    cmdline_p->verbose = FALSE;
-
-    opt.opt_table = option_def;
-    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
-    opt.allowNegNum = TRUE;  /* We may have parms that are negative numbers */
-
-    optParseOptions2(&argc, argv, opt, 0);
-        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
-
-    if (cmdline_p->width < 0)
-        pm_error("-width may not be negative.");
-    if (cmdline_p->height < 0)
-        pm_error("-height may not be negative.");
-
-    if ((argc-1) != 0 && (argc-1) != 1 && (argc-1) != 4 && (argc-1) != 5)
-        pm_error("Wrong number of arguments.  "
-                 "Must be 0, 1, 4, or 5 arguments.");
-
-    switch (argc-1) {
-    case 0:
-        cmdline_p->input_filespec = "-";
-        break;
-    case 1:
-        cmdline_p->input_filespec = argv[1];
-        break;
-    case 4:
-    case 5: {
-        int warg, harg;  /* The "width" and "height" command line arguments */
-
-        if (sscanf(argv[1], "%d", &cmdline_p->left) != 1)
-            pm_error("Invalid number for left column argument");
-        if (sscanf(argv[2], "%d", &cmdline_p->top) != 1)
-            pm_error("Invalid number for top row argument");
-        if (sscanf(argv[3], "%d", &warg) != 1)
-            pm_error("Invalid number for width argument");
-        if (sscanf(argv[4], "%d", &harg) != 1)
-            pm_error("Invalid number for height argument");
-
-        if (warg > 0) {
-            cmdline_p->width = warg;
-            cmdline_p->right = UNSPEC;
-        } else {
-            cmdline_p->width = UNSPEC;
-            cmdline_p->right = warg -1;
-        }
-        if (harg > 0) {
-            cmdline_p->height = harg;
-            cmdline_p->bottom = UNSPEC;
-        } else {
-            cmdline_p->height = UNSPEC;
-            cmdline_p->bottom = harg - 1;
-        }
-
-        if (argc-1 == 4)
-            cmdline_p->input_filespec = "-";
-        else
-            cmdline_p->input_filespec = argv[5];
-        break;
-    }
-    }
-}
-
-
-
-static void
-compute_cut_bounds(const int cols, const int rows,
-                   const int leftarg, const int rightarg, 
-                   const int toparg, const int bottomarg,
-                   const int widtharg, const int heightarg,
-                   int * const leftcol_p, int * const rightcol_p,
-                   int * const toprow_p, int * const bottomrow_p) {
-/*----------------------------------------------------------------------------
-   From the values given on the command line 'leftarg', 'rightarg',
-   'toparg', 'bottomarg', 'widtharg', and 'heightarg', determine what
-   rectangle the user wants cut out.
-
-   Any of these arguments may be UNSPEC to indicate "not specified".
-   Any except 'widtharg' and 'heightarg' may be negative to indicate
-   relative to the far edge.  'widtharg' and 'heightarg' are positive.
-
-   Return the location of the rectangle as *leftcol_p, *rightcol_p,
-   *toprow_p, and *bottomrow_p.  
------------------------------------------------------------------------------*/
-
-    int leftcol, rightcol, toprow, bottomrow;
-        /* The left and right column numbers and top and bottom row numbers
-           specified by the user, except with negative values translated
-           into the actual values.
-
-           Note that these may very well be negative themselves, such
-           as when the user says "column -10" and there are only 5 columns
-           in the image.
-           */
-
-    /* Translate negative column and row into real column and row */
-    /* Exploit the fact that UNSPEC is a positive number */
-
-    if (leftarg >= 0)
-        leftcol = leftarg;
-    else
-        leftcol = cols + leftarg;
-    if (rightarg >= 0)
-        rightcol = rightarg;
-    else
-        rightcol = cols + rightarg;
-    if (toparg >= 0)
-        toprow = toparg;
-    else
-        toprow = rows + toparg;
-    if (bottomarg >= 0)
-        bottomrow = bottomarg;
-    else
-        bottomrow = rows + bottomarg;
-
-    /* Sort out left, right, and width specifications */
-
-    if (leftcol == UNSPEC && rightcol == UNSPEC && widtharg == UNSPEC) {
-        *leftcol_p = 0;
-        *rightcol_p = cols - 1;
-    }
-    if (leftcol == UNSPEC && rightcol == UNSPEC && widtharg != UNSPEC) {
-        *leftcol_p = 0;
-        *rightcol_p = 0 + widtharg - 1;
-    }
-    if (leftcol == UNSPEC && rightcol != UNSPEC && widtharg == UNSPEC) {
-        *leftcol_p = 0;
-        *rightcol_p = rightcol;
-    }
-    if (leftcol == UNSPEC && rightcol != UNSPEC && widtharg != UNSPEC) {
-        *leftcol_p = rightcol - widtharg + 1;
-        *rightcol_p = rightcol;
-    }
-    if (leftcol != UNSPEC && rightcol == UNSPEC && widtharg == UNSPEC) {
-        *leftcol_p = leftcol;
-        *rightcol_p = cols - 1;
-    }
-    if (leftcol != UNSPEC && rightcol == UNSPEC && widtharg != UNSPEC) {
-        *leftcol_p = leftcol;
-        *rightcol_p = leftcol + widtharg - 1;
-    }
-    if (leftcol != UNSPEC && rightcol != UNSPEC && widtharg == UNSPEC) {
-        *leftcol_p = leftcol;
-        *rightcol_p = rightcol;
-    }
-    if (leftcol != UNSPEC && rightcol != UNSPEC && widtharg != UNSPEC) {
-        pm_error("You may not specify left, right, and width.\n"
-                 "Choose at most two of these.");
-    }
-
-
-    /* Sort out top, bottom, and height specifications */
-
-    if (toprow == UNSPEC && bottomrow == UNSPEC && heightarg == UNSPEC) {
-        *toprow_p = 0;
-        *bottomrow_p = rows - 1;
-    }
-    if (toprow == UNSPEC && bottomrow == UNSPEC && heightarg != UNSPEC) {
-        *toprow_p = 0;
-        *bottomrow_p = 0 + heightarg - 1;
-    }
-    if (toprow == UNSPEC && bottomrow != UNSPEC && heightarg == UNSPEC) {
-        *toprow_p = 0;
-        *bottomrow_p = bottomrow;
-    }
-    if (toprow == UNSPEC && bottomrow != UNSPEC && heightarg != UNSPEC) {
-        *toprow_p = bottomrow - heightarg + 1;
-        *bottomrow_p = bottomrow;
-    }
-    if (toprow != UNSPEC && bottomrow == UNSPEC && heightarg == UNSPEC) {
-        *toprow_p = toprow;
-        *bottomrow_p = rows - 1;
-    }
-    if (toprow != UNSPEC && bottomrow == UNSPEC && heightarg != UNSPEC) {
-        *toprow_p = toprow;
-        *bottomrow_p = toprow + heightarg - 1;
-    }
-    if (toprow != UNSPEC && bottomrow != UNSPEC && heightarg == UNSPEC) {
-        *toprow_p = toprow;
-        *bottomrow_p = bottomrow;
-    }
-    if (toprow != UNSPEC && bottomrow != UNSPEC && heightarg != UNSPEC) {
-        pm_error("You may not specify top, bottom, and height.\n"
-                 "Choose at most two of these.");
-    }
-
-}
-
-
-
-static void
-reject_out_of_bounds(const int cols, const int rows, 
-                     const int leftcol, const int rightcol, 
-                     const int toprow, const int bottomrow) {
-
-    /* Reject coordinates off the edge */
-
-    if (leftcol < 0)
-        pm_error("You have specified a left edge (%d) that is beyond\n"
-                 "the left edge of the image (0)", leftcol);
-    if (rightcol > cols-1)
-        pm_error("You have specified a right edge (%d) that is beyond\n"
-                 "the right edge of the image (%d)", rightcol, cols-1);
-    if (rightcol < 0)
-        pm_error("You have specified a right edge (%d) that is beyond\n"
-                 "the left edge of the image (0)", rightcol);
-    if (rightcol > cols-1)
-        pm_error("You have specified a right edge (%d) that is beyond\n"
-                 "the right edge of the image (%d)", rightcol, cols-1);
-    if (leftcol > rightcol) 
-        pm_error("You have specified a left edge (%d) that is to the right\n"
-                 "of the right edge you specified (%d)", 
-                 leftcol, rightcol);
-    
-    if (toprow < 0)
-        pm_error("You have specified a top edge (%d) that is above the top "
-                 "edge of the image (0)", toprow);
-    if (bottomrow > rows-1)
-        pm_error("You have specified a bottom edge (%d) that is below the\n"
-                 "bottom edge of the image (%d)", bottomrow, rows-1);
-    if (bottomrow < 0)
-        pm_error("You have specified a bottom edge (%d) that is above the\n"
-                 "top edge of the image (0)", bottomrow);
-    if (bottomrow > rows-1)
-        pm_error("You have specified a bottom edge (%d) that is below the\n"
-                 "bottom edge of the image (%d)", bottomrow, rows-1);
-    if (toprow > bottomrow) 
-        pm_error("You have specified a top edge (%d) that is below\n"
-                 "the bottom edge you specified (%d)", 
-                 toprow, bottomrow);
-}
-
-
-
-static void
-write_black_rows(FILE *outfile, const int rows, const int cols, 
-                 xel * const output_row,
-                 const pixval maxval, const int format) {
-/*----------------------------------------------------------------------------
-   Write out to file 'outfile' 'rows' rows of 'cols' black xels each,
-   part of an image of format 'format' with maxval 'maxval'.  
-
-   Use *output_row as a buffer.  It is at least 'cols' xels wide.
------------------------------------------------------------------------------*/
-    int row;
-    for (row = 0; row < rows; row++) {
-        int col;
-        for (col = 0; col < cols; col++) output_row[col] = black_xel;
-        pnm_writepnmrow(outfile, output_row, cols, maxval, format, 0);
-    }
-}
-
-
-
-int
-main(int argc, char *argv[]) {
-
-    FILE* ifp;
-    xel* xelrow;  /* Row from input image */
-    xel* output_row;  /* Row of output image */
-    xelval maxval;
-    int rows, cols, format, row;
-    int leftcol, rightcol, toprow, bottomrow;
-    int output_cols;  /* Width of output image */
-    struct cmdline_info cmdline;
-
-    pnm_init( &argc, argv );
-
-    parse_command_line(argc, argv, &cmdline);
-
-    ifp = pm_openr(cmdline.input_filespec);
-
-    pnm_readpnminit(ifp, &cols, &rows, &maxval, &format);
-    xelrow = pnm_allocrow(cols);
-
-    black_xel = pnm_blackxel(maxval, format);
-
-    compute_cut_bounds(cols, rows, 
-                       cmdline.left, cmdline.right, 
-                       cmdline.top, cmdline.bottom, 
-                       cmdline.width, cmdline.height, 
-                       &leftcol, &rightcol, &toprow, &bottomrow);
-
-    if (!cmdline.pad)
-        reject_out_of_bounds(cols, rows, leftcol, rightcol, toprow, bottomrow);
-
-    if (cmdline.verbose) {
-        pm_message("Image goes from Row 0, Column 0 through Row %d, Column %d",
-                   rows-1, cols-1);
-        pm_message("Cutting from Row %d, Column %d through Row %d Column %d",
-                   toprow, leftcol, bottomrow, rightcol);
-    }
-
-    output_cols = rightcol-leftcol+1;
-    output_row = pnm_allocrow(output_cols);
-    
-    pnm_writepnminit(stdout, output_cols, bottomrow-toprow+1, 
-                     maxval, format, 0 );
-
-    /* Implementation note:  If speed is ever an issue, we can probably
-       speed up significantly the non-padding case by writing a special
-       case loop here for the case cmdline.pad == FALSE.
-       */
-
-    /* Write out top padding */
-    write_black_rows(stdout, 0 - toprow, output_cols, output_row, 
-                     maxval, format);
-    
-    /* Read input and write out rows extracted from it */
-    for (row = 0; row < rows; row++) {
-        pnm_readpnmrow(ifp, xelrow, cols, maxval, format);
-        if (row >= toprow && row <= bottomrow) {
-            int col;
-            /* Put in left padding */
-            for (col = leftcol; col < 0; col++) { 
-                output_row[col-leftcol] = black_xel;
-            }
-            /* Put in extracted columns */
-            for (col = MAX(leftcol, 0); col <= MIN(rightcol, cols-1); col++) {
-                output_row[col-leftcol] = xelrow[col];
-            }
-            /* Put in right padding */
-            for (col = MAX(cols, leftcol); col <= rightcol; col++) {
-                output_row[col-leftcol] = black_xel;
-            }
-            pnm_writepnmrow(stdout, output_row, output_cols, 
-                            maxval, format, 0);
-        }
-    }
-    /* Note that we may be tempted just to quit after reaching the bottom
-       of the extracted image, but that would cause a broken pipe problem
-       for the process that's feeding us the image.
-       */
-    /* Write out bottom padding */
-    write_black_rows(stdout, bottomrow - (rows-1), output_cols, output_row, 
-                     maxval, format);
-
-    pnm_freerow(output_row);
-    pnm_freerow(xelrow);
-    pm_close(ifp);
-    pm_close(stdout);
-    
-    exit( 0 );
-}
-
diff --git a/editor/pnmflip b/editor/pnmflip
index 6149aaa2..44d95b45 100755
--- a/editor/pnmflip
+++ b/editor/pnmflip
@@ -57,6 +57,7 @@ foreach (@ARGV) {
         if (defined($infile)) {
             print(STDERR
                   "You may specify at most one non-option parameter.\n");
+            exit(10);
         } else {
             $infile = $_;
         }
diff --git a/editor/pnmgamma.c b/editor/pnmgamma.c
index efdfe039..b079adf1 100644
--- a/editor/pnmgamma.c
+++ b/editor/pnmgamma.c
@@ -13,8 +13,9 @@
 #include <math.h>
 #include <ctype.h>
 
-#include "shhopt.h"
+#include "pm_c_util.h"
 #include "mallocvar.h"
+#include "shhopt.h"
 #include "pnm.h"
 
 enum transferFunction {
@@ -32,7 +33,7 @@ struct cmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
-    const char *filespec;  /* '-' if stdin */
+    const char * filespec;  /* '-' if stdin */
     enum transferFunction transferFunction;
     float rgamma, ggamma, bgamma;
     unsigned int maxval;
@@ -103,13 +104,12 @@ getGammaFromOpts(struct cmdlineInfo * const cmdlineP,
 
     if (gammaSpec)
         if (gammaOpt < 0.0)
-            pm_error("Invalid gamma value.  "
-                         "Must be positive floating point number.");
+            pm_error("Invalid gamma value %f.  Must be positive.", gammaOpt);
     
     if (rgammaSpec) {
         if (cmdlineP->rgamma < 0.0)
-            pm_error("Invalid gamma value.  "
-                     "Must be positive floating point number.");
+            pm_error("Invalid red gamma value %f.  Must be positive.",
+                     cmdlineP->rgamma);
     } else {
         if (gammaSpec)
             cmdlineP->rgamma = gammaOpt;
@@ -118,8 +118,8 @@ getGammaFromOpts(struct cmdlineInfo * const cmdlineP,
     }
     if (ggammaSpec) {
         if (cmdlineP->ggamma < 0.0) 
-            pm_error("Invalid gamma value.  "
-                     "Must be positive floating point number.");
+            pm_error("Invalid green gamma value %f.  Must be positive.",
+                     cmdlineP->ggamma);
     } else {
         if (gammaSpec)
             cmdlineP->ggamma = gammaOpt;
@@ -128,8 +128,8 @@ getGammaFromOpts(struct cmdlineInfo * const cmdlineP,
     }
     if (bgammaSpec) {
         if (cmdlineP->bgamma < 0.0)
-            pm_error("Invalid gamma value.  "
-                     "Must be positive floating point number.");
+            pm_error("Invalid blue gamma value %f.  Must be positive.",
+                     cmdlineP->bgamma);
     } else {
         if (gammaSpec)
             cmdlineP->bgamma = gammaOpt;
diff --git a/editor/pnmhisteq.c b/editor/pnmhisteq.c
index 2c6893bd..1987efc3 100644
--- a/editor/pnmhisteq.c
+++ b/editor/pnmhisteq.c
@@ -12,6 +12,7 @@
 
 #include <string.h>
 
+#include "pm_c_util.h"
 #include "pnm.h"
 #include "shhopt.h"
 #include "mallocvar.h"
diff --git a/editor/pnmindex.sh b/editor/pnmindex.sh
index 15ba1abd..dfc5b82a 100755
--- a/editor/pnmindex.sh
+++ b/editor/pnmindex.sh
@@ -90,9 +90,8 @@ if [ $# -eq 0 ]; then
 fi
 
 tempdir="${TMPDIR-/tmp}/pnmindex.$$"
-mkdir $tempdir || { echo "Could not create temporary file. Exiting."; exit 1;}
-chmod 700 $tempdir
-
+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
diff --git a/editor/pnminvert.test b/editor/pnminvert.test
index 606e4e5c..5534f20d 100644
--- a/editor/pnminvert.test
+++ b/editor/pnminvert.test
@@ -2,7 +2,7 @@ echo Test 1.  Should print 1240379484 41
 ./pnminvert ../testgrid.pbm | cksum
 echo Test 2.  Should print 1416115901 101484
 ./pnminvert ../testimg.ppm | cksum
-echo Test 3.  Should print 4215652354 33838
+echo Test 3.  Should print 2961441369 33838
 ppmtopgm ../testimg.ppm | ./pnminvert | cksum
 echo Test 4.  Should print 2595564405 14
 pbmmake -w 7 7 | ./pnminvert | cksum
diff --git a/editor/pnmmargin b/editor/pnmmargin
index 31420f99..b31deefd 100755
--- a/editor/pnmmargin
+++ b/editor/pnmmargin
@@ -12,9 +12,8 @@
 # implied warranty.
 
 tempdir="${TMPDIR-/tmp}/pnmmargin.$$"
-mkdir $tempdir || { echo "Could not create temporary file. Exiting."; exit 1;}
-chmod 700 $tempdir
-
+mkdir -m 0700 $tempdir || \
+  { echo "Could not create temporary file. Exiting." 1>&2; exit 1;}
 trap 'rm -rf $tempdir' 0 1 3 15
 
 tmp1=$tempdir/pnmm1
@@ -23,22 +22,27 @@ tmp3=$tempdir/pnmm3
 tmp4=$tempdir/pnmm4
 
 color="-gofigure"
+plainopt=""
 
 # Parse args.
 while true ; do
     case "$1" in
-	-w* )
+        -p|-pl|-pla|-plai|-plain )
+        plainopt="-plain"
+        shift
+        ;;
+	-w|-wh|-whi|-whit|-white )
 	color="-white"
 	shift
 	;;
-	-b* )
+	-b|-bl|-bla|-blac|-black )
 	color="-black"
 	shift
 	;;
-	-c* )
+	-c|-co|-col|-colo|-color )
 	shift
 	if [ ! ${1-""} ] ; then
-	    echo "usage: $0 [-white|-black|-color <colorspec>] <size> [pnmfile]" 1>&2
+       	    echo "usage: $0 [-white|-black|-color <colorspec>] <size> [pnmfile]" 1>&2
 	    exit 1
 	fi
 	color="$1"
@@ -67,22 +71,38 @@ if [ ${2-""} ] ; then
 fi
 
 # Capture input file in a tmp file, in case it's a pipe.
+# TODO: This code does not consider the case when the input file is specified
+# and there is also input coming in from a pipe.
+
 cat $@ > $tmp1
 
-# Construct spacer files.
-case "$color" in
-    -gofigure )
-    pnmcut 0 0 1 1 $tmp1 | pnmtile $size 1 > $tmp2
-    ;;
-    -white | -black )
-    pbmmake $color $size 1 > $tmp2
-    ;;
-    * )
-    ppmmake $color $size 1 > $tmp2
-    ;;
-esac
-pamflip -rotate90 $tmp2 > $tmp3
+if [ $size -eq 0 ] ; then
+    # Zero margin; just copy input to output
+    pamtopnm $plainopt $tmp1;
+else
+    # If -white or -black, write output with pnmpad and exit.
+    # Otherwise construct spacer files.
+    
+    case "$color" in
+        -gofigure )
+        pnmcut 0 0 1 1 $tmp1 | pnmtile $size 1 > $tmp2
+        ;;
+        -white | -black )
+        pnmpad $plainopt $color \
+            -left=$size -right=$size -top=$size -bottom=$size $tmp1
+        exit
+        ;;
+        * )
+        ppmmake $color $size 1 > $tmp2
+        ;;
+    esac
+    pamflip -rotate90 $tmp2 > $tmp3
+    
+    # Cat things together.
+    pnmcat -lr $tmp2 $tmp1 $tmp2 > $tmp4
+    pnmcat -tb $plainopt $tmp3 $tmp4 $tmp3
+fi
+
+
+
 
-# Cat things together.
-pnmcat -lr $tmp2 $tmp1 $tmp2 > $tmp4
-pnmcat -tb $tmp3 $tmp4 $tmp3
diff --git a/editor/pnmmontage.c b/editor/pnmmontage.c
index 8916d251..2e30a43b 100644
--- a/editor/pnmmontage.c
+++ b/editor/pnmmontage.c
@@ -10,19 +10,136 @@
  * implied warranty.
  */
 
+#define _BSD_SOURCE  /* Make sure strdup() is in <string.h> */
+#include <assert.h>
 #include <limits.h>
 #include <string.h>
 
-#include "pam.h"
-#include "shhopt.h"
-#include "nstring.h"
+#include "pm_c_util.h"
 #include "mallocvar.h"
+#include "nstring.h"
+#include "shhopt.h"
+#include "pam.h"
+
+
+
+struct cmdlineInfo {
+    const char * header;
+    const char * data;
+    const char * prefix;
+    unsigned int quality;
+    unsigned int quality2;
+    unsigned int nFiles;
+    const char ** inFileName;
+};
+
+
+
+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 OptParseOptions3 on how to parse our options. */
+    optStruct3 opt;
+    unsigned int dataSpec, headerSpec, prefixSpec, qualitySpec;
+    unsigned int option_def_index;
+    unsigned int i;
+    unsigned int q[10];
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+  
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3( 0,  "data",    OPT_STRING, &cmdlineP->data, &dataSpec, 0);
+    OPTENT3( 0,  "header",  OPT_STRING, &cmdlineP->header, &headerSpec, 0);
+    OPTENT3('q', "quality", OPT_UINT,   &cmdlineP->quality,   &qualitySpec, 0);
+    OPTENT3('p', "prefix",  OPT_STRING, &cmdlineP->prefix,    &prefixSpec, 0);
+    OPTENT3('0', "0",       OPT_FLAG,   NULL, &q[0],      0);
+    OPTENT3('1', "1",       OPT_FLAG,   NULL, &q[1],      0);
+    OPTENT3('2', "2",       OPT_FLAG,   NULL, &q[2],      0);
+    OPTENT3('3', "3",       OPT_FLAG,   NULL, &q[3],      0);
+    OPTENT3('4', "4",       OPT_FLAG,   NULL, &q[4],      0);
+    OPTENT3('5', "5",       OPT_FLAG,   NULL, &q[5],      0);
+    OPTENT3('6', "6",       OPT_FLAG,   NULL, &q[6],      0);
+    OPTENT3('7', "7",       OPT_FLAG,   NULL, &q[7],      0);
+    OPTENT3('8', "8",       OPT_FLAG,   NULL, &q[8],      0);
+    OPTENT3('9', "9",       OPT_FLAG,   NULL, &q[9],      0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;
+    opt.allowNegNum = FALSE;
+
+    optParseOptions3(&argc, (char**)argv, opt, sizeof(opt), 0);
+
+    if (!dataSpec)
+        cmdlineP->data = NULL;
+    if (!headerSpec)
+        cmdlineP->header = NULL;
+    if (!prefixSpec)
+        cmdlineP->prefix = "";
+    if (!qualitySpec)
+        cmdlineP->quality = 200;
+
+    
+    /* cmdlineP->quality2 is the greatest number from the --1, --2, etc.
+       options, or 5 if none of those are specified.
+    */
+    cmdlineP->quality2 = 5;  /* initial value */
+    for (i = 0; i < 10; ++i) {
+        if (q[i])
+            cmdlineP->quality2 = i;
+    }
+
+    cmdlineP->nFiles = argc-1;
 
-typedef struct { int f[sizeof(int) * 8 + 1]; } factorset;
-typedef struct { int x; int y; } coord;
+    MALLOCARRAY_NOFAIL(cmdlineP->inFileName, argc-1);
 
-static int qfactor = 200;
-static int quality = 5;
+    for (i = 0; i < argc-1; ++i) {
+        if (cmdlineP->data && strchr(argv[i+1], ':'))
+            pm_error("Filename '%s' contains a \":\", which is forbidden "
+                     "with -data", argv[i+1]);
+        else
+            cmdlineP->inFileName[i] = strdup(argv[i+1]);
+    }
+}
+
+
+
+typedef struct {
+    int f[sizeof(int) * 8 + 1];
+} factorset;
+
+typedef struct {
+    int x; int y;
+} coord;
+
+typedef struct {
+    coord ul;
+    coord size;
+} rectangle;
+
+static coord
+lr(rectangle const r) {
+/*----------------------------------------------------------------------------
+   Return the coordinates of the lower right corner of 'r'
+   (i.e. the pixel just beyond the lowest rightmost one).
+-----------------------------------------------------------------------------*/
+    coord retval;
+
+    retval.x = r.ul.x + r.size.x;
+    retval.y = r.ul.y + r.size.y;
+
+    return retval;
+}
 
 static factorset 
 factor(int n)
@@ -63,109 +180,151 @@ gcd(int n, int m)
   return (g);
 }
 
-static __inline__ int imax(int n, int m) { return (n > m ? n : m); }
 
-static int 
-checkcollision(coord *locs, coord *szs, coord *cloc, coord *csz, int n)
-{
-  int i;
-  for (i = 0; i < n; ++i)
-  {
-    if ((locs[i].x < cloc->x + csz->x) &&
-        (locs[i].y < cloc->y + csz->y) &&
-        (locs[i].x + szs[i].x > cloc->x) &&
-        (locs[i].y + szs[i].y > cloc->y))
-      return (1);
-  }
-  return (0);
+
+static bool
+overlaps(rectangle const a,
+         rectangle const b) {
+
+    return
+        (a.ul.x < lr(b).x && a.ul.y < lr(b).y) &&
+        (lr(a).x > b.ul.x && lr(a).y > b.ul.y);
+}
+
+
+
+static bool
+collides(rectangle         const test,
+         const rectangle * const fieldList,
+         unsigned int      const n) {
+/*----------------------------------------------------------------------------
+   Return true iff the rectangle 'test' overlaps any of the 'n' rectangles
+   fieldList[].
+-----------------------------------------------------------------------------*/
+    unsigned int i;
+
+    for (i = 0; i < n; ++i)
+        if (overlaps(fieldList[i], test))
+            return true;
+
+    return false;
 }
 
+
+
 static void 
-recursefindpack(coord *current, coord currentsz, coord *set, 
-                coord *best, int minarea, int *maxarea, 
-                int depth, int n, int xinc, int yinc)
-{
-  coord c;
-  if (depth == n)
-  {
-    if (currentsz.x * currentsz.y < *maxarea)
-    {
-      memcpy(best, current, sizeof(coord) * n);
-      *maxarea = currentsz.x * currentsz.y;
-    }
-    return;
-  }
+recursefindpack(rectangle *    const current,
+                coord          const currentsz,
+                coord *        const best,
+                unsigned int   const minarea,
+                unsigned int * const maxareaP, 
+                unsigned int   const depth,
+                unsigned int   const n,
+                unsigned int   const xinc,
+                unsigned int   const yinc,
+                unsigned int   const quality,
+                unsigned int   const qfactor) {
+
+    if (depth == n) {
+        if (currentsz.x * currentsz.y < *maxareaP) {
+            unsigned int i;
+            for (i = 0; i < n; ++i)
+                best[i] = current[i].ul;
+            *maxareaP = currentsz.x * currentsz.y;
+        }
+    } else {
+        unsigned int i;
 
-  for (current[depth].x = 0; 
-       imax(current[depth].x + set[depth].x, currentsz.x) * 
-           imax(currentsz.y, set[depth].y) < *maxarea; 
-       current[depth].x += xinc)
-  {
-    for (current[depth].y = 0; 
-         imax(current[depth].x + set[depth].x, currentsz.x) * 
-             imax(currentsz.y, current[depth].y + set[depth].y) < *maxarea; 
-         current[depth].y += yinc)
-    {
-      c.x = imax(current[depth].x + set[depth].x, currentsz.x);
-      c.y = imax(current[depth].y + set[depth].y, currentsz.y);
-      if (!checkcollision(current, set, &current[depth], &set[depth], depth))
-      {
-        recursefindpack(current, c, set, best, minarea, maxarea, 
-                        depth + 1, n, xinc, yinc);
-        if (*maxarea <= minarea)
-          return;
-      }
+        rectangle * const newP = &current[depth];
+
+        for (i = 0; ; ++i) {
+            for (newP->ul.x = 0, newP->ul.y = i * yinc;
+                 newP->ul.y <= i * yinc;) {
+
+                coord c;
+
+                c.x = MAX(lr(*newP).x, currentsz.x);
+                c.y = MAX(lr(*newP).y, currentsz.y);
+                pm_message("current = (%u.%u, %u.%u) new = (%u.%u, %u.%u)",
+                           current[0].ul.x, current[0].size.x,
+                           current[0].ul.y, current[0].size.y,
+                           newP->ul.x,   newP->size.x,
+                           newP->ul.y,   newP->size.y);
+                if (!collides(*newP, current, depth)) {
+                    pm_message("Depth %u: Doesn't collide at i=%u", depth,i);
+                    recursefindpack(current, c, best, minarea, maxareaP,
+                                    depth + 1, n, xinc, yinc,
+                                    quality, qfactor);
+                    if (*maxareaP <= minarea)
+                        return;
+                }
+                if (newP->ul.x == (i - 1) * xinc)
+                    newP->ul.y = 0;
+                if (newP->ul.x < i * xinc)
+                    newP->ul.x += xinc;
+                else
+                    newP->ul.y += yinc;
+            }
+        }
     }
-  }
 }
 
+
+
 static void 
-findpack(struct pam *imgs, int n, coord *coords)
-{
-  int minarea;
-  int i;
-  int rdiv;
-  int cdiv;
-  int minx = -1;
-  int miny = -1;
-  coord *current;
-  coord *set;
-  int z = INT_MAX;
-  coord c = { 0, 0 };
-
-  if (quality > 1)
-  {
-    for (minarea = i = 0; i < n; ++i)
-      minarea += imgs[i].height * imgs[i].width,
-      minx = imax(minx, imgs[i].width),
-      miny = imax(miny, imgs[i].height);
+findpack(struct pam * const imgs,
+         unsigned int const n,
+         coord *      const coords,
+         unsigned int const quality,
+         unsigned int const qfactor) {
 
-    minarea = minarea * qfactor / 100;
-  }
-  else
-  {
-    minarea = INT_MAX - 1;
-  }
+    int minarea;
+    int i;
+    int rdiv;
+    int cdiv;
+    int minx;
+    int miny;
+    rectangle * current;
+    unsigned int z;
+    coord c;
+
+    minx = -1; miny = -1;  /* initial value */
+    z = UINT_MAX;  /* initial value */
+    c.x = 0; c.y = 0;  /* initial value */
+
+    if (quality > 1) {
+        unsigned int realMinarea;
+        for (realMinarea = i = 0; i < n; ++i)
+            realMinarea += imgs[i].height * imgs[i].width,
+                minx = MAX(minx, imgs[i].width),
+                miny = MAX(miny, imgs[i].height);
+
+        minarea = realMinarea * qfactor / 100;
+    } else {
+        minarea = INT_MAX - 1;
+    }
 
-  /* It's relatively easy to show that, if all the images
-   * are multiples of a particular size, then a best
-   * packing will always align the images on a grid of
-   * that size.
-   *
-   * This speeds computation immensely.
-   */
-  for (rdiv = imgs[0].height, i = 1; i < n; ++i)
-    rdiv = gcd(imgs[i].height, rdiv);
-
-  for (cdiv = imgs[0].width, i = 1; i < n; ++i)
-    cdiv = gcd(imgs[i].width, cdiv);
-
-  MALLOCARRAY(current, n);
-  MALLOCARRAY(set, n);
-  for (i = 0; i < n; ++i)
-    set[i].x = imgs[i].width,
-    set[i].y = imgs[i].height;
-  recursefindpack(current, c, set, coords, minarea, &z, 0, n, cdiv, rdiv);
+    /* It's relatively easy to show that, if all the images
+     * are multiples of a particular size, then a best
+     * packing will always align the images on a grid of
+     * that size.
+     *
+     * This speeds computation immensely.
+     */
+    for (rdiv = imgs[0].height, i = 1; i < n; ++i)
+        rdiv = gcd(imgs[i].height, rdiv);
+
+    for (cdiv = imgs[0].width, i = 1; i < n; ++i)
+        cdiv = gcd(imgs[i].width, cdiv);
+
+    MALLOCARRAY(current, n);
+
+    for (i = 0; i < n; ++i) {
+        current[i].size.x = imgs[i].width;
+        current[i].size.y = imgs[i].height;
+    }
+    recursefindpack(current, c, coords, minarea, &z, 0, n, cdiv, rdiv,
+                    quality, qfactor);
 }
 
 
@@ -255,202 +414,263 @@ writePam(struct pam *       const outpamP,
 
 
 
-int 
-main(int argc, char **argv)
-{
-  struct pam *imgs;
-  struct pam outimg;
-  struct pam p;
-  int nfiles;
-  int i, j;
-  unsigned int q[10];
-  coord *coords;
-  const char *headfname = NULL;
-  const char *datafname = NULL;
-  const char *prefix = "";
-  FILE *header;
-  FILE *data;
-  char **names;
-  char *c;
-
-  optEntry *option_def = malloc(100*sizeof(optEntry));
-      /* Instructions to OptParseOptions3 on how to parse our options.
-       */
-  optStruct3 opt;
-
-  unsigned int option_def_index;
-
-  option_def_index = 0;   /* incremented by OPTENTRY */
-  OPTENT3( 0,  "data",    OPT_STRING, &datafname, NULL, 0);
-  OPTENT3( 0,  "header",  OPT_STRING, &headfname, NULL, 0);
-  OPTENT3('q', "quality", OPT_UINT,   &qfactor,   NULL, 0);
-  OPTENT3('p', "prefix",  OPT_STRING, &prefix,    NULL, 0);
-  OPTENT3('0', "0",       OPT_FLAG,   NULL, &q[0],      0);
-  OPTENT3('1', "1",       OPT_FLAG,   NULL, &q[1],      0);
-  OPTENT3('2', "2",       OPT_FLAG,   NULL, &q[2],      0);
-  OPTENT3('3', "3",       OPT_FLAG,   NULL, &q[3],      0);
-  OPTENT3('4', "4",       OPT_FLAG,   NULL, &q[4],      0);
-  OPTENT3('5', "5",       OPT_FLAG,   NULL, &q[5],      0);
-  OPTENT3('6', "6",       OPT_FLAG,   NULL, &q[6],      0);
-  OPTENT3('7', "7",       OPT_FLAG,   NULL, &q[7],      0);
-  OPTENT3('8', "8",       OPT_FLAG,   NULL, &q[8],      0);
-  OPTENT3('9', "9",       OPT_FLAG,   NULL, &q[9],      0);
-
-  opt.opt_table = option_def;
-  opt.short_allowed = FALSE;
-  opt.allowNegNum = FALSE;
-
-  pnm_init(&argc, argv);
-
-  /* Check for flags. */
-  optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
-
-  if (headfname)
-    header = pm_openw(headfname);
-
-  if (datafname)
-    data = pm_openw(datafname);
-
-  for (i = 0; i < 10; ++i)
-  {
-    if (q[i])
-    {
-      quality = i;
-      switch (quality)
-      {
-        case 0: case 1: break;
-        case 2: case 3: case 4: case 5: case 6: 
-            qfactor = 100 * (8 - quality); 
-            break;
-        case 7: qfactor = 150; break;
-        case 8: qfactor = 125; break;
-        case 9: qfactor = 100; break;
-      }
+static void
+writeData(FILE *             const dataFileP,
+          unsigned int       const width,
+          unsigned int       const height,
+          unsigned int       const nfiles,
+          const char **      const names,
+          const coord *      const coords,
+          const struct pam * const imgs) {
+
+    unsigned int i;
+
+    fprintf(dataFileP, ":0:0:%u:%u\n", width, height);
+
+    for (i = 0; i < nfiles; ++i) {
+        fprintf(dataFileP, "%s:%u:%u:%u:%u\n", names[i], coords[i].x,
+                coords[i].y, imgs[i].width, imgs[i].height);
     }
-  }
+}
 
-  if (1 < argc)
-    nfiles = argc - 1;
-  else
-    nfiles = 1;
 
-  MALLOCARRAY(imgs, nfiles);
-  MALLOCARRAY(coords, nfiles);
-  MALLOCARRAY(names, nfiles);
-  
-  if (!imgs || !coords || !names)
-    pm_error("out of memory");
 
-  if (1 < argc)
-  {
-    for (i = 0; i < nfiles; ++i)
-    {
-      if (strchr(argv[i+1], ':'))
-      {
-        imgs[i].file = pm_openr(strchr(argv[i+1], ':') + 1);
-        *strchr(argv[i+1], ':') = 0;
-        names[i] = argv[i+1];
-      }
-      else
-      {
-        imgs[i].file = pm_openr(argv[i+1]);
-        names[i] = argv[i+1];
-      }
+static void
+writeHeader(FILE * const headerFileP,
+            const char * const prefix,
+            unsigned int const width,
+            unsigned int const height,
+            unsigned int const nfiles,
+            const char ** const names,
+            const coord * const coords,
+            const struct pam * imgs) {
+
+    unsigned int i;
+
+    fprintf(headerFileP, "#define %sOVERALLX %u\n", prefix, width);
+
+    fprintf(headerFileP, "#define %sOVERALLY %u\n", prefix, height);
+
+    fprintf(headerFileP, "\n");
+
+    for (i = 0; i < nfiles; ++i) {
+        char * const buffer = strdup(names[i]);
+        coord const coord = coords[i];
+        struct pam const img = imgs[i];
+
+        unsigned int j;
+        
+        *strchr(buffer, '.') = 0;
+        for (j = 0; buffer[j]; ++j) {
+            if (ISLOWER(buffer[j]))
+                buffer[j] = TOUPPER(buffer[j]);
+        }
+        fprintf(headerFileP, "#define %s%sX %u\n", 
+                prefix, buffer, coord.x);
+
+        fprintf(headerFileP, "#define %s%sY %u\n",
+                prefix, buffer, coord.y);
+
+        fprintf(headerFileP, "#define %s%sSZX %u\n",
+                prefix, buffer, img.width);
+
+        fprintf(headerFileP, "#define %s%sSZY %u\n",
+                prefix, buffer, img.height);
+
+        fprintf(headerFileP, "\n");
     }
-  }
-  else
-  {
-    imgs[0].file = stdin;
-  }
+}
 
-  pnm_readpaminit(imgs[0].file, &imgs[0], PAM_STRUCT_SIZE(tuple_type));
-  outimg.maxval = imgs[0].maxval;
-  outimg.format = imgs[0].format;
-  memcpy(outimg.tuple_type, imgs[0].tuple_type, sizeof(imgs[0].tuple_type));
-  outimg.depth = imgs[0].depth;
 
-  for (i = 1; i < nfiles; ++i)
-  {
-    pnm_readpaminit(imgs[i].file, &imgs[i], PAM_STRUCT_SIZE(tuple_type));
-    if (PAM_FORMAT_TYPE(imgs[i].format) > PAM_FORMAT_TYPE(outimg.format))
-      outimg.format = imgs[i].format,
-      memcpy(outimg.tuple_type, imgs[i].tuple_type, 
-             sizeof(imgs[i].tuple_type));
-    outimg.maxval = imax(imgs[i].maxval, outimg.maxval);
-    outimg.depth = imax(imgs[i].depth, outimg.depth);
-  }
 
-  for (i = 0; i < nfiles - 1; ++i)
-    for (j = i + 1; j < nfiles; ++j)
-      if (imgs[j].width * imgs[j].height > imgs[i].width * imgs[i].height)
-        p = imgs[i], imgs[i] = imgs[j], imgs[j] = p,
-        c = names[i], names[i] = names[j], names[j] = c;
+static void
+sortImagesByArea(unsigned int  const nfiles,
+                 struct pam *  const imgs,
+                 const char ** const names) {
+/*----------------------------------------------------------------------------
+   Sort the images described by 'imgs' and 'names' in place, from largest
+   area to smallest.
+-----------------------------------------------------------------------------*/
+    /* Bubble sort */
+
+    unsigned int i;
+
+    for (i = 0; i < nfiles - 1; ++i) {
+        unsigned int j;
+        for (j = i + 1; j < nfiles; ++j) {
+            if (imgs[j].width * imgs[j].height >
+                imgs[i].width * imgs[i].height) {
+
+                struct pam p;
+                const char * c;
+                
+                p = imgs[i]; imgs[i] = imgs[j]; imgs[j] = p;
+                c = names[i]; names[i] = names[j]; names[j] = c;
+            }
+        }
+    }
+}
 
-  findpack(imgs, nfiles, coords);
 
-  outimg.height = outimg.width = 0;
-  for (i = 0; i < nfiles; ++i)
-  {
-    outimg.width = imax(outimg.width, imgs[i].width + coords[i].x);
-    outimg.height = imax(outimg.height, imgs[i].height + coords[i].y);
-  }
 
-  outimg.size = sizeof(outimg);
-  outimg.len = PAM_STRUCT_SIZE(allocation_depth);
-  pnm_setminallocationdepth(&outimg, outimg.depth);
-  outimg.plainformat = false;
-  outimg.file = stdout;
+static void
+computeOutputType(sample *           const maxvalP,
+                  int *              const formatP,
+                  char *             const tupleTypeP,
+                  unsigned int *     const depthP,
+                  unsigned int       const nfiles,
+                  const struct pam * const imgs) {
+
+    unsigned int i;
+
+    sample maxval;
+    int format;
+    const char * tupleType;
+    unsigned int depth;
+
+    assert(nfiles > 0);
+
+    /* initial guesses */
+    maxval    = imgs[0].maxval;
+    format    = imgs[0].format;
+    depth     = imgs[0].depth;
+    tupleType = imgs[0].tuple_type;
+
+    for (i = 1; i < nfiles; ++i) {
+        if (PAM_FORMAT_TYPE(imgs[i].format) > PAM_FORMAT_TYPE(format)) {
+            format    = imgs[i].format;
+            tupleType = imgs[i].tuple_type;
+        }
+        maxval = MAX(maxval, imgs[i].maxval);
+        depth  = MAX(depth,  imgs[i].depth);
+    }
+
+    *maxvalP = maxval;
+    *formatP = format;
+    *depthP  = depth;
+    memcpy(tupleTypeP, tupleType, sizeof(imgs[0].tuple_type));
+}
 
-  writePam(&outimg, nfiles, coords, imgs);
 
-  if (datafname)
-  {
-    fprintf(data, ":0:0:%u:%u\n", outimg.width, outimg.height);
 
-    for (i = 0; i < nfiles; ++i)
-    {
-      fprintf(data, "%s:%u:%u:%u:%u\n", names[i], coords[i].x,
-          coords[i].y, imgs[i].width, imgs[i].height);
+static void
+computeOutputDimensions(int * const widthP,
+                        int * const heightP,
+                        unsigned int const nfiles,
+                        const struct pam * const imgs,
+                        const coord * const coords) {
+
+    unsigned int widthGuess, heightGuess;
+    unsigned int i;
+
+    widthGuess  = 0;  /* initial value */
+    heightGuess = 0;  /* initial value */
+    
+    for (i = 0; i < nfiles; ++i) {
+        widthGuess  = MAX(widthGuess,  imgs[i].width  + coords[i].x);
+        heightGuess = MAX(heightGuess, imgs[i].height + coords[i].y);
     }
-  }
 
-  if (headfname)
-  {
-    fprintf(header, "#define %sOVERALLX %u\n"
-                    "#define %sOVERALLY %u\n"
-                    "\n",
-                    prefix, outimg.width,
-                    prefix, outimg.height);
+    *widthP  = widthGuess;
+    *heightP = heightGuess;
+}
 
-    for (i = 0; i < nfiles; ++i)
-    {
-      *strchr(names[i], '.') = 0;
-      for (j = 0; names[i][j]; ++j)
-      {
-        if (ISLOWER(names[i][j]))
-          names[i][j] = TOUPPER(names[i][j]);
-      }
-      fprintf(header, "#define %s%sX %u\n"
-                      "#define %s%sY %u\n"
-                      "#define %s%sSZX %u\n"
-                      "#define %s%sSZY %u\n"
-                      "\n",
-                      prefix, names[i], coords[i].x,
-                      prefix, names[i], coords[i].y,
-                      prefix, names[i], imgs[i].width,
-                      prefix, names[i], imgs[i].height);
+
+
+int 
+main(int argc, const char **argv) {
+
+    struct cmdlineInfo cmdline;
+    struct pam * imgs;
+    struct pam outimg;
+    unsigned int nfiles;
+    coord * coords;
+    FILE * header;
+    FILE * data;
+    const char ** names;
+    unsigned int i;
+    unsigned int qfactor;  /* In per cent */
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    header = cmdline.header ? pm_openw(cmdline.header) : NULL;
+    data = cmdline.data ? pm_openw(cmdline.data) : NULL;
+
+    switch (cmdline.quality2) {
+    case 0: case 1:
+        qfactor = cmdline.quality;
+        break;
+    case 2: case 3: case 4: case 5: case 6: 
+        qfactor = 100 * (8 - cmdline.quality2); 
+        break;
+    case 7: qfactor = 150; break;
+    case 8: qfactor = 125; break;
+    case 9: qfactor = 100; break;
+    default: pm_error("Internal error - impossible value of 'quality2': %u",
+                      cmdline.quality2);
     }
-  }
 
-  for (i = 0; i < nfiles; ++i)
-    pm_close(imgs[i].file);
-  pm_close(stdout);
+    nfiles = cmdline.nFiles > 0 ? cmdline.nFiles : 1;
 
-  if (headfname)
-    pm_close(header);
+    MALLOCARRAY(imgs, nfiles);
+    MALLOCARRAY(coords, nfiles);
+    MALLOCARRAY(names, nfiles);
+  
+    if (!imgs || !coords || !names)
+        pm_error("out of memory");
+
+    if (cmdline.nFiles > 0) {
+        unsigned int i;
+
+        for (i = 0; i < cmdline.nFiles; ++i) {
+            imgs[i].file = pm_openr(cmdline.inFileName[i]);
+            names[i] = strdup(cmdline.inFileName[i]);
+        }
+    } else {
+        imgs[0].file = stdin;
+        names[0] = strdup("stdin");
+    }
+
+    for (i = 0; i < nfiles; ++i)
+        pnm_readpaminit(imgs[i].file, &imgs[i], PAM_STRUCT_SIZE(tuple_type));
 
-  if (datafname)
-    pm_close(data);
+    sortImagesByArea(nfiles, imgs, names);
 
-  return 0;
+    findpack(imgs, nfiles, coords, cmdline.quality2, qfactor);
+
+    computeOutputType(&outimg.maxval, &outimg.format, outimg.tuple_type,
+                      &outimg.depth, nfiles, imgs);
+
+    computeOutputDimensions(&outimg.width, &outimg.height, nfiles,
+                            imgs, coords);
+
+    pnm_setminallocationdepth(&outimg, outimg.depth);
+
+    outimg.size = sizeof(outimg);
+    outimg.len = PAM_STRUCT_SIZE(allocation_depth);
+    pnm_setminallocationdepth(&outimg, outimg.depth);
+    outimg.plainformat = false;
+    outimg.file = stdout;
+ 
+    writePam(&outimg, nfiles, coords, imgs);
+
+    if (data)
+        writeData(data, outimg.width, outimg.height,
+                  nfiles, names, coords, imgs);
+
+    if (header)
+        writeHeader(header, cmdline.prefix, outimg.width, outimg.height,
+                    nfiles, names, coords, imgs);
+
+    for (i = 0; i < nfiles; ++i)
+        pm_close(imgs[i].file);
+    pm_close(stdout);
+    if (header)
+        pm_close(header);
+    if (data)
+        pm_close(data);
+
+    return 0;
 }
diff --git a/editor/pnmnlfilt.c b/editor/pnmnlfilt.c
index 20705f82..bde0cd82 100644
--- a/editor/pnmnlfilt.c
+++ b/editor/pnmnlfilt.c
@@ -52,6 +52,63 @@
 #include "pm_c_util.h"
 #include "pnm.h"
 
+struct cmdlineInfo {
+    const char * inputFileName;
+    double alpha;
+    double radius;
+};
+
+
+static void 
+parseCommandLine(int argc, 
+                 char ** argv, 
+                 struct cmdlineInfo  * const cmdlineP) {
+
+    if (argc-1 < 2)
+        pm_error("You must specify at least two arguments: alpha and radius.  "
+                 "You specified %u", argc-1);
+
+    if (sscanf(argv[1], "%lf", &cmdlineP->alpha) != 1)
+        pm_error("Invalid alpha (1st) argument '%s'.  "
+                 "Must be a decimal number",
+                 argv[1]);
+
+    if (sscanf( argv[2], "%lf", &cmdlineP->radius ) != 1)
+        pm_error("Invalid radius (2nd) argument '%s'.  "
+                 "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 "
+                  "for alpha trimmed mean.  "
+                  "You specified %f", cmdlineP->alpha);
+    if (cmdlineP->alpha > 2.0)
+        pm_error("Alpha must be in range 1.0 <= cmdlineP->alpha <= 2.0 "
+                  "for optimal estimation.  You specified %f",
+                 cmdlineP->alpha);
+
+    if (cmdlineP->alpha < -0.9 ||
+        (cmdlineP->alpha > -0.1 && cmdlineP->alpha < 0.0))
+        pm_error( "Alpha must be in range -0.9 <= alpha <= -0.1 "
+                  "for edge enhancement.  You specified %f",
+                  cmdlineP->alpha);
+
+    if (cmdlineP->radius < 1.0/3 || cmdlineP->radius > 1.0)
+        pm_error("Radius must be in range 1/3 <= radius <= 1. "
+                 "You specified %f", cmdlineP->radius);
+
+    if (argc-1 < 3)
+        cmdlineP->inputFileName = "-";
+    else
+        cmdlineP->inputFileName = argv[3];
+
+    if (argc-1 > 3)
+        pm_error("Too many arguments: %u.  The most allowed are 3: alpha, "
+                 "radius, and file name", argc-1);
+}
+
+
 /* MXIVAL is the maximum input sample value we can handle.
    It is limited by our willingness to allocate storage in various arrays
    that are indexed by sample values.
@@ -76,7 +133,6 @@ int noisevariance;
 */
 static  xel *irows[3];
 static  xel *irow0, *irow1, *irow2, *orow;
-static  double radius=0.0,alpha= -1.0;
 static  int rows, cols, format, oformat, row, col;
 static  int (*atfunc)(int *);
 static  xelval maxval;
@@ -756,7 +812,7 @@ atfilt5(int *p) {
 
 
 static void 
-do_one_frame(FILE *ifp) {
+do_one_frame(FILE * const ifP) {
 
     pnm_writepnminit( stdout, cols, rows, omaxval, oformat, 0 );
     
@@ -770,12 +826,12 @@ do_one_frame(FILE *ifp) {
 
             if (row == 0) {
                 irow0 = irow1;
-                pnm_readpnmrow( ifp, irow1, cols, maxval, format );
+                pnm_readpnmrow( ifP, irow1, cols, maxval, format );
             }
             if (row == (rows-1))
                 irow2 = irow1;
             else
-                pnm_readpnmrow( ifp, irow2, cols, maxval, format );
+                pnm_readpnmrow( ifP, irow2, cols, maxval, format );
 
             for (col = cols-1,po= col>0?1:0,no=0,
                      ip0=irow0,ip1=irow1,ip2=irow2,op=orow;
@@ -846,7 +902,7 @@ do_one_frame(FILE *ifp) {
 
             if (row == 0) {
                 irow0 = irow1;
-                pnm_readpnmrow( ifp, irow1, cols, maxval, format );
+                pnm_readpnmrow( ifP, irow1, cols, maxval, format );
                 if ( promote )
                     pnm_promoteformatrow( irow1, cols, maxval, 
                                           format, maxval, oformat );
@@ -854,7 +910,7 @@ do_one_frame(FILE *ifp) {
             if (row == (rows-1))
                 irow2 = irow1;
             else {
-                pnm_readpnmrow( ifp, irow2, cols, maxval, format );
+                pnm_readpnmrow( ifP, irow2, cols, maxval, format );
                 if ( promote )
                     pnm_promoteformatrow( irow2, cols, maxval, 
                                           format, maxval, oformat );
@@ -932,44 +988,18 @@ int (*atfuncs[6]) (int *) = {atfilt0,atfilt1,atfilt2,atfilt3,atfilt4,atfilt5};
 int
 main(int argc, char *argv[]) {
 
-    FILE * ifp;
+    FILE * ifP;
+    struct cmdlineInfo cmdline;
 	bool eof;  /* We've hit the end of the input stream */
     unsigned int imageSeq;  /* Sequence number of image, starting from 0 */
 
-    const char* const usage = "alpha radius pnmfile\n"
-        "0.0 <= alpha <= 0.5 for alpha trimmed mean -or- \n"
-        "1.0 <= alpha <= 2.0 for optimal estimation -or- \n"
-        "-0.1 >= alpha >= -0.9 for edge enhancement\n"
-        "0.3333 <= radius <= 1.0 specify effective radius\n";
-
-    pnm_init( &argc, argv );
+    pnm_init(&argc, argv);
 
-    if ( argc < 3 || argc > 4 )
-        pm_usage( usage );
+    parseCommandLine(argc, argv, &cmdline);
 
-    if ( sscanf( argv[1], "%lf", &alpha ) != 1 )
-        pm_usage( usage );
-    if ( sscanf( argv[2], "%lf", &radius ) != 1 )
-        pm_usage( usage );
-        
-    if ((alpha > -0.1 && alpha < 0.0) || (alpha > 0.5 && alpha < 1.0))
-        pm_error( "Alpha must be in range 0.0 <= alpha <= 0.5 "
-                  "for alpha trimmed mean" );
-    if (alpha > 2.0)
-        pm_error( "Alpha must be in range 1.0 <= alpha <= 2.0 "
-                  "for optimal estimation" );
-    if (alpha < -0.9 || (alpha > -0.1 && alpha < 0.0))
-        pm_error( "Alpha must be in range -0.9 <= alpha <= -0.1 "
-                  "for edge enhancement" );
-    if (radius < 0.333 || radius > 1.0)
-        pm_error( "Radius must be in range 0.333333333 <= radius <= 1.0" );
+    ifP = pm_openr(cmdline.inputFileName);
 
-    if ( argc == 4 )
-        ifp = pm_openr( argv[3] );
-    else
-        ifp = stdin;
-        
-    pnm_readpnminit( ifp, &cols, &rows, &maxval, &format );
+    pnm_readpnminit(ifP, &cols, &rows, &maxval, &format);
         
     if (maxval > MXIVAL) 
         pm_error("The maxval of the input image (%d) is too large.\n"
@@ -980,12 +1010,12 @@ main(int argc, char *argv[]) {
     /* force output to max precision without forcing new 2-byte format */
     omaxval = MIN(maxval, PPM_MAXMAXVAL);
         
-    atfunc = atfuncs[atfilt_setup(alpha, radius,
+    atfunc = atfuncs[atfilt_setup(cmdline.alpha, cmdline.radius,
                                   (double)omaxval/(double)maxval)];
 
-    if ( oformat < PGM_TYPE ) {
+    if (oformat < PGM_TYPE) {
         oformat = RPGM_FORMAT;
-        pm_message( "promoting file to PGM" );
+        pm_message("promoting file to PGM");
     }
 
     orow = pnm_allocrow(cols);
@@ -999,8 +1029,8 @@ main(int argc, char *argv[]) {
     eof = FALSE;  /* We're already in the middle of the first image */
     imageSeq = 0;
     while (!eof) {
-        do_one_frame(ifp);
-        pm_nextimage(ifp, &eof);
+        do_one_frame(ifP);
+        pm_nextimage(ifP, &eof);
         if (!eof) {
             /* Read and validate header of next image */
             int imageCols, imageRows;
@@ -1008,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,
@@ -1020,9 +1050,7 @@ main(int argc, char *argv[]) {
     pnm_freerow(irow1);
     pnm_freerow(irow2);
     pnm_freerow(orow);
-    pm_close(ifp);
+    pm_close(ifP);
 
     return 0;
 }
-
-
diff --git a/editor/pnmnorm.c b/editor/pnmnorm.c
index b36ad462..27d51115 100644
--- a/editor/pnmnorm.c
+++ b/editor/pnmnorm.c
@@ -29,9 +29,10 @@
 
 #include <assert.h>
 
-#include "pnm.h"
-#include "shhopt.h"
+#include "pm_c_util.h"
 #include "mallocvar.h"
+#include "shhopt.h"
+#include "pnm.h"
 
 enum brightMethod {BRIGHT_LUMINOSITY, BRIGHT_COLORVALUE, BRIGHT_SATURATION};
 
@@ -60,8 +61,8 @@ struct cmdlineInfo {
 
 
 static void
-parseCommandLine (int argc, char ** argv,
-                  struct cmdlineInfo *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.  
@@ -116,7 +117,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 */
 
-    optParseOptions3( &argc, argv, opt, sizeof(opt), 0 );
+    optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdline_p and others. */
 
     if (!cmdlineP->wpercentSpec)
@@ -354,21 +355,85 @@ computeAdjustmentForExpansionLimit(xelval   const maxval,
 
 
 static void
-computeEndValues(FILE *             const ifp,
-                 int                const cols,
-                 int                const rows,
-                 xelval             const maxval,
-                 int                const format,
-                 struct cmdlineInfo const cmdline,
-                 xelval *           const bvalueP,
-                 xelval *           const wvalueP) {
+disOverlap(xelval   const reqBvalue,
+           xelval   const reqWvalue,
+           bool     const bIsFixed,
+           bool     const wIsFixed,
+           xelval   const maxval,
+           xelval * const nonOlapBvalueP,
+           xelval * const nonOlapWvalueP) {
 /*----------------------------------------------------------------------------
-   Figure out what original values will be translated to full white and
-   full black -- thus defining to what all the other values get translated.
+   Compute black and white end values that don't overlap, i.e. the
+   black value is darker than the white, from an initial attempt that
+   might overlap.
 
-   This may involve looking at the image.  The image is in the file
-   'ifp', which is positioned just past the header (at the raster).
-   Leave it positioned arbitrarily.
+   'req{B|W}value' is that initial attempt.  We return the
+   nonoverlapping version as *nonOlap{B|W}valueP.
+
+   '{b|w}IsFixed' means we cannot change that endpoint.
+
+   If both ends are fixed 'reqBvalue' and 'reqWvalue' overlap, we just
+   fail the program -- the user asked for the impossible.
+
+   Where one end is fixed and the other is not, we move the unfixed end
+   to be one unit above or below the fixed end, as appropriate.
+
+   Where both ends are free, we move them to the point halfway between them,
+   the white end being one more than the black end.
+-----------------------------------------------------------------------------*/
+    assert(maxval > 0);
+
+    if (reqBvalue < reqWvalue) {
+        /* No overlap; initial attempt is fine. */
+        *nonOlapBvalueP = reqBvalue;
+        *nonOlapWvalueP = reqWvalue;
+    } else {
+        if (bIsFixed && wIsFixed)
+            pm_error("The colors which become black (value <= %u) "
+                     "would overlap the "
+                     "colors which become white (value >= %u).",
+                     reqBvalue, reqWvalue);
+        else if (bIsFixed) {
+            if (reqBvalue >= maxval)
+                pm_error("The black value must be less than the maxval");
+            else {
+                *nonOlapBvalueP = reqBvalue;
+                *nonOlapWvalueP = reqBvalue + 1;
+            }
+        } else if (wIsFixed) {
+            if (reqWvalue == 0)
+                pm_error("The white value must be greater than 0");
+            else {
+                *nonOlapBvalueP = reqWvalue - 1;
+                *nonOlapWvalueP = reqWvalue;
+            }
+        } else {
+            /* Both ends are free; use the point halfway between them. */
+            xelval const midPoint = (reqWvalue + reqBvalue + maxval/2)/2;
+            *nonOlapBvalueP = MIN(midPoint, maxval-1);
+            *nonOlapWvalueP = *nonOlapBvalueP + 1;
+        }
+    }
+}
+
+
+
+static void
+resolvePercentParams(FILE *             const ifP,
+                     unsigned int       const cols,
+                     unsigned int       const rows,
+                     xelval             const maxval,
+                     int                const format,
+                     struct cmdlineInfo const cmdline,
+                     xelval *           const bvalueP,
+                     xelval *           const wvalueP) {
+/*----------------------------------------------------------------------------
+   Figure out the endpoint of the stretch (the value that is to be stretched
+   to black and the one that is to be stretched to white) as requested
+   by the -bvalue, -bpercent, -wvalue, and -wpercent options.
+
+   These values may be invalid due to overlapping, and they may exceed
+   the maximum allowed stretch; Caller must deal with that.
 -----------------------------------------------------------------------------*/
     unsigned int * hist;  /* malloc'ed */
 
@@ -377,45 +442,71 @@ computeEndValues(FILE *             const ifp,
     if (hist == NULL)
         pm_error("Unable to allocate storage for intensity histogram.");
     else {
-        xelval unlimitedBvalue, unlimitedWvalue;
-        unsigned int bLower, wRaise;
-
-        buildHistogram(ifp, cols, rows, maxval, format, hist,
+        buildHistogram(ifP, cols, rows, maxval, format, hist,
                        cmdline.brightMethod);
 
         if (cmdline.bvalueSpec && !cmdline.bpercentSpec) {
-            unlimitedBvalue = cmdline.bvalue;
+            *bvalueP = cmdline.bvalue;
         } else {
             xelval percentBvalue;
             computeBottomPercentile(hist, maxval, cols*rows, cmdline.bpercent, 
                                     &percentBvalue);
             if (cmdline.bvalueSpec)
-                unlimitedBvalue = MIN(percentBvalue, cmdline.bvalue);
+                *bvalueP = MIN(percentBvalue, cmdline.bvalue);
             else
-                unlimitedBvalue = percentBvalue;
+                *bvalueP = percentBvalue;
         }
 
         if (cmdline.wvalueSpec && !cmdline.wpercentSpec) {
-            unlimitedWvalue = cmdline.wvalue;
+            *wvalueP = cmdline.wvalue;
         } else {
             xelval percentWvalue;
             computeTopPercentile(hist, maxval, cols*rows, cmdline.wpercent, 
                                  &percentWvalue);
             if (cmdline.wvalueSpec)
-                unlimitedWvalue = MAX(percentWvalue, cmdline.wvalue);
+                *wvalueP = MAX(percentWvalue, cmdline.wvalue);
             else
-                unlimitedWvalue = percentWvalue;
+                *wvalueP = percentWvalue;
         }
+        free(hist);
+    }
+}
 
-        computeAdjustmentForExpansionLimit(
-            maxval, unlimitedBvalue, unlimitedWvalue, cmdline.maxExpansion,
-            &bLower, &wRaise);
 
-        *bvalueP = unlimitedBvalue - bLower;
-        *wvalueP = unlimitedWvalue + wRaise;
 
-        free(hist);
-    }
+static void
+computeEndValues(FILE *             const ifP,
+                 int                const cols,
+                 int                const rows,
+                 xelval             const maxval,
+                 int                const format,
+                 struct cmdlineInfo const cmdline,
+                 xelval *           const bvalueP,
+                 xelval *           const wvalueP) {
+/*----------------------------------------------------------------------------
+   Figure out what original values will be translated to full white and
+   full black -- thus defining to what all the other values get translated.
+
+   This may involve looking at the image.  The image is in the file
+   'ifp', which is positioned just past the header (at the raster).
+   Leave it positioned arbitrarily.
+-----------------------------------------------------------------------------*/
+    xelval reqBvalue, reqWvalue, nonOlapBvalue, nonOlapWvalue;
+    unsigned int bLower, wRaise;
+
+    resolvePercentParams(ifP, cols, rows, maxval, format, cmdline,
+                         &reqBvalue, &reqWvalue);
+
+    disOverlap(reqBvalue, reqWvalue,
+               cmdline.bvalueSpec, cmdline.wvalueSpec, maxval,
+               &nonOlapBvalue, &nonOlapWvalue);
+
+    computeAdjustmentForExpansionLimit(
+        maxval, nonOlapBvalue, nonOlapWvalue, cmdline.maxExpansion,
+        &bLower, &wRaise);
+
+    *bvalueP = nonOlapBvalue - bLower;
+    *wvalueP = nonOlapWvalue + wRaise;
 }
 
 
@@ -545,9 +636,9 @@ writeRowNormalized(xel *             const xelrow,
                 float const scaler =
                     brightScaler(p, maxval, newBrightness, brightMethod);
 
-                xelval const r = MIN((int)(PPM_GETR(p)*scaler+0.5), maxval);
-                xelval const g = MIN((int)(PPM_GETG(p)*scaler+0.5), maxval);
-                xelval const b = MIN((int)(PPM_GETB(p)*scaler+0.5), maxval);
+                xelval const r = MIN(ROUNDU(PPM_GETR(p)*scaler), maxval);
+                xelval const g = MIN(ROUNDU(PPM_GETG(p)*scaler), maxval);
+                xelval const b = MIN(ROUNDU(PPM_GETB(p)*scaler), maxval);
                 PNM_ASSIGN(outrow[col], r, g, b);
             } else 
                 PNM_ASSIGN(outrow[col], 
@@ -563,7 +654,7 @@ writeRowNormalized(xel *             const xelrow,
 
 
 int
-main(int argc, char *argv[]) {
+main(int argc, const char *argv[]) {
 
     struct cmdlineInfo cmdline;
     FILE *ifP;
@@ -572,7 +663,7 @@ main(int argc, char *argv[]) {
     int rows, cols, format;
     xelval bvalue, wvalue;
     
-    pnm_init(&argc, argv);
+    pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
 
@@ -584,19 +675,17 @@ main(int argc, char *argv[]) {
 
     computeEndValues(ifP, cols, rows, maxval, format, cmdline, 
                      &bvalue, &wvalue);
-        
-    if (wvalue <= bvalue)
-        pm_error("The colors which become black would overlap the "
-                 "colors which become white.");
-    else {
+    {
         xelval * newBrightness;
         int row;
         xel * xelrow;
         xel * rowbuf;
         
+        assert(wvalue > bvalue);
+
         xelrow = pnm_allocrow(cols);
 
-        pm_message("remapping %d..%d to %d..%d", bvalue, wvalue, 0, maxval);
+        pm_message("remapping %u..%u to %u..%u", bvalue, wvalue, 0, maxval);
 
         computeTransferFunction(bvalue, wvalue, maxval, &newBrightness);
 
@@ -614,7 +703,7 @@ main(int argc, char *argv[]) {
         free(newBrightness);
         pnm_freerow(rowbuf);
         pnm_freerow(xelrow);
-    }
-    pm_close(ifP);
+    } 
+   pm_close(ifP);
     return 0;
 }
diff --git a/editor/pnmpad.c b/editor/pnmpad.c
index e1fbdaec..34672dc5 100644
--- a/editor/pnmpad.c
+++ b/editor/pnmpad.c
@@ -1,28 +1,22 @@
 /* pnmpad.c - add border to sides of a portable anymap
- ** AJCD 4/9/90
- */
-
-/*
- * Changelog
- *
- * 2002/01/25 - Rewrote options parsing code.
- *      Added pad-to-width and pad-to-height with custom
- *      alignment.  MVB.
+   ** AJCD 4/9/90
  */
 
 #include <string.h>
 #include <stdio.h>
 
-#include "pnm.h"
-#include "shhopt.h"
+#include "pm_c_util.h"
 #include "mallocvar.h"
+#include "shhopt.h"
+#include "pnm.h"
 
+#define MAX_WIDTHHEIGHT INT_MAX-10
 
 struct cmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
-    const char *input_filespec;  /* Filespecs of input files */
+    const char * input_filespec;  /* Filespecs of input files */
     unsigned int xsize;
     unsigned int xsizeSpec;
     unsigned int ysize;
@@ -44,7 +38,7 @@ struct cmdlineInfo {
 
 
 static void
-parseCommandLine(int argc, char ** argv,
+parseCommandLine(int argc, const char ** argv,
                  struct cmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that the file spec array we return is stored in the storage that
@@ -62,7 +56,7 @@ parseCommandLine(int argc, char ** argv,
     MALLOCARRAY_NOFAIL(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENT3 */
-    OPTENT3(0,   "xsize",     OPT_UINT,    &cmdlineP->xsize,       
+    OPTENT3(0,   "xsize",     OPT_UINT,    &cmdlineP->xsize,
             &cmdlineP->xsizeSpec, 0);
     OPTENT3(0,   "width",     OPT_UINT,    &cmdlineP->xsize,
             &cmdlineP->xsizeSpec, 0);
@@ -70,13 +64,13 @@ parseCommandLine(int argc, char ** argv,
             &cmdlineP->ysizeSpec, 0);
     OPTENT3(0,   "height",    OPT_UINT,    &cmdlineP->ysize,
             &cmdlineP->ysizeSpec, 0);
-    OPTENT3(0,   "left",      OPT_UINT,    &cmdlineP->left, 
+    OPTENT3(0,   "left",      OPT_UINT,    &cmdlineP->left,
             &cmdlineP->leftSpec, 0);
-    OPTENT3(0,   "right",     OPT_UINT,    &cmdlineP->right, 
+    OPTENT3(0,   "right",     OPT_UINT,    &cmdlineP->right,
             &cmdlineP->rightSpec, 0);
-    OPTENT3(0,   "top",       OPT_UINT,    &cmdlineP->top, 
+    OPTENT3(0,   "top",       OPT_UINT,    &cmdlineP->top,
             &cmdlineP->topSpec, 0);
-    OPTENT3(0,   "bottom",    OPT_UINT,    &cmdlineP->bottom, 
+    OPTENT3(0,   "bottom",    OPT_UINT,    &cmdlineP->bottom,
             &cmdlineP->bottomSpec, 0);
     OPTENT3(0,   "xalign",    OPT_FLOAT,   &cmdlineP->xalign,
             &xalignSpec,           0);
@@ -86,7 +80,7 @@ parseCommandLine(int argc, char ** argv,
             &yalignSpec,           0);
     OPTENT3(0,   "valign",    OPT_FLOAT,   &cmdlineP->yalign,
             &yalignSpec,           0);
-    OPTENT3(0,   "black",     OPT_FLAG,    NULL, 
+    OPTENT3(0,   "black",     OPT_FLAG,    NULL,
             &blackOpt,           0);
     OPTENT3(0,   "white",     OPT_FLAG,    NULL,
             &cmdlineP->white,    0);
@@ -97,12 +91,30 @@ 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 */
 
-    optParseOptions3(&argc, argv, opt, sizeof opt, 0);
+    optParseOptions3(&argc, (char **)argv, opt, sizeof opt, 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
     if (blackOpt && cmdlineP->white)
         pm_error("You cannot specify both -black and -white");
 
+    if (cmdlineP->topSpec > 1)
+       pm_error("You can specify -top only once");
+
+    if (cmdlineP->bottomSpec > 1)
+       pm_error("You can specify -bottom only once");
+
+    if (cmdlineP->leftSpec > 1)
+       pm_error("You can specify -left only once");
+
+    if (cmdlineP->rightSpec > 1)
+       pm_error("You can specify -right only once");
+
+    if (cmdlineP->xsizeSpec > 1)
+       pm_error("You can specify -width only once");
+
+    if (cmdlineP->ysizeSpec > 1)
+       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");
 
@@ -117,20 +129,20 @@ parseCommandLine(int argc, char ** argv,
 
     if (xalignSpec) {
         if (cmdlineP->xalign < 0)
-            pm_error("You have specified a negative -halign value (%f)", 
+            pm_error("You have specified a negative -halign value (%f)",
                      cmdlineP->xalign);
         if (cmdlineP->xalign > 1)
-            pm_error("You have specified a -halign value (%f) greater than 1", 
+            pm_error("You have specified a -halign value (%f) greater than 1",
                      cmdlineP->xalign);
     } else
         cmdlineP->xalign = 0.5;
 
     if (yalignSpec) {
         if (cmdlineP->yalign < 0)
-            pm_error("You have specified a negative -halign value (%f)", 
+            pm_error("You have specified a negative -halign value (%f)",
                      cmdlineP->yalign);
         if (cmdlineP->yalign > 1)
-            pm_error("You have specified a -valign value (%f) greater than 1", 
+            pm_error("You have specified a -valign value (%f) greater than 1",
                      cmdlineP->yalign);
     } else
         cmdlineP->yalign = 0.5;
@@ -139,16 +151,16 @@ parseCommandLine(int argc, char ** argv,
     if (argc-1 > 1)
         pm_error("This program takes at most 1 parameter.  You specified %d",
                  argc-1);
-    else if (argc-1 == 1) 
+    else if (argc-1 == 1)
         cmdlineP->input_filespec = argv[1];
-    else 
+    else
         cmdlineP->input_filespec = "-";
 }
 
 
 
 static void
-parseCommandLineOld(int argc, char ** argv,
+parseCommandLineOld(int argc, const char ** argv,
                     struct cmdlineInfo * const cmdlineP) {
 
     /* This syntax was abandonned in February 2002. */
@@ -166,24 +178,32 @@ parseCommandLineOld(int argc, char ** argv,
         case 'l':
             if (atoi(argv[1]+2) < 0)
                 pm_error("left border too small");
+            else if (atoi(argv[1]+2) > MAX_WIDTHHEIGHT)
+                pm_error("left border too large");
             else
                 cmdlineP->left = atoi(argv[1]+2);
             break;
         case 'r':
             if (atoi(argv[1]+2) < 0)
                 pm_error("right border too small");
+            else if (atoi(argv[1]+2) > MAX_WIDTHHEIGHT)
+                pm_error("right border too large");
             else
                 cmdlineP->right = atoi(argv[1]+2);
             break;
         case 'b':
             if (atoi(argv[1]+2) < 0)
                 pm_error("bottom border too small");
+            else if (atoi(argv[1]+2) > MAX_WIDTHHEIGHT)
+                pm_error("bottom border too large");
             else
                 cmdlineP->bottom = atoi(argv[1]+2);
             break;
         case 't':
             if (atoi(argv[1]+2) < 0)
                 pm_error("top border too small");
+            else if (atoi(argv[1]+2) > MAX_WIDTHHEIGHT)
+                pm_error("top border too large");
             else
                 cmdlineP->top = atoi(argv[1]+2);
             break;
@@ -208,11 +228,36 @@ parseCommandLineOld(int argc, char ** argv,
 
 
 static void
+validateHorizontalSize(struct cmdlineInfo const cmdline,
+                       unsigned int const cols) {
+
+    unsigned int const xsize = cmdline.xsizeSpec ? cmdline.xsize : 0;
+    unsigned int const lpad  = cmdline.leftSpec  ? cmdline.left  : 0;
+    unsigned int const rpad  = cmdline.rightSpec ? cmdline.right : 0;
+
+    if (xsize > MAX_WIDTHHEIGHT)
+        pm_error("The width value you specified is too large.");
+
+    if (lpad > MAX_WIDTHHEIGHT)
+        pm_error("The left padding value you specified is too large.");
+
+    if (rpad > MAX_WIDTHHEIGHT)
+        pm_error("The right padding value you specified is too large.");
+
+    if ((double) cols + (double) lpad + (double) rpad > MAX_WIDTHHEIGHT)
+        pm_error("Given padding value(s) makes output width too large.");
+}
+
+
+
+static void
 computeHorizontalPadSizes(struct cmdlineInfo const cmdline,
-                          int                const cols,
+                          unsigned int       const cols,
                           unsigned int *     const lpadP,
                           unsigned int *     const rpadP) {
 
+    validateHorizontalSize(cmdline, cols);
+
     if (cmdline.xsizeSpec) {
         if (cmdline.leftSpec && cmdline.rightSpec) {
             if (cmdline.left + cols + cmdline.right < cmdline.xsize) {
@@ -226,7 +271,7 @@ computeHorizontalPadSizes(struct cmdlineInfo const cmdline,
             }
         } else if (cmdline.leftSpec) {
             *lpadP = cmdline.left;
-            *rpadP = MAX(cmdline.xsize, cmdline.left + cols) - 
+            *rpadP = MAX(cmdline.xsize, cmdline.left + cols) -
                 (cmdline.left + cols);
         } else if (cmdline.rightSpec) {
             *rpadP = cmdline.right;
@@ -242,7 +287,7 @@ computeHorizontalPadSizes(struct cmdlineInfo const cmdline,
             }
         }
     } else {
-        *lpadP = cmdline.leftSpec ? cmdline.left : 0;
+        *lpadP = cmdline.leftSpec  ? cmdline.left  : 0;
         *rpadP = cmdline.rightSpec ? cmdline.right : 0;
     }
 }
@@ -250,11 +295,36 @@ computeHorizontalPadSizes(struct cmdlineInfo const cmdline,
 
 
 static void
+validateVerticalSize(struct cmdlineInfo const cmdline,
+                     unsigned int       const rows) {
+
+    unsigned int const ysize = cmdline.ysizeSpec  ? cmdline.ysize  : 0;
+    unsigned int const tpad  = cmdline.topSpec    ? cmdline.top    : 0;
+    unsigned int const bpad  = cmdline.bottomSpec ? cmdline.bottom : 0;
+
+    if (ysize > MAX_WIDTHHEIGHT)
+        pm_error("The height value you specified is too large.");
+
+    if (tpad > MAX_WIDTHHEIGHT)
+        pm_error("The top padding value you specified is too large.");
+
+    if (bpad > MAX_WIDTHHEIGHT)
+        pm_error("The bottom padding value you specified is too large.");
+
+    if ((double) rows + (double) tpad + (double) bpad > MAX_WIDTHHEIGHT)
+        pm_error("Given padding value(s) makes output height too large.");
+}
+
+
+
+static void
 computeVerticalPadSizes(struct cmdlineInfo const cmdline,
                         int                const rows,
                         unsigned int *     const tpadP,
                         unsigned int *     const bpadP) {
 
+    validateVerticalSize(cmdline, rows);
+
     if (cmdline.ysizeSpec) {
         if (cmdline.topSpec && cmdline.bottomSpec) {
             if (cmdline.bottom + rows + cmdline.top < cmdline.ysize) {
@@ -268,11 +338,11 @@ computeVerticalPadSizes(struct cmdlineInfo const cmdline,
             }
         } else if (cmdline.topSpec) {
             *tpadP = cmdline.top;
-            *bpadP = MAX(cmdline.ysize, cmdline.top + rows) - 
+            *bpadP = MAX(cmdline.ysize, cmdline.top + rows) -
                 (cmdline.top + rows);
         } else if (cmdline.bottomSpec) {
             *bpadP = cmdline.bottom;
-            *tpadP = MAX(cmdline.ysize, rows + cmdline.bottom) - 
+            *tpadP = MAX(cmdline.ysize, rows + cmdline.bottom) -
                 (rows + cmdline.bottom);
         } else {
             if (cmdline.ysize > rows) {
@@ -285,7 +355,7 @@ computeVerticalPadSizes(struct cmdlineInfo const cmdline,
         }
     } else {
         *bpadP = cmdline.bottomSpec ? cmdline.bottom : 0;
-        *tpadP = cmdline.topSpec ? cmdline.top : 0;
+        *tpadP = cmdline.topSpec    ? cmdline.top    : 0;
     }
 }
 
@@ -311,18 +381,121 @@ computePadSizes(struct cmdlineInfo const cmdline,
 
 
 
+static void
+padPbm(FILE *       const ifP,
+       unsigned int const cols,
+       unsigned int const rows,
+       int          const format,
+       unsigned int const newcols,
+       unsigned int const lpad,
+       unsigned int const rpad,
+       unsigned int const tpad,
+       unsigned int const bpad,
+       bool         const colorWhite) {
+/*----------------------------------------------------------------------------
+  Fast padding routine for PBM
+-----------------------------------------------------------------------------*/
+    unsigned char * const bgrow  = pbm_allocrow_packed(newcols);
+    unsigned char * const newrow = pbm_allocrow_packed(newcols);
+
+    unsigned char const padChar =
+        0xff * (colorWhite ? PBM_WHITE : PBM_BLACK);
+
+    unsigned int const newColChars = pbm_packed_bytes(newcols);
+
+    unsigned int row;
+    unsigned int charCnt;
+
+    /* Set up margin row, input-output buffer */
+    for (charCnt = 0; charCnt < newColChars; ++charCnt)
+        bgrow[charCnt] = newrow[charCnt] = padChar;
+
+    if (newcols % 8 > 0) {
+        bgrow[newColChars-1]  <<= 8 - newcols % 8;
+        newrow[newColChars-1] <<= 8 - newcols % 8;
+    }
+
+    pbm_writepbminit(stdout, newcols, rows + tpad + bpad, 0);
+
+    /* 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);
+        pbm_writepbmrow_packed(stdout, newrow, newcols, 0);
+    }
+
+    pnm_freerow(newrow);
+
+    /* Write bottom margin */
+    for (row = 0; row < bpad; ++row)
+        pbm_writepbmrow_packed(stdout, bgrow, newcols, 0);
+
+    pnm_freerow(bgrow);
+}
+
+
+static void
+padGeneral(FILE *       const ifP,
+           unsigned int const cols,
+           unsigned int const rows,
+           xelval       const maxval,
+           int          const format,
+           unsigned int const newcols,
+           unsigned int const lpad,
+           unsigned int const rpad,
+           unsigned int const tpad,
+           unsigned int const bpad,
+           bool         const colorWhite) {
+/*----------------------------------------------------------------------------
+  General padding routine (logic works for PBM)
+-----------------------------------------------------------------------------*/
+    xel * const bgrow  = pnm_allocrow(newcols);
+    xel * const xelrow = pnm_allocrow(newcols);
+    xel background;
+    unsigned int row, col;
+
+    if (colorWhite)
+        background = pnm_whitexel(maxval, format);
+    else
+        background = pnm_blackxel(maxval, format);
+
+    for (col = 0; col < newcols; ++col)
+        xelrow[col] = bgrow[col] = background;
+
+    pnm_writepnminit(stdout, newcols, rows + tpad + bpad, maxval, format, 0);
+
+    for (row = 0; row < tpad; ++row)
+        pnm_writepnmrow(stdout, bgrow, newcols, maxval, format, 0);
+
+    for (row = 0; row < rows; ++row) {
+        pnm_readpnmrow(ifP, &xelrow[lpad], cols, maxval, format);
+        pnm_writepnmrow(stdout, xelrow, newcols, maxval, format, 0);
+    }
+
+    for (row = 0; row < bpad; ++row)
+        pnm_writepnmrow(stdout, bgrow, newcols, maxval, format, 0);
+
+    pnm_freerow(xelrow);
+    pnm_freerow(bgrow);
+}
+
+
+
 int
-main(int argc, char ** argv) {
+main(int argc, const char ** argv) {
 
     struct cmdlineInfo cmdline;
-    FILE *ifP;
-    xel *xelrow, *bgrow, background;
+    FILE * ifP;
+
     xelval maxval;
-    int rows, cols, newcols, row, col, format;
+    int rows, cols, newcols, format;
     bool depr_cmd; /* use deprecated commandline interface */
     unsigned int lpad, rpad, tpad, bpad;
 
-    pnm_init( &argc, argv );
+    pm_proginit(&argc, argv);
 
     /* detect deprecated options */
     depr_cmd = FALSE;  /* initial assumption */
@@ -332,7 +505,7 @@ main(int argc, char ** argv) {
             if (argv[1][2] >= '0' && argv[1][2] <= '9')
                 depr_cmd = TRUE;
         }
-    } 
+    }
     if (argc > 2 && argv[2][0] == '-') {
         if (argv[2][1] == 't' || argv[2][1] == 'b'
             || argv[2][1] == 'l' || argv[2][1] == 'r') {
@@ -341,45 +514,27 @@ main(int argc, char ** argv) {
         }
     }
 
-    if (depr_cmd) 
+    if (depr_cmd)
         parseCommandLineOld(argc, argv, &cmdline);
-    else 
+    else
         parseCommandLine(argc, argv, &cmdline);
 
     ifP = pm_openr(cmdline.input_filespec);
 
     pnm_readpnminit(ifP, &cols, &rows, &maxval, &format);
-    if (cmdline.white)
-        background = pnm_whitexel(maxval, format);
-    else
-        background = pnm_blackxel(maxval, format);
 
     if (cmdline.verbose) pm_message("image WxH = %dx%d", cols, rows);
 
     computePadSizes(cmdline, cols, rows, &lpad, &rpad, &tpad, &bpad);
 
     newcols = cols + lpad + rpad;
-    xelrow = pnm_allocrow(newcols);
-    bgrow = pnm_allocrow(newcols);
 
-    for (col = 0; col < newcols; col++)
-        xelrow[col] = bgrow[col] = background;
-
-    pnm_writepnminit(stdout, newcols, rows + tpad + bpad, maxval, format, 0);
-
-    for (row = 0; row < tpad; row++)
-        pnm_writepnmrow(stdout, bgrow, newcols, maxval, format, 0);
-
-    for (row = 0; row < rows; row++) {
-        pnm_readpnmrow(ifP, &xelrow[lpad], cols, maxval, format);
-        pnm_writepnmrow(stdout, xelrow, newcols, maxval, format, 0);
-    }
-
-    for (row = 0; row < bpad; row++)
-        pnm_writepnmrow(stdout, bgrow, newcols, maxval, format, 0);
-
-    pnm_freerow(xelrow);
-    pnm_freerow(bgrow);
+    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/pnmpaste.c b/editor/pnmpaste.c
index 38b316c6..33834669 100644
--- a/editor/pnmpaste.c
+++ b/editor/pnmpaste.c
@@ -10,176 +10,419 @@
 ** implied warranty.
 */
 
+#include <assert.h>
+
 #include "pm_c_util.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "shhopt.h"
 #include "pnm.h"
 
+
+enum boolOp {REPLACE, AND, OR, XOR /*, NAND, NOR, NXOR */ };
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * baseFilename;
+    const char * insetFilename;
+    int insertCol;  /* Negative means from right edge */
+    int insertRow;  /* Negative means from bottom edge */
+    enum boolOp operation;
+};
+
+
+
+static void
+parseCommandLine(int argc, const char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+    unsigned int replaceOpt, andOpt, orOpt, xorOpt;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "replace",     OPT_FLAG,    NULL,
+            &replaceOpt,           0);
+    OPTENT3(0,   "and",         OPT_FLAG,    NULL,
+            &andOpt,               0);
+    OPTENT3(0,   "or",          OPT_FLAG,    NULL,
+            &orOpt,                0);
+    OPTENT3(0,   "xor",         OPT_FLAG,    NULL,
+            &xorOpt,               0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = TRUE;  /* We have parms that are negative numbers */
+
+    optParseOptions3(&argc, (char **)argv, opt, sizeof opt, 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (replaceOpt + andOpt + orOpt + xorOpt > 1)
+        pm_error("You may specify only one of -replace, -and, -or, and -xor");
+
+    cmdlineP->operation =
+        replaceOpt ? REPLACE :
+        andOpt     ? AND     :
+        orOpt      ? OR      :
+        xorOpt     ? XOR     :
+        replaceOpt;
+        
+
+    if (argc-1 >= 3) {
+        cmdlineP->insetFilename = argv[1];
+        cmdlineP->insertCol     = atoi(argv[2]);
+        cmdlineP->insertRow     = atoi(argv[3]);
+
+        if (argc-1 >= 4) {
+            cmdlineP->baseFilename = argv[4];
+
+            if (argc-1 > 4)
+                pm_error("Too many arguments: %u.  This program takes "
+                         "at most 4", argc-1);
+        } else
+            cmdlineP->baseFilename = "-";
+    } else
+        pm_error("You must specify at least 3 arguments: \"from\" file "
+                 "name, insert-at column, and insert-at row.  "
+                 "You specified %u", argc-1);
+
+    if (streq(cmdlineP->baseFilename, "-") &&
+        streq(cmdlineP->insetFilename, "-"))
+        pm_error("You can't use Standard Input for both the input images");
+}
+
+
+
+static unsigned char
+leftBits(unsigned char const x,
+         unsigned int  const n){
+/*----------------------------------------------------------------------------
+  Clear rightmost (8-n) bits, retain leftmost (=high) n bits.
+-----------------------------------------------------------------------------*/
+    assert(n <= 8);
+
+    return (x >> (8-n)) << (8-n);
+}
+
+
+
+static unsigned char
+rightBits(unsigned char const x,
+          unsigned int  const n){
+/*----------------------------------------------------------------------------
+  Return rightmost (=low) n bits of x. (Clear the rest).
+-----------------------------------------------------------------------------*/
+    assert(n <= 8);
+
+    return ((unsigned char)(x << (8-n))) >> (8-n);
+}
+
+
+
+static void
+insertDirect(FILE *          const ifP,
+             unsigned char * const destrow,
+             unsigned int    const cols,
+             int             const format,
+             enum boolOp     const operation,
+             unsigned char * const buffer) {
+/*----------------------------------------------------------------------------
+   Read the next row from PBM file 'ifP' and merge it according to
+   'operation' into 'destrow', flush left in packed PBM format.
+
+   'cols' and 'format' describe the 'ifP' image.
+
+   'buffer' is a scratch buffer for our use, at least wide enough to hold
+   a packed PBM row of 'ifP'.
+-----------------------------------------------------------------------------*/
+    unsigned int  const colBytes  = pbm_packed_bytes(cols);
+    unsigned int  const last      = colBytes - 1;
+    unsigned char const origRight = destrow[last];
+
+    if (operation == REPLACE)
+        pbm_readpbmrow_packed(ifP, destrow, cols, format);
+    else {
+        unsigned int i;
+
+        pbm_readpbmrow_packed(ifP, buffer, cols, format);
+
+        for (i = 0; i < colBytes; ++i) {
+            switch (operation) {
+            case AND: destrow[i] |= buffer[i]; break;
+            case OR : destrow[i] &= buffer[i]; break;
+            case XOR: destrow[i]  = ~( destrow[i] ^ buffer[i] ) ; break;
+            /*
+            case NAND: destrow[i] = ~( destrow[i] | buffer[i] ) ; break;
+            case NOR : destrow[i] = ~( destrow[i] & buffer[i] ) ; break;
+            case NXOR: destrow[i] ^= buffer[i]  ; break;
+            */
+            case REPLACE: assert(false); break;
+            }
+        }
+    }
+
+    if (cols % 8 > 0)
+        destrow[last] = leftBits(destrow[last], cols % 8)
+            | rightBits(origRight, 8 - cols % 8);
+}
+
+
+
+static void
+insertShift(FILE *          const ifP,
+            unsigned char * const destrow,
+            unsigned int    const cols,
+            unsigned int    const format,
+            unsigned int    const offset,
+            enum boolOp     const operation,
+            unsigned char * const buffer) {
+/*----------------------------------------------------------------------------
+   Same as insertDirect(), but start merging 'offset' bits from the left
+   end of 'destrow'.  'offset' is less than 8.
+
+   buffer[] is wide enough to hold a packed PBM row of *ifP plus one
+   byte of margin.
+-----------------------------------------------------------------------------*/
+    unsigned int const shiftBytes = pbm_packed_bytes(cols + offset);
+    unsigned int const last = shiftBytes - 1;
+    unsigned char const origLeft  = destrow[0];
+    unsigned char const origRight = destrow[last];
+
+    unsigned int const padOffset = (cols + offset) % 8;
+
+    unsigned int i;
+
+    assert(offset < 8);
+
+    pbm_readpbmrow_packed(ifP, &buffer[1], cols, format);
+
+    /* Note that buffer[0] is undefined. */
+
+    for (i = 0; i < shiftBytes; ++i) {
+        unsigned int  const rsh = offset;
+        unsigned int  const lsh = 8-rsh;
+        unsigned char const t = buffer[i] << lsh | buffer[i+1] >> rsh;
+
+        switch (operation) {
+        case REPLACE: destrow[i] = t; break;
+        case AND:     destrow[i] |= t; break;
+        case OR :     destrow[i] &= t; break;
+        case XOR:     destrow[i] = ~ (destrow[i] ^ t); break;
+        /*
+        case NAND:    destrow[i] = ~ (destrow[i] | t); break;
+        case NOR :    destrow[i] = ~ (destrow[i] & t); break;
+        case NXOR:    destrow[i] ^= t; break;
+        */
+        }
+    }
+
+    /* destrow[] now contains garbage in the 'offset' leftmost bits
+       and 8-offset rightmost bits.  Those are supposed to be unchanged
+       from the input, so we restore them now.
+    */
+
+    destrow[0] = leftBits(origLeft, offset) |
+        rightBits(destrow[0], 8-offset);
+   
+    if (padOffset % 8 > 0)
+        destrow[last] = leftBits(destrow[last], padOffset) |
+            rightBits(origRight , 8-padOffset);
+}
+
+
+
+static void
+pastePbm(FILE *       const fpInset,
+         FILE *       const fpBase,
+         int          const insetFormat,
+         int          const baseFormat,
+         unsigned int const insetRows,
+         unsigned int const baseRows,
+         unsigned int const insetCols,
+         unsigned int const baseCols,
+         unsigned int const insertCol,
+         unsigned int const insertRow,
+         enum boolOp  const operation) {
+/*----------------------------------------------------------------------------
+  Fast paste for PBM
+-----------------------------------------------------------------------------*/
+    unsigned char * const baserow = pbm_allocrow_packed(baseCols);
+    unsigned char * const buffer = pbm_allocrow_packed(insetCols+8);
+    int const shiftBytes = insertCol / 8;
+    unsigned int const shiftOffset = insertCol % 8;
+    int const baseColBytes = pbm_packed_bytes(baseCols);
+
+    unsigned int row;
+
+    pbm_writepbminit(stdout, baseCols, baseRows, 0);
+
+    for (row = 0; row < baseRows; ++row) {
+        pbm_readpbmrow_packed(fpBase, baserow, baseCols, baseFormat);
+        
+        if (row >= insertRow && row < insertRow + insetRows) {
+            if (shiftOffset == 0)
+                insertDirect(fpInset, &baserow[shiftBytes], insetCols,
+                             insetFormat, operation, buffer);
+            else
+                insertShift(fpInset, &baserow[shiftBytes], insetCols,
+                            insetFormat, shiftOffset, operation, buffer);
+        }
+
+        if (baseCols % 8 > 0)
+            baserow[baseColBytes-1]
+                = leftBits(baserow[baseColBytes-1] , baseCols % 8);
+
+        pbm_writepbmrow_packed(stdout, baserow, baseCols, 0);
+    }
+    pbm_freerow_packed(buffer);
+    pbm_freerow_packed(baserow);
+}
+
+
+
+static void
+pasteNonPbm(FILE *       const fpInset,
+            FILE *       const fpBase,
+            int          const formatInset,
+            int          const formatBase,
+            int          const newformat,
+            xelval       const maxvalInset,
+            xelval       const maxvalBase,
+            unsigned int const rowsInset,
+            unsigned int const rowsBase,
+            unsigned int const colsInset,
+            unsigned int const colsBase,
+            unsigned int const insertCol,
+            unsigned int const insertRow) {
+
+    /* Logic works for PBM, but cannot do bitwise operations */             
+
+    xelval const newmaxval = MAX(maxvalInset, maxvalBase);
+
+    xel * const xelrowInset = pnm_allocrow(colsInset);
+    xel * const xelrowBase  = pnm_allocrow(colsBase);
+
+    unsigned int row;
+
+    pnm_writepnminit(stdout, colsBase, rowsBase, newmaxval, newformat, 0);
+
+    for (row = 0; row < rowsBase; ++row) {
+        pnm_readpnmrow(fpBase, xelrowBase, colsBase, maxvalBase, formatBase);
+        pnm_promoteformatrow(xelrowBase, colsBase, maxvalBase, formatBase,
+                             newmaxval, newformat);
+
+        if (row >= insertRow && row < insertRow + rowsInset) {
+            unsigned int colInset;
+
+            pnm_readpnmrow(fpInset, xelrowInset, colsInset, maxvalInset,
+                           formatInset);
+            pnm_promoteformatrow(xelrowInset, colsInset, maxvalInset,
+                                 formatInset, newmaxval, newformat );
+            for (colInset = 0; colInset < colsInset; ++colInset)
+                xelrowBase[insertCol + colInset] = xelrowInset[colInset];
+        }
+        pnm_writepnmrow(stdout, xelrowBase, colsBase, newmaxval, newformat, 0);
+    }
+    
+    pnm_freerow(xelrowBase);
+    pnm_freerow(xelrowInset);
+}
+
+
+
 int
-main( argc, argv )
-    int argc;
-    char* argv[];
-    {
-    FILE* ifp1;
-    FILE* ifp2;
-    register xel* xelrow1;
-    register xel* xelrow2;
-    register xel* x1P;
-    register xel* x2P;
-    xelval maxval1, maxval2, newmaxval;
-    int argn, rows1, cols1, format1, x, y;
-    int rows2, cols2, format2, newformat, row;
-    register int col;
-    char function;
-    const char* const usage = "[-replace|-or|-and|-xor] frompnmfile x y [intopnmfile]";
-
-    pnm_init( &argc, argv );
-
-    argn = 1;
-    function = 'r';
-
-    /* Check for flags. */
-    if ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
-	{
-	if ( pm_keymatch( argv[argn], "-replace", 2 ) )
-	    function = 'r';
-	else if ( pm_keymatch( argv[argn], "-or", 2 ) )
-	    function = 'o';
-	else if ( pm_keymatch( argv[argn], "-and", 2 ) )
-	    function = 'a';
-	else if ( pm_keymatch( argv[argn], "-xor", 2 ) )
-	    function = 'x';
-	else
-	    pm_usage( usage );
-	++argn;
-	}
-
-    if ( argn == argc )
-	pm_usage( usage );
-    ifp1 = pm_openr( argv[argn] );
-    ++argn;
-
-    if ( argn == argc )
-	pm_usage( usage );
-    if ( sscanf( argv[argn], "%d", &x ) != 1 )
-	pm_usage( usage );
-    ++argn;
-    if ( argn == argc )
-	pm_usage( usage );
-    if ( sscanf( argv[argn], "%d", &y ) != 1 )
-	pm_usage( usage );
-    ++argn;
-
-    if ( argn != argc )
-	{
-	ifp2 = pm_openr( argv[argn] );
-	++argn;
-	}
+main(int argc, const char ** argv) {
+
+    struct cmdlineInfo cmdline;
+    FILE * fpInset;
+    FILE * fpBase;
+    xelval maxvalInset, maxvalBase;
+    int rowsInset, colsInset;
+    int formatInset;
+    int rowsBase, colsBase;
+    int formatBase;
+    int newformat;
+    unsigned int insertRow, insertCol;
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    fpInset = pm_openr(cmdline.insetFilename);
+    fpBase  = pm_openr(cmdline.baseFilename);
+
+    pnm_readpnminit(fpInset, &colsInset, &rowsInset,
+                    &maxvalInset, &formatInset);
+    pnm_readpnminit(fpBase, &colsBase, &rowsBase, &maxvalBase, &formatBase);
+
+    if (colsBase < colsInset)
+        pm_error(
+            "Image to paste is wider than base image by %u cols",
+            colsInset - colsBase);
+    else if (cmdline.insertCol <= -colsBase)
+        pm_error(
+            "x is too negative -- the second image has only %u cols",
+            colsBase);
+    else if (cmdline.insertCol >= colsBase)
+        pm_error(
+            "x is too large -- the second image has only %u cols",
+            colsBase);
+
+    if (rowsBase < rowsInset)
+        pm_error(
+            "Image to paste is taller than base image by %u rows",
+            rowsInset - rowsBase);
+    else if (cmdline.insertRow <= -rowsBase)
+        pm_error(
+            "y is too negative -- the second image has only %u rows",
+            rowsBase);
+    else if (cmdline.insertRow >= rowsBase)
+        pm_error(
+            "y is too large -- the second image has only %d rows",
+            rowsBase);
+
+    insertCol = cmdline.insertCol < 0 ?
+        colsBase + cmdline.insertCol : cmdline.insertCol;
+    insertRow = cmdline.insertRow < 0 ?
+        rowsBase + cmdline.insertRow : cmdline.insertRow;
+
+    if (insertCol + colsInset > colsBase)
+        pm_error("Extends over right edge by %u pixels",
+                 (insertCol + colsInset) - colsBase);
+    if (insertRow + rowsInset > rowsBase)
+        pm_error("Extends over bottom edge by %u pixels",
+                 (insertRow + rowsInset) - rowsBase);
+
+    newformat = MAX(PNM_FORMAT_TYPE(formatInset), PNM_FORMAT_TYPE(formatBase));
+
+    if (cmdline.operation != REPLACE && newformat != PBM_TYPE)
+        pm_error("no logical operations allowed for a non-PBM image");
+
+    if (newformat == PBM_TYPE)
+        pastePbm(fpInset, fpBase, formatInset, formatBase,
+                 rowsInset, rowsBase, colsInset, colsBase,
+                 insertCol, insertRow, cmdline.operation);
     else
-	ifp2 = stdin;
-
-    if ( argn != argc )
-	pm_usage( usage );
-
-    pnm_readpnminit( ifp1, &cols1, &rows1, &maxval1, &format1 );
-    xelrow1 = pnm_allocrow(cols1);
-    pnm_readpnminit( ifp2, &cols2, &rows2, &maxval2, &format2 );
-    xelrow2 = pnm_allocrow(cols2);
-
-    if ( x <= -cols2 )
-	pm_error(
-	    "x is too negative -- the second anymap has only %d cols",
-	    cols2 );
-    else if ( x >= cols2 )
-	pm_error(
-	    "x is too large -- the second anymap has only %d cols",
-	    cols2 );
-    if ( y <= -rows2 )
-	pm_error(
-	    "y is too negative -- the second anymap has only %d rows",
-	    rows2 );
-    else if ( y >= rows2 )
-	pm_error(
-	    "y is too large -- the second anymap has only %d rows",
-	    rows2 );
-
-    if ( x < 0 )
-	x += cols2;
-    if ( y < 0 )
-	y += rows2;
-
-    if ( x + cols1 > cols2 )
-	pm_error( "x + width is too large by %d pixels", x + cols1 - cols2 );
-    if ( y + rows1 > rows2 )
-	pm_error( "y + height is too large by %d pixels", y + rows1 - rows2 );
-
-    newformat = MAX( PNM_FORMAT_TYPE(format1), PNM_FORMAT_TYPE(format2) );
-    newmaxval = MAX( maxval1, maxval2 );
-
-    if ( function != 'r' && newformat != PBM_TYPE )
-	pm_error( "no logical operations allowed for non-bitmaps" );
-
-    pnm_writepnminit( stdout, cols2, rows2, newmaxval, newformat, 0 );
-
-    for ( row = 0; row < rows2; ++row )
-	{
-	pnm_readpnmrow( ifp2, xelrow2, cols2, maxval2, format2 );
-	pnm_promoteformatrow( xelrow2, cols2, maxval2, format2,
-	    newmaxval, newformat );
-
-	if ( row >= y && row < y + rows1 )
-	    {
-	    pnm_readpnmrow( ifp1, xelrow1, cols1, maxval1, format1 );
-	    pnm_promoteformatrow( xelrow1, cols1, maxval1, format1,
-		newmaxval, newformat );
-	    for ( col = 0, x1P = xelrow1, x2P = &(xelrow2[x]);
-		  col < cols1; ++col, ++x1P, ++x2P )
-		{
-		register xelval b1, b2;
-
-		switch ( function )
-		    {
-		    case 'r':
-		    *x2P = *x1P;
-		    break;
-
-		    case 'o':
-		    b1 = PNM_GET1( *x1P );
-		    b2 = PNM_GET1( *x2P );
-		    if ( b1 != 0 || b2 != 0 )
-			PNM_ASSIGN1( *x2P, newmaxval );
-		    else
-			PNM_ASSIGN1( *x2P, 0 );
-		    break;
-
-		    case 'a':
-		    b1 = PNM_GET1( *x1P );
-		    b2 = PNM_GET1( *x2P );
-		    if ( b1 != 0 && b2 != 0 )
-			PNM_ASSIGN1( *x2P, newmaxval );
-		    else
-			PNM_ASSIGN1( *x2P, 0 );
-		    break;
-
-		    case 'x':
-		    b1 = PNM_GET1( *x1P );
-		    b2 = PNM_GET1( *x2P );
-		    if ( ( b1 != 0 && b2 == 0 ) || ( b1 == 0 && b2 != 0 ) )
-			PNM_ASSIGN1( *x2P, newmaxval );
-		    else
-			PNM_ASSIGN1( *x2P, 0 );
-		    break;
-
-		    default:
-		    pm_error( "can't happen" );
-		    }
-		}
-	    }
-
-	pnm_writepnmrow( stdout, xelrow2, cols2, newmaxval, newformat, 0 );
-	}
-    
-    pm_close( ifp1 );
-    pm_close( ifp2 );
-    pm_close( stdout );
+        pasteNonPbm(fpInset, fpBase,
+                    formatInset, formatBase, newformat,
+                    maxvalInset, maxvalBase,
+                    rowsInset, rowsBase, colsInset, colsBase,
+                    insertCol, insertRow);
 
-    exit( 0 );
-    }
+    pm_close(fpInset);
+    pm_close(fpBase);
+    pm_close(stdout);
+
+    return 0;
+}
diff --git a/editor/pnmremap.c b/editor/pnmremap.c
index 1ed07fdb..db35e2c0 100644
--- a/editor/pnmremap.c
+++ b/editor/pnmremap.c
@@ -40,15 +40,6 @@ enum missingMethod {
     MISSING_CLOSE
 };
 
-#define FS_SCALE 1024
-
-struct fserr {
-    long** thiserr;
-    long** nexterr;
-    bool fsForward;
-};
-
-
 struct cmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
@@ -56,6 +47,7 @@ struct cmdlineInfo {
     const char * inputFilespec;  /* Filespec of input file */
     const char * mapFilespec;    /* Filespec of colormap file */
     unsigned int floyd;   /* Boolean: -floyd/-fs option */
+    unsigned int norandom;
     enum missingMethod missingMethod;
     char * missingcolor;      
         /* -missingcolor value.  Null if not specified */
@@ -98,6 +90,8 @@ parseCommandLine (int argc, char ** argv,
             NULL,                       &nofloyd, 0);
     OPTENT3(0,   "nofs",         OPT_FLAG,   
             NULL,                       &nofloyd, 0);
+    OPTENT3(0,   "norandom",     OPT_FLAG,   
+            NULL,                       &cmdlineP->norandom, 0);
     OPTENT3(0,   "firstisdefault", OPT_FLAG,   
             NULL,                       &firstisdefault, 0);
     OPTENT3(0,   "mapfile",      OPT_STRING, 
@@ -143,53 +137,112 @@ parseCommandLine (int argc, char ** argv,
 }
 
 
+typedef enum {
+    ADJUST_NONE,
+    ADJUST_RGBTO1,
+    ADJUST_GRAYSCALETO3
+} depthAdjustment;
+
+
 
 static void
-rgbToDepth1(const struct pam * const pamP,
-            tuple *            const tupleRow) {
-    
-    unsigned int col;
+rgbToDepth1(tuple const tuple) {
 
-    for (col = 0; col < pamP->width; ++col) {
-        unsigned int plane;
-        double grayvalue;
-        grayvalue = 0.0;  /* initial value */
-        for (plane = 0; plane < pamP->depth; ++plane)
-            grayvalue += pnm_lumin_factor[plane] * tupleRow[col][plane];
-        tupleRow[col][0] = (sample) (grayvalue + 0.5);
-    }
+    unsigned int plane;
+    double grayvalue;
+
+    grayvalue = 0.0;  /* initial value */
+
+    for (plane = 0; plane < 3; ++plane)
+        grayvalue += pnm_lumin_factor[plane] * tuple[plane];
+
+    tuple[0] = (sample) (grayvalue + 0.5);
+}
+
+
+
+static void
+grayscaleToDepth3(tuple const tuple) {
+
+    tuple[1] = tuple[0];
+    tuple[2] = tuple[0];
 }
 
 
 
 static void
-grayscaleToDepth3(const struct pam * const pamP,
-                  tuple *            const tupleRow) {
+adjustDepthTuple(tuple           const tuple,
+                 depthAdjustment const adjustment) {
     
-    unsigned int col;
+    switch (adjustment) {
+    case ADJUST_NONE:
+        break;
+    case ADJUST_RGBTO1:
+        rgbToDepth1(tuple);
+        break;
+    case ADJUST_GRAYSCALETO3:
+        grayscaleToDepth3(tuple);
+        break;
+    }
+}
+
 
-    assert(pamP->allocation_depth >= 3);
 
-    for (col = 0; col < pamP->width; ++col) {
-        tupleRow[col][1] = tupleRow[col][0];
-        tupleRow[col][2] = tupleRow[col][0];
+static void
+inverseAdjustDepthTuple(tuple           const tuple,
+                        depthAdjustment const adjustment) {
+    
+    switch (adjustment) {
+    case ADJUST_NONE:
+        break;
+    case ADJUST_RGBTO1:
+        grayscaleToDepth3(tuple);
+        break;
+    case ADJUST_GRAYSCALETO3:
+        rgbToDepth1(tuple);
+        break;
     }
 }
 
 
 
 static void
-adjustDepth(const struct pam * const pamP,
-            tuple *            const tupleRow,
-            unsigned int       const newDepth) {
+adjustDepthRow(tuple *         const tupleRow,
+               unsigned int    const width,
+               depthAdjustment const adjustment) {
 /*----------------------------------------------------------------------------
-   Change the depth of the raster row tupleRow[] of the image
-   described by 'pamP' to newDepth.
+   Change tupleRow[] depth as indicated by 'adjustment',
+   i.e. turned from RGB to grayscale or grayscale to RGB.
+
+   We assume tupleRow[] is consistent with 'adjustment' -- i.e. if
+   'adjustment' says grayscale to RGB, tupleRow[] has an allocation depth of
+   at least 3 and if 'adjustment' says from RGB to grayscale, tupleRow[] has
+   RGB tuples.
+-----------------------------------------------------------------------------*/
+    if (adjustment == ADJUST_NONE) {
+    } else {
+        unsigned int col;
+
+        for (col = 0; col < width; ++col) {
+            if (adjustment == ADJUST_RGBTO1)
+                rgbToDepth1(tupleRow[col]);
+            else {
+                assert(adjustment == ADJUST_GRAYSCALETO3);
+                grayscaleToDepth3(tupleRow[col]);
+            }
+        }
+    }
+}
+
 
-   We don't change the memory allocation; tupleRow[] must already have
-   space allocated for at least 'newDepth' planes.  When we're done,
-   all but the first 'newDepth' planes are meaningless, but the space is
-   still there.
+
+static void
+selectDepthAdjustment(const struct pam * const pamP,
+                      unsigned int       const newDepth,
+                      depthAdjustment *  const adjustmentP) {
+/*----------------------------------------------------------------------------
+   Determine what kind of depth adjustment the pixels of an image described
+   by 'pamP' need to be comparable to pixels with depth 'newDepth'.
 
    The only depth changes we know how to do are:
 
@@ -197,15 +250,23 @@ adjustDepth(const struct pam * const pamP,
 
        We change it to grayscale or black and white.
 
+       For this, we return *adjustmentP == ADJUST_RGBTO1.
+
      - from tuple type GRAYSCALE or BLACKANDWHITE depth 1 to depth 3.
 
        We change it to RGB.
 
+       For this, we return *adjustmentP == ADJUST_GRAYSCALETO3.
+
    For any other depth change request, we issue an error message and abort
    the program.
------------------------------------------------------------------------------*/
-    if (newDepth != pamP->depth) {
 
+   If 'newDepth' is the same depth as the original (no depth change required),
+   we return *adjustmentP == ADJUST_NONE.
+-----------------------------------------------------------------------------*/
+    if (newDepth == pamP->depth)
+        *adjustmentP = ADJUST_NONE;
+    else {
         if (stripeq(pamP->tuple_type, "RGB")) {
             if (newDepth != 1) {
                 pm_error("Map image depth of %u differs from input image "
@@ -214,7 +275,7 @@ adjustDepth(const struct pam * const pamP,
                          "an RGB tuple is 1.",
                          newDepth, pamP->depth);
             } else
-                rgbToDepth1(pamP, tupleRow);
+                *adjustmentP = ADJUST_RGBTO1;
         } else if (stripeq(pamP->tuple_type, "GRAYSCALE") ||
                    stripeq(pamP->tuple_type, "BLACKANDWHITE")) {
             if (newDepth != 3) {
@@ -225,7 +286,7 @@ adjustDepth(const struct pam * const pamP,
                          "a GRAYSCALE or BLACKANDWHITE tuple is 3.",
                          newDepth, pamP->depth);
             } else
-                grayscaleToDepth3(pamP, tupleRow);
+                *adjustmentP = ADJUST_GRAYSCALETO3;
         } else {
             pm_error("Map image depth of %u differs from input image depth "
                      "of %u, and the input image does not have a tuple type "
@@ -240,7 +301,6 @@ adjustDepth(const struct pam * const pamP,
 
 
 
-
 static void
 computeColorMapFromMap(struct pam *   const mappamP, 
                        tuple **       const maptuples, 
@@ -263,15 +323,103 @@ computeColorMapFromMap(struct pam *   const mappamP,
         pnm_computetuplefreqtable(mappamP, maptuples, MAXCOLORS, &colors);
     if (*colormapP == NULL)
         pm_error("too many colors in colormap!");
-    pm_message("%d colors found in colormap", colors);
+    pm_message("%u colors found in colormap", colors);
     *newcolorsP = colors;
 }
 
 
 
+#define FS_SCALE 1024
+
+struct fserr {
+    unsigned int width;
+        /* Width of the image being dithered */
+    long ** thiserr;
+    long ** nexterr;
+    bool    fsForward;
+        /* We are in a left-to-right row */
+    int     begCol;
+        /* First column in the sweep.  Determined by 'fsForward': either
+           the leftmost or rightmost column in the row
+        */
+    int     endCol;
+        /* Column after the last column in the sweep.  Determined by
+           'fsForward': either one past the left end or one past the right
+           end of the row.
+        */
+    int     step;
+        /* What we add to a column number to get the next one in the sweep.
+           Determined by 'fsForward': +1 or -1.
+        */
+};
+
+
+
+static void
+randomizeError(long **      const err,
+               unsigned int const width,
+               unsigned int const depth) {
+/*----------------------------------------------------------------------------
+   Set a random error in the range [-1 .. 1] (normalized via FS_SCALE)
+   in the error array err[][].
+-----------------------------------------------------------------------------*/
+    unsigned int col;
+
+    srand(pm_randseed());
+
+    for (col = 0; col < width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < depth; ++plane) 
+            err[plane][col] = rand() % (FS_SCALE * 2) - FS_SCALE;
+    }
+}
+
+
+
+static void
+zeroError(long **      const err,
+          unsigned int const width,
+          unsigned int const depth) {
+/*----------------------------------------------------------------------------
+   Set all errors to zero in the error array err[][].
+-----------------------------------------------------------------------------*/
+    unsigned int col;
+
+    for (col = 0; col < width; ++col) {
+        unsigned int plane;
+        for (plane = 0; plane < depth; ++plane) 
+            err[plane][col] = 0;
+    }
+}
+
+
+
+static void
+fserrSetForward(struct fserr * const fserrP) {
+
+    fserrP->fsForward = TRUE;
+    fserrP->begCol = 0;
+    fserrP->endCol = fserrP->width;
+    fserrP->step   = +1;
+}
+
+
+
+static void
+fserrSetBackward(struct fserr * const fserrP) {
+
+    fserrP->fsForward = FALSE;
+    fserrP->begCol = fserrP->width - 1;
+    fserrP->endCol = -1;
+    fserrP->step   = -1;
+}
+
+
+
 static void
 initFserr(struct pam *   const pamP,
-          struct fserr * const fserrP) {
+          struct fserr * const fserrP,
+          bool           const initRandom) {
 /*----------------------------------------------------------------------------
    Initialize the Floyd-Steinberg error vectors
 -----------------------------------------------------------------------------*/
@@ -279,6 +427,8 @@ initFserr(struct pam *   const pamP,
 
     unsigned int const fserrSize = pamP->width + 2;
 
+    fserrP->width = pamP->width;
+
     MALLOCARRAY(fserrP->thiserr, pamP->depth);
     if (fserrP->thiserr == NULL)
         pm_error("Out of memory allocating Floyd-Steinberg structures "
@@ -299,20 +449,12 @@ initFserr(struct pam *   const pamP,
                      "for Plane %u, size %u", plane, fserrSize);
     }
 
-    srand((int)(time(0) ^ getpid()));
+    if (initRandom)
+        randomizeError(fserrP->thiserr, fserrSize, pamP->depth);
+    else
+        zeroError(fserrP->thiserr, fserrSize, pamP->depth);
 
-    {
-        int col;
-
-        for (col = 0; col < fserrSize; ++col) {
-            unsigned int plane;
-            for (plane = 0; plane < pamP->depth; ++plane) 
-                fserrP->thiserr[plane][col] = 
-                    rand() % (FS_SCALE * 2) - FS_SCALE;
-                    /* (random errors in [-1 .. 1]) */
-        }
-    }
-    fserrP->fsForward = TRUE;
+    fserrSetForward(fserrP);
 }
 
 
@@ -333,7 +475,8 @@ floydInitRow(struct pam * const pamP, struct fserr * const fserrP) {
 
 static void
 floydAdjustColor(struct pam *   const pamP, 
-                 tuple          const tuple, 
+                 tuple          const intuple, 
+                 tuple          const outtuple, 
                  struct fserr * const fserrP, 
                  int            const col) {
 /*----------------------------------------------------------------------------
@@ -343,8 +486,8 @@ floydAdjustColor(struct pam *   const pamP,
 
     for (plane = 0; plane < pamP->depth; ++plane) {
         long int const s =
-            tuple[plane] + fserrP->thiserr[plane][col+1] / FS_SCALE;
-        tuple[plane] = MIN(pamP->maxval, MAX(0,s));
+            intuple[plane] + fserrP->thiserr[plane][col+1] / FS_SCALE;
+        outtuple[plane] = MIN(pamP->maxval, MAX(0,s));
     }
 }
 
@@ -396,7 +539,11 @@ floydSwitchDir(struct pam * const pamP, struct fserr * const fserrP) {
         fserrP->thiserr[plane] = fserrP->nexterr[plane];
         fserrP->nexterr[plane] = temperr;
     }
-    fserrP->fsForward = ! fserrP->fsForward;
+
+    if (fserrP->fsForward)
+        fserrSetBackward(fserrP);
+    else
+        fserrSetForward(fserrP);
 }
 
 
@@ -616,21 +763,106 @@ lookupThroughHash(struct pam *            const pamP,
 
 
 static void
-convertRow(struct pam *            const pamP, 
-           tuple                         tuplerow[],
-           tupletable              const colormap,
-           struct colormapFinder * const colorFinderP,
-           tuplehash               const colorhash, 
-           bool *                  const usehashP,
-           bool                    const floyd, 
-           tuple                   const defaultColor,
-           struct fserr *          const fserrP,
-           unsigned int *          const missingCountP) {
+mapTuple(struct pam *            const pamP,
+         tuple                   const inTuple,
+         tuple                   const defaultColor,
+         tupletable              const colormap,
+         struct colormapFinder * const colorFinderP,
+         tuplehash               const colorhash, 
+         bool *                  const usehashP,
+         tuple                   const outTuple,
+         bool *                  const missingP) {
+
+    int colormapIndex;
+        /* Index into the colormap of the replacement color, or -1 if
+           there is no usable color in the color map.
+        */
+
+    lookupThroughHash(pamP, inTuple, !!defaultColor, colorFinderP, 
+                      colorhash, &colormapIndex, usehashP);
+
+    if (colormapIndex == -1) {
+        *missingP = TRUE;
+
+        assert(defaultColor);  /* Otherwise, lookup would have succeeded */
+
+        pnm_assigntuple(pamP, outTuple, defaultColor);
+    } else {
+        *missingP = FALSE;
+        pnm_assigntuple(pamP, outTuple, colormap[colormapIndex]->tuple);
+    }
+}
+
+
+
+static void
+convertRowStraight(struct pam *            const inpamP,
+                   struct pam *            const outpamP, 
+                   tuple                         inrow[],
+                   depthAdjustment         const depthAdjustment,
+                   tupletable              const colormap,
+                   struct colormapFinder * const colorFinderP,
+                   tuplehash               const colorhash, 
+                   bool *                  const usehashP,
+                   tuple                   const defaultColor,
+                   tuple                         outrow[],
+                   unsigned int *          const missingCountP) {
 /*----------------------------------------------------------------------------
-  Replace the colors in row tuplerow[] (described by *pamP) with the
-  new colors.
+  Like convertRow(), compute outrow[] from inrow[], replacing each pixel with
+  the new colors.  Do a straight pixel for pixel remap; no dithering.
 
-  Use and update the Floyd-Steinberg state *fserrP.
+  Return the number of pixels that were not matched in the color map as
+  *missingCountP.
+
+  *colorFinderP is a color finder based on 'colormap' -- it tells us what
+  index of 'colormap' corresponds to a certain color.
+-----------------------------------------------------------------------------*/
+    unsigned int col;
+    unsigned int missingCount;
+    
+    /* The following modify tuplerow, to make it consistent with
+     *outpamP instead of *inpamP.
+     */
+    assert(outpamP->allocation_depth >= inpamP->depth);
+
+    pnm_scaletuplerow(inpamP, outrow, inrow, outpamP->maxval);
+
+    adjustDepthRow(outrow, outpamP->width, depthAdjustment);
+
+    missingCount = 0;  /* initial value */
+    
+    for (col = 0; col < outpamP->width; ++col) {
+        bool missing;
+        mapTuple(outpamP, outrow[col], defaultColor,
+                 colormap, colorFinderP,
+                 colorhash, usehashP, outrow[col], &missing);
+
+        if (missing)
+            ++missingCount;
+    }
+
+    *missingCountP = missingCount;
+}
+
+
+
+static void
+convertRowDither(struct pam *            const inpamP,
+                 struct pam *            const outpamP,
+                 tuple                   const inrow[],
+                 depthAdjustment         const depthAdjustment,
+                 tupletable              const colormap,
+                 struct colormapFinder * const colorFinderP,
+                 tuplehash               const colorhash, 
+                 bool *                  const usehashP,
+                 tuple                   const defaultColor,
+                 struct fserr *          const fserrP,
+                 tuple                         outrow[],
+                 unsigned int *          const missingCountP) {
+/*----------------------------------------------------------------------------
+  Like convertRow(), compute outrow[] from inrow[], replacing each pixel with
+  the new colors.  Do a Floyd-Steinberg dither, using and updating the error
+  accumulator *fserrP.
 
   Return the number of pixels that were not matched in the color map as
   *missingCountP.
@@ -638,79 +870,136 @@ convertRow(struct pam *            const pamP,
   *colorFinderP is a color finder based on 'colormap' -- it tells us what
   index of 'colormap' corresponds to a certain color.
 -----------------------------------------------------------------------------*/
-    int col;
-    int limitcol;
-        /* The column at which to stop processing the row.  If we're scanning
-           forwards, this is the rightmost column.  If we're scanning 
-           backward, this is the leftmost column.
+    tuple const ditheredTuple = pnm_allocpamtuple(inpamP);
+        /* The input tuple we're converting, adjusted by the dither */
+    tuple const normTuple = pnm_allocpamtuple(outpamP);
+        /* Same as above, normalized to the maxval of the output file /
+           colormap.
         */
-    
-    if (floyd)
-        floydInitRow(pamP, fserrP);
+    unsigned int missingCount;
+    int col;
 
-    *missingCountP = 0;  /* initial value */
+    floydInitRow(inpamP, fserrP);
+
+    missingCount = 0;  /* initial value */
     
-    if ((!floyd) || fserrP->fsForward) {
-        col = 0;
-        limitcol = pamP->width;
-    } else {
-        col = pamP->width - 1;
-        limitcol = -1;
+    for (col = fserrP->begCol; col != fserrP->endCol; col += fserrP->step) {
+        bool missing;
+
+        floydAdjustColor(inpamP, inrow[col], ditheredTuple, fserrP, col);
+
+        /* Convert tuple to the form of those in the colormap */
+        assert(outpamP->allocation_depth >= inpamP->depth);
+        pnm_scaletuple(inpamP, normTuple, ditheredTuple, outpamP->maxval);
+        adjustDepthTuple(normTuple, depthAdjustment);
+
+        mapTuple(outpamP, normTuple, defaultColor,
+                 colormap, colorFinderP,
+                 colorhash, usehashP, outrow[col], &missing);
+
+        if (missing)
+            ++missingCount;
+
+        /* Convert tuple back to the form of the input, where dithering
+           takes place.
+        */
+        pnm_scaletuple(outpamP, normTuple, outrow[col], inpamP->maxval);
+        inverseAdjustDepthTuple(normTuple, depthAdjustment);
+
+        floydPropagateErr(inpamP, fserrP, col, inrow[col], normTuple);
     }
-    do {
-        int colormapIndex;
-            /* Index into the colormap of the replacement color, or -1 if
-               there is no usable color in the color map.
-            */
 
-        if (floyd) 
-            floydAdjustColor(pamP, tuplerow[col], fserrP, col);
+    floydSwitchDir(inpamP, fserrP);
 
-        lookupThroughHash(pamP, tuplerow[col], 
-                          !!defaultColor, colorFinderP, 
-                          colorhash, &colormapIndex, usehashP);
-        if (floyd)
-            floydPropagateErr(pamP, fserrP, col, tuplerow[col], 
-                              colormap[colormapIndex]->tuple);
+    pnm_freepamtuple(normTuple);
+    pnm_freepamtuple(ditheredTuple);
 
-        if (colormapIndex == -1) {
-            ++*missingCountP;
+    *missingCountP = missingCount;
+}
 
-            assert(defaultColor);  // Otherwise, lookup would have succeeded
 
-            pnm_assigntuple(pamP, tuplerow[col], defaultColor);
-        } else 
-            pnm_assigntuple(pamP, tuplerow[col], 
-                            colormap[colormapIndex]->tuple);
 
-        if (floyd && !fserrP->fsForward) 
-            --col;
-        else
-            ++col;
-    } while (col != limitcol);
+static void
+convertRow(struct pam *            const inpamP,
+           struct pam *            const outpamP,
+           tuple                         inrow[],
+           depthAdjustment               depthAdjustment,
+           tupletable              const colormap,
+           struct colormapFinder * const colorFinderP,
+           tuplehash               const colorhash, 
+           bool *                  const usehashP,
+           bool                    const floyd, 
+           tuple                   const defaultColor,
+           struct fserr *          const fserrP,
+           tuple                         outrow[],
+           unsigned int *          const missingCountP) {
+/*----------------------------------------------------------------------------
+  Replace the colors in row tuplerow[] (described by *inpamP) with the
+  new colors and convert so it is described by *outpamP.
+
+  Use and update the Floyd-Steinberg state *fserrP.
+
+  Return the number of pixels that were not matched in the color map as
+  *missingCountP.
 
+  *colorFinderP is a color finder based on 'colormap' -- it tells us what
+  index of 'colormap' corresponds to a certain color.
+
+  outrow[] doubles as a work space, so we require it to have an allocation
+  depth at least as great as that of inrow[].
+-----------------------------------------------------------------------------*/
+    /* The following both consults and adds to 'colorhash' and
+       its associated '*usehashP'.  It modifies 'tuplerow' too.
+    */
     if (floyd)
-        floydSwitchDir(pamP, fserrP);
+        convertRowDither(inpamP, outpamP, inrow,
+                         depthAdjustment, colormap, colorFinderP, colorhash,
+                         usehashP, defaultColor,
+                         fserrP, outrow, missingCountP);
+    else 
+        convertRowStraight(inpamP, outpamP, inrow,
+                           depthAdjustment, colormap, colorFinderP, colorhash,
+                           usehashP, defaultColor,
+                           outrow, missingCountP);
 }
 
 
 
 static void
-copyRaster(struct pam *   const inpamP, 
-           struct pam *   const outpamP,
-           tupletable     const colormap, 
-           unsigned int   const colormapSize,
-           bool           const floyd, 
-           tuple          const defaultColor, 
-           unsigned int * const missingCountP) {
+copyRaster(struct pam *       const inpamP, 
+           struct pam *       const outpamP,
+           tupletable         const colormap, 
+           unsigned int       const colormapSize,
+           bool               const floyd, 
+           bool               const randomize,
+           tuple              const defaultColor, 
+           unsigned int *     const missingCountP) {
 
     tuplehash const colorhash = pnm_createtuplehash();
+
+    tuple * inrow;
+    tuple * outrow;
+    struct pam workpam;
+        /* This is for work space we use for building up the output
+           pixels.  To save time and memory, we modify them in place in a
+           buffer, which ultimately holds the output pixels.  This pam
+           structure is thus the same as the *outpamP, but with a tuple
+           allocation depth large enough to handle intermediate results.
+        */
+    depthAdjustment depthAdjustment;
     struct colormapFinder * colorFinderP;
     bool usehash;
     struct fserr fserr;
-    tuple * tuplerow = pnm_allocpamrow(inpamP);
     int row;
 
+    workpam = *outpamP;
+    workpam.allocation_depth = MAX(workpam.depth, inpamP->depth);
+    workpam.size             = sizeof(workpam);
+    workpam.len              = PAM_STRUCT_SIZE(allocation_depth);
+
+    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 "
@@ -718,40 +1007,34 @@ copyRaster(struct pam *   const inpamP,
                  "specify -firstisdefault or -missingcolor)",
                  (unsigned int)outpamP->maxval, (unsigned int)inpamP->maxval);
 
+    selectDepthAdjustment(inpamP, outpamP->depth, &depthAdjustment);
+
     usehash = TRUE;
 
     createColormapFinder(outpamP, colormap, colormapSize, &colorFinderP);
 
     if (floyd)
-        initFserr(inpamP, &fserr);
+        initFserr(inpamP, &fserr, randomize);
 
     *missingCountP = 0;  /* initial value */
 
     for (row = 0; row < inpamP->height; ++row) {
         unsigned int missingCount;
 
-        pnm_readpamrow(inpamP, tuplerow);
+        pnm_readpamrow(inpamP, inrow);
 
-        /* The following modify tuplerow, to make it consistent with
-           *outpamP instead of *inpamP.
-        */
-        assert(inpamP->allocation_depth >= outpamP->depth);
-        pnm_scaletuplerow(inpamP, tuplerow, tuplerow, outpamP->maxval);
-        adjustDepth(inpamP, tuplerow, outpamP->depth); 
-
-        /* The following both consults and adds to 'colorhash' and
-           its associated 'usehash'.  It modifies 'tuplerow' too.
-        */
-        convertRow(outpamP, tuplerow, colormap, colorFinderP, colorhash, 
-                   &usehash,
-                   floyd, defaultColor, &fserr, &missingCount);
+        convertRow(inpamP, &workpam, inrow,
+                   depthAdjustment, colormap, colorFinderP, colorhash,
+                   &usehash, floyd, defaultColor,
+                   &fserr,  outrow, &missingCount);
         
         *missingCountP += missingCount;
         
-        pnm_writepamrow(outpamP, tuplerow);
+        pnm_writepamrow(outpamP, outrow);
     }
     destroyColormapFinder(colorFinderP);
-    pnm_freepamrow(tuplerow);
+    pnm_freepamrow(inrow);
+    pnm_freepamrow(outrow);
     pnm_destroytuplehash(colorhash);
 }
 
@@ -763,6 +1046,7 @@ remap(FILE *             const ifP,
       tupletable         const colormap, 
       unsigned int       const colormapSize,
       bool               const floyd,
+      bool               const randomize,
       tuple              const defaultColor,
       bool               const verbose) {
 /*----------------------------------------------------------------------------
@@ -801,7 +1085,7 @@ remap(FILE *             const ifP,
         pnm_setminallocationdepth(&inpam, outpam.depth);
     
         copyRaster(&inpam, &outpam, colormap, colormapSize, floyd,
-                   defaultColor, &missingCount);
+                   randomize, defaultColor, &missingCount);
         
         if (verbose)
             pm_message("%u pixels not matched in color map", missingCount);
@@ -920,7 +1204,7 @@ main(int argc, char * argv[] ) {
     }
 
     remap(ifP, &outpamCommon, colormap, colormapSize, 
-          cmdline.floyd, defaultColor,
+          cmdline.floyd, !cmdline.norandom, defaultColor,
           cmdline.verbose);
 
     pnm_freepamtuple(firstColor);
diff --git a/editor/pnmrotate.c b/editor/pnmrotate.c
index ba817c17..ba37f4e0 100644
--- a/editor/pnmrotate.c
+++ b/editor/pnmrotate.c
@@ -15,9 +15,11 @@
 #include <math.h>
 #include <assert.h>
 
-#include "pnm.h"
-#include "shhopt.h"
+#include "pm_c_util.h"
 #include "mallocvar.h"
+#include "shhopt.h"
+#include "ppm.h"
+#include "pnm.h"
 
 #define SCALE 4096
 #define HALFSCALE 2048
@@ -159,27 +161,6 @@ computeNewFormat(bool     const antialias,
 
 
 
-static bool
-isWhite(xel    const color,
-        xelval const maxval) {
-
-    return (PPM_GETR(color) == maxval &&
-            PPM_GETG(color) == maxval &&
-            PPM_GETB(color) == maxval);
-}
-
-
-
-static bool
-isBlack(xel const color) {
-
-    return (PPM_GETR(color) == 0 &&
-            PPM_GETG(color) == 0 &&
-            PPM_GETB(color) == 0);
-}
-
-
-
 static xel
 backgroundColor(const char * const backgroundColorName,
                 xel *        const topRow,
@@ -190,24 +171,7 @@ backgroundColor(const char * const backgroundColorName,
     xel retval;
 
     if (backgroundColorName) {
-        retval = ppm_parsecolor(backgroundColorName, maxval);
-
-        switch(PNM_FORMAT_TYPE(format)) {
-        case PGM_TYPE:
-            if (!PPM_ISGRAY(retval))
-                pm_error("Image is PGM (grayscale), "
-                         "but you specified a non-gray "
-                         "background color '%s'", backgroundColorName);
-
-            break;
-        case PBM_TYPE:
-            if (!isWhite(retval, maxval) && !isBlack(retval))
-                pm_error("Image is PBM (black and white), "
-                         "but you specified '%s', which is neither black "
-                         "nor white, as background color", 
-                         backgroundColorName);
-            break;
-        }
+        retval = pnm_parsecolorxel(backgroundColorName, maxval, format);
     } else 
         retval = pnm_backgroundxelrow(topRow, cols, maxval, format);
 
@@ -725,7 +689,7 @@ shearXToOutputFile(FILE *                 const ofP,
     unsigned int row;
     xel * xelrow;
     
-    pnm_writepnminit(stdout, newcols, rows, maxval, format, 0);
+    pnm_writepnminit(ofP, newcols, rows, maxval, format, 0);
 
     xelrow = pnm_allocrow(newcols);
 
@@ -742,7 +706,7 @@ shearXToOutputFile(FILE *                 const ofP,
         shearFinal(xels[row], xelrow, cols, newcols, format, 
                    bgColor, antialias, shiftAmount, x2shearjunk);
 
-        pnm_writepnmrow(stdout, xelrow, newcols, maxval, format, 0);
+        pnm_writepnmrow(ofP, xelrow, newcols, maxval, format, 0);
     }
     pnm_freerow(xelrow);
 }
diff --git a/editor/pnmscale.c b/editor/pnmscale.c
deleted file mode 100644
index f75f440c..00000000
--- a/editor/pnmscale.c
+++ /dev/null
@@ -1,748 +0,0 @@
-/* pnmscale.c - read a portable anymap and scale it
-**
-** 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.
-**
-*/
-
-/* 
-
-      DON'T ADD NEW FUNCTION TO THIS PROGRAM.  ADD IT TO pamscale.c
-      INSTEAD.
-
-*/
-
- 
-#include <math.h>
-#include <string.h>
-
-#include "pnm.h"
-#include "shhopt.h"
-
-/* The pnm library allows us to code this program without branching cases
-   for PGM and PPM, but we do the branch anyway to speed up processing of 
-   PGM images.
-*/
-
-
-struct cmdline_info {
-    /* All the information the user supplied in the command line,
-       in a form easy for the program to use.
-    */
-    const char *input_filespec;  /* Filespecs of input files */
-    unsigned int xsize;
-    unsigned int ysize;
-    float xscale;
-    float yscale;
-    unsigned int xbox;
-    unsigned int ybox;
-    unsigned int pixels;
-    unsigned int verbose;
-    unsigned int nomix;
-};
-
-
-static void
-parse_command_line(int argc, char ** argv,
-                   struct cmdline_info *cmdline_p) {
-/*----------------------------------------------------------------------------
-   Note that the file spec array we return is stored in the storage that
-   was passed to us as the argv array.
------------------------------------------------------------------------------*/
-    optEntry *option_def = malloc( 100*sizeof( optEntry ) );
-        /* Instructions to optParseOptions3 on how to parse our options.
-         */
-    optStruct3 opt;
-
-    unsigned int option_def_index;
-    unsigned int xysize;
-    int xsize, ysize, pixels;
-    int reduce;
-    float xscale, yscale, scale_parm;
-
-    option_def_index = 0;   /* incremented by OPTENTRY */
-    OPTENT3(0, "xsize",     OPT_UINT,    &xsize,               NULL, 0);
-    OPTENT3(0, "width",     OPT_UINT,    &xsize,               NULL, 0);
-    OPTENT3(0, "ysize",     OPT_UINT,    &ysize,               NULL, 0);
-    OPTENT3(0, "height",    OPT_UINT,    &ysize,               NULL, 0);
-    OPTENT3(0, "xscale",    OPT_FLOAT,   &xscale,              NULL, 0);
-    OPTENT3(0, "yscale",    OPT_FLOAT,   &yscale,              NULL, 0);
-    OPTENT3(0, "pixels",    OPT_UINT,    &pixels,              NULL, 0);
-    OPTENT3(0, "reduce",    OPT_UINT,    &reduce,              NULL, 0);
-    OPTENT3(0, "xysize",    OPT_FLAG,    NULL, &xysize,              0);
-    OPTENT3(0, "verbose",   OPT_FLAG,    NULL, &cmdline_p->verbose,  0);
-    OPTENT3(0, "nomix",     OPT_FLAG,    NULL, &cmdline_p->nomix,    0);
-
-    /* Set the defaults. -1 = unspecified */
-    /* (Now that we're using ParseOptions3, we don't have to do this -1
-       nonsense, but we don't want to risk screwing these complex 
-       option compatibilities up, so we'll convert that later.
-    */
-    xsize = -1;
-    ysize = -1;
-    xscale = -1.0;
-    yscale = -1.0;
-    pixels = -1;
-    reduce = -1;
-
-    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 */
-
-    optParseOptions3( &argc, argv, opt, sizeof(opt), 0 );
-        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
-
-    if (xsize == 0)
-        pm_error("-xsize/width must be greater than zero.");
-    if (ysize == 0)
-        pm_error("-ysize/height must be greater than zero.");
-    if (xscale != -1.0 && xscale <= 0.0)
-        pm_error("-xscale must be greater than zero.");
-    if (yscale != -1.0 && yscale <= 0.0)
-        pm_error("-yscale must be greater than zero.");
-    if (reduce <= 0 && reduce != -1)
-        pm_error("-reduce must be greater than zero.");
-
-    if (xsize != -1 && xscale != -1)
-        pm_error("Cannot specify both -xsize/width and -xscale.");
-    if (ysize != -1 && yscale != -1)
-        pm_error("Cannot specify both -ysize/height and -yscale.");
-    
-    if (xysize && 
-        (xsize != -1 || xscale != -1 || ysize != -1 || yscale != -1 || 
-         reduce != -1 || pixels != -1) )
-        pm_error("Cannot specify -xysize with other dimension options.");
-    if (pixels != -1 && 
-        (xsize != -1 || xscale != -1 || ysize != -1 || yscale != -1 ||
-         reduce != -1) )
-        pm_error("Cannot specify -pixels with other dimension options.");
-    if (reduce != -1 && 
-        (xsize != -1 || xscale != -1 || ysize != -1 || yscale != -1) )
-        pm_error("Cannot specify -reduce with other dimension options.");
-
-    if (pixels == 0)
-        pm_error("-pixels must be greater than zero");
-
-    /* Get the program parameters */
-
-    if (xysize) {
-        /* parameters are xbox, ybox, and optional filespec */
-        scale_parm = 0.0;
-        if (argc-1 < 2)
-            pm_error("You must supply at least two parameters with -xysize:\n "
-                     "x and y dimensions of the bounding box.");
-        else if (argc-1 > 3)
-            pm_error("Too many arguments.  With -xysize, you need 2 or 3 "
-                     "arguments.");
-        else {
-            char * endptr;
-            cmdline_p->xbox = strtol(argv[1], &endptr, 10);
-            if (strlen(argv[1]) > 0 && *endptr != '\0')
-                pm_error("horizontal xysize not an integer: '%s'", argv[1]);
-            if (cmdline_p->xbox <= 0)
-                pm_error("horizontal size is not positive: %d", 
-                         cmdline_p->xbox);
-
-            cmdline_p->ybox = strtol(argv[2], &endptr, 10);
-            if (strlen(argv[2]) > 0 && *endptr != '\0')
-                pm_error("vertical xysize not an integer: '%s'", argv[2]);
-            if (cmdline_p->ybox <= 0)
-                pm_error("vertical size is not positive: %d", 
-                         cmdline_p->ybox);
-            
-            if (argc-1 < 3)
-                cmdline_p->input_filespec = "-";
-            else
-                cmdline_p->input_filespec = argv[3];
-        }
-    } else {
-        cmdline_p->xbox = 0;
-        cmdline_p->ybox = 0;
-        
-        if (xsize == -1 && xscale == -1 && ysize == -1 && yscale == -1
-            && pixels == -1 && reduce == -1) {
-            /* parameters are scale factor and optional filespec */
-            if (argc-1 < 1)
-                pm_error("With no dimension options, you must supply at least "
-                         "one parameter: \nthe scale factor.");
-            else {
-                scale_parm = atof(argv[1]);
-
-                if (scale_parm == 0.0)
-                    pm_error("The scale parameter %s is not "
-                             "a positive number.",
-                             argv[1]);
-                else {
-                    if (argc-1 < 2)
-                        cmdline_p->input_filespec = "-";
-                    else
-                        cmdline_p->input_filespec = argv[2];
-                }
-            }
-        } else {
-            /* Only parameter allowed is optional filespec */
-            if (argc-1 < 1)
-                cmdline_p->input_filespec = "-";
-            else
-                cmdline_p->input_filespec = argv[1];
-
-            if (reduce != -1) {
-                scale_parm = ((double) 1.0) / ((double) reduce);
-                pm_message("reducing by %d gives scale factor of %f.", 
-                           reduce, scale_parm);
-            } else
-                scale_parm = 0.0;
-        }
-    }
-
-    cmdline_p->xsize = xsize == -1 ? 0 : xsize;
-    cmdline_p->ysize = ysize == -1 ? 0 : ysize;
-    cmdline_p->pixels = pixels == -1 ? 0 : pixels;
-
-    if (scale_parm) {
-        cmdline_p->xscale = scale_parm;
-        cmdline_p->yscale = scale_parm;
-    } else {
-        cmdline_p->xscale = xscale == -1.0 ? 0.0 : xscale;
-        cmdline_p->yscale = yscale == -1.0 ? 0.0 : yscale;
-    }
-}
-
-
-
-static void
-compute_output_dimensions(const struct cmdline_info cmdline, 
-                          const int rows, const int cols,
-                          int * newrowsP, int * newcolsP) {
-
-    if (cmdline.pixels) {
-        if (rows * cols <= cmdline.pixels) {
-            *newrowsP = rows;
-            *newcolsP = cols;
-        } else {
-            const double scale =
-                sqrt( (float) cmdline.pixels / ((float) cols * (float) rows));
-            *newrowsP = rows * scale;
-            *newcolsP = cols * scale;
-        }
-    } else if (cmdline.xbox) {
-        const double aspect_ratio = (float) cols / (float) rows;
-        const double box_aspect_ratio = 
-            (float) cmdline.xbox / (float) cmdline.ybox;
-        
-        if (box_aspect_ratio > aspect_ratio) {
-            *newrowsP = cmdline.ybox;
-            *newcolsP = *newrowsP * aspect_ratio + 0.5;
-        } else {
-            *newcolsP = cmdline.xbox;
-            *newrowsP = *newcolsP / aspect_ratio + 0.5;
-        }
-    } else {
-        if (cmdline.xsize)
-            *newcolsP = cmdline.xsize;
-        else if (cmdline.xscale)
-            *newcolsP = cmdline.xscale * cols + .5;
-        else if (cmdline.ysize)
-            *newcolsP = cols * ((float) cmdline.ysize/rows) +.5;
-        else
-            *newcolsP = cols;
-
-        if (cmdline.ysize)
-            *newrowsP = cmdline.ysize;
-        else if (cmdline.yscale)
-            *newrowsP = cmdline.yscale * rows +.5;
-        else if (cmdline.xsize)
-            *newrowsP = rows * ((float) cmdline.xsize/cols) +.5;
-        else
-            *newrowsP = rows;
-    }    
-
-    /* If the calculations above yielded (due to rounding) a zero 
-       dimension, we fudge it up to 1.  We do this rather than considering
-       it a specification error (and dying) because it's friendlier to 
-       automated processes that work on arbitrary input.  It saves them
-       having to check their numbers to avoid catastrophe.
-    */
-
-    if (*newcolsP < 1) *newcolsP = 1;
-    if (*newrowsP < 1) *newrowsP = 1;
-}        
-
-
-
-static void
-horizontal_scale(const xel inputxelrow[], xel newxelrow[], 
-                 const int cols, const int newcols, const float xscale, 
-                 const int format, const xelval maxval,
-                 float * const stretchP) {
-/*----------------------------------------------------------------------------
-   Take the input row inputxelrow[], which is 'cols' columns wide, and
-   scale it by a factor of 'xscale', to create
-   the output row newxelrow[], which is 'newcols' columns wide.
-
-   'format' and 'maxval' describe the Netpbm format of the both input and
-   output rows.
------------------------------------------------------------------------------*/
-    float r, g, b;
-    float fraccoltofill, fraccolleft;
-    unsigned int col;
-    unsigned int newcol;
-    
-    newcol = 0;
-    fraccoltofill = 1.0;  /* Output column is "empty" now */
-    r = g = b = 0;          /* initial value */
-    for (col = 0; col < cols; ++col) {
-        /* Process one pixel from input ('inputxelrow') */
-        fraccolleft = xscale;
-        /* Output all columns, if any, that can be filled using information
-           from this input column, in addition to what's already in the output
-           column.
-        */
-        while (fraccolleft >= fraccoltofill) {
-            /* Generate one output pixel in 'newxelrow'.  It will consist
-               of anything accumulated from prior input pixels in 'r','g', 
-               and 'b', plus a fraction of the current input pixel.
-            */
-            switch (PNM_FORMAT_TYPE(format)) {
-            case PPM_TYPE:
-                r += fraccoltofill * PPM_GETR(inputxelrow[col]);
-                g += fraccoltofill * PPM_GETG(inputxelrow[col]);
-                b += fraccoltofill * PPM_GETB(inputxelrow[col]);
-                PPM_ASSIGN( newxelrow[newcol], 
-                            MIN(maxval, (int) (r + 0.5)), 
-                            MIN(maxval, (int) (g + 0.5)), 
-                            MIN(maxval, (int) (b + 0.5))
-                    );
-                break;
-
-            default:
-                g += fraccoltofill * PNM_GET1(inputxelrow[col]);
-                PNM_ASSIGN1( newxelrow[newcol], MIN(maxval, (int) (g + 0.5)));
-                break;
-            }
-            fraccolleft -= fraccoltofill;
-            /* Set up to start filling next output column */
-            newcol++;
-            fraccoltofill = 1.0;
-            r = g = b = 0.0;
-        }
-        /* There's not enough left in the current input pixel to fill up 
-           a whole output column, so just accumulate the remainder of the
-           pixel into the current output column.
-        */
-        if (fraccolleft > 0.0) {
-            switch (PNM_FORMAT_TYPE(format)) {
-            case PPM_TYPE:
-                r += fraccolleft * PPM_GETR(inputxelrow[col]);
-                g += fraccolleft * PPM_GETG(inputxelrow[col]);
-                b += fraccolleft * PPM_GETB(inputxelrow[col]);
-                break;
-                    
-            default:
-                g += fraccolleft * PNM_GET1(inputxelrow[col]);
-                break;
-            }
-            fraccoltofill -= fraccolleft;
-        }
-    }
-
-    if (newcol < newcols-1 || newcol > newcols)
-        pm_error("Internal error: last column filled is %d, but %d "
-                 "is the rightmost output column.",
-                 newcol, newcols-1);
-
-    if (newcol < newcols ) {
-        /* We were still working on the last output column when we 
-           ran out of input columns.  This would be because of rounding
-           down, and we should be missing only a tiny fraction of that
-           last output column.
-        */
-
-        *stretchP = fraccoltofill;
-
-        switch (PNM_FORMAT_TYPE(format)) {
-        case PPM_TYPE:
-            r += fraccoltofill * PPM_GETR(inputxelrow[cols-1]);
-            g += fraccoltofill * PPM_GETG(inputxelrow[cols-1]);
-            b += fraccoltofill * PPM_GETB(inputxelrow[cols-1]);
-
-            PPM_ASSIGN(newxelrow[newcol], 
-                       MIN(maxval, (int) (r + 0.5)), 
-                       MIN(maxval, (int) (g + 0.5)), 
-                       MIN(maxval, (int) (b + 0.5))
-                );
-            break;
-                
-        default:
-            g += fraccoltofill * PNM_GET1(inputxelrow[cols-1]);
-            PNM_ASSIGN1( newxelrow[newcol], MIN(maxval, (int) (g + 0.5)));
-            break;
-        }
-    } else 
-        *stretchP = 0;
-}
-
-
-
-static void
-zeroAccum(int const cols, int const format, 
-          float rs[], float gs[], float bs[]) {
-
-    int col;
-
-    for ( col = 0; col < cols; ++col )
-        rs[col] = gs[col] = bs[col] = 0.0;
-}
-
-
-
-static void
-accumOutputRow(xel * const xelrow, float const fraction, 
-               float rs[], float gs[], float bs[], 
-               int const cols, int const format) {
-/*----------------------------------------------------------------------------
-   Take 'fraction' times the color in row xelrow and add it to 
-   rs/gs/bs.  'fraction' is less than 1.0.
------------------------------------------------------------------------------*/
-    int col;
-
-    switch ( PNM_FORMAT_TYPE(format) ) {
-    case PPM_TYPE:
-        for ( col = 0; col < cols; ++col ) {
-            rs[col] += fraction * PPM_GETR(xelrow[col]);
-            gs[col] += fraction * PPM_GETG(xelrow[col]);
-            bs[col] += fraction * PPM_GETB(xelrow[col]);
-        }
-        break;
-
-    default:
-        for ( col = 0; col < cols; ++col)
-            gs[col] += fraction * PNM_GET1(xelrow[col]);
-        break;
-    }
-}
-
-
-
-static void
-makeRow(xel * const xelrow, float rs[], float gs[], float bs[],
-        int const cols, xelval const maxval, int const format) {
-/*----------------------------------------------------------------------------
-   Make an xel row at 'xelrow' with format 'format' and
-   maxval 'maxval' out of the color values in 
-   rs[], gs[], and bs[].
------------------------------------------------------------------------------*/
-    int col;
-
-    switch ( PNM_FORMAT_TYPE(format) ) {
-    case PPM_TYPE:
-        for ( col = 0; col < cols; ++col) {
-            PPM_ASSIGN(xelrow[col], 
-                       MIN(maxval, (int) (rs[col] + 0.5)), 
-                       MIN(maxval, (int) (gs[col] + 0.5)), 
-                       MIN(maxval, (int) (bs[col] + 0.5))
-                );
-        }
-        break;
-
-    default:
-        for ( col = 0; col < cols; ++col ) {
-            PNM_ASSIGN1(xelrow[col], 
-                        MIN(maxval, (int) (gs[col] + 0.5)));
-        }
-        break;
-    }
-}
-
-
-
-static void
-scaleWithMixing(FILE * const ifP,
-                int const cols, int const rows,
-                xelval const maxval, int const format,
-                int const newcols, int const newrows,
-                xelval const newmaxval, int const newformat,
-                float const xscale, float const yscale,
-                bool const verbose) {
-/*----------------------------------------------------------------------------
-   Scale the image on input file 'ifP' (which is described by 
-   'cols', 'rows', 'format', and 'maxval') by xscale horizontally and
-   yscale vertically and write the result to standard output as format
-   'newformat' and with maxval 'newmaxval'.
-
-   The input file is positioned past the header, to the beginning of the
-   raster.  The output file is too.
-
-   Mix colors from input rows together in the output rows.
------------------------------------------------------------------------------*/
-    /* Here's how we think of the color mixing scaling operation:  
-       
-       First, I'll describe scaling in one dimension.  Assume we have
-       a one row image.  A raster row is ordinarily a sequence of
-       discrete pixels which have no width and no distance between
-       them -- only a sequence.  Instead, think of the raster row as a
-       bunch of pixels 1 unit wide adjacent to each other.  For
-       example, we are going to scale a 100 pixel row to a 150 pixel
-       row.  Imagine placing the input row right above the output row
-       and stretching it so it is the same size as the output row.  It
-       still contains 100 pixels, but they are 1.5 units wide each.
-       Our goal is to make the output row look as much as possible
-       like the input row, while observing that a pixel can be only
-       one color.
-
-       Output Pixel 0 is completely covered by Input Pixel 0, so we
-       make Output Pixel 0 the same color as Input Pixel 0.  Output
-       Pixel 1 is covered half by Input Pixel 0 and half by Input
-       Pixel 1.  So we make Output Pixel 1 a 50/50 mix of Input Pixels
-       0 and 1.  If you stand back far enough, input and output will
-       look the same.
-
-       This works for all scale factors, both scaling up and scaling down.
-       
-       This program always stretches or squeezes the input row to be the
-       same length as the output row; The output row's pixels are always
-       1 unit wide.
-
-       The same thing works in the vertical direction.  We think of
-       rows as stacked strips of 1 unit height.  We conceptually
-       stretch the image vertically first (same process as above, but
-       in place of a single-color pixels, we have a vector of colors).
-       Then we take each row this vertical stretching generates and
-       stretch it horizontally.  
-    */
-
-    xel* xelrow;  /* An input row */
-    xel* vertScaledRow;
-        /* An output row after vertical scaling, but before horizontal
-           scaling
-        */
-    xel* newxelrow;
-    float rowsleft;
-        /* The number of rows of output that need to be formed from the
-           current input row (the one in xelrow[]), less the number that 
-           have already been formed (either in the rs/gs/bs accumulators
-           or output to the file).  This can be fractional because of the
-           way we define rows as having height.
-        */
-    float fracrowtofill;
-        /* The fraction of the current output row (the one in vertScaledRow[])
-           that hasn't yet been filled in from an input row.
-        */
-    float *rs, *gs, *bs;
-        /* The red, green, and blue color intensities so far accumulated
-           from input rows for the current output row.
-        */
-    int rowsread;
-        /* Number of rows of the input file that have been read */
-    int row;
-    
-    xelrow = pnm_allocrow(cols); 
-    vertScaledRow = pnm_allocrow(cols);
-    rs = (float*) pm_allocrow( cols, sizeof(float) );
-    gs = (float*) pm_allocrow( cols, sizeof(float) );
-    bs = (float*) pm_allocrow( cols, sizeof(float) );
-    rowsread = 0;
-    rowsleft = 0.0;
-    zeroAccum(cols, format, rs, gs, bs);
-    fracrowtofill = 1.0;
-
-    newxelrow = pnm_allocrow( newcols );
-
-    for ( row = 0; row < newrows; ++row ) {
-        /* First scale Y from xelrow[] into vertScaledRow[]. */
-
-        if ( newrows == rows ) { /* shortcut Y scaling if possible */
-            pnm_readpnmrow( ifP, vertScaledRow, cols, newmaxval, format );
-	    } else {
-            while (fracrowtofill > 0) {
-                if (rowsleft <= 0.0) {
-                    if (rowsread < rows) {
-                        pnm_readpnmrow(ifP, xelrow, cols, newmaxval, format);
-                        ++rowsread;
-                    } else {
-                        /* We need another input row to fill up this
-                           output row, but there aren't any more.
-                           That's because of rounding down on our
-                           scaling arithmetic.  So we go ahead with
-                           the data from the last row we read, which
-                           amounts to stretching out the last output
-                           row.  
-                        */
-                        if (verbose)
-                            pm_message("%f of bottom row stretched due to "
-                                       "arithmetic imprecision", 
-                                       fracrowtofill);
-                    }
-                    rowsleft = yscale;
-                }
-                if (rowsleft < fracrowtofill) {
-                    accumOutputRow(xelrow, rowsleft, rs, gs, bs, 
-                                   cols, format);
-                    fracrowtofill -= rowsleft;
-                    rowsleft = 0.0;
-                } else {
-                    accumOutputRow(xelrow, fracrowtofill, rs, gs, bs,
-                                   cols, format);
-                    rowsleft = rowsleft - fracrowtofill;
-                    fracrowtofill = 0.0;
-                }
-            }
-            makeRow(vertScaledRow, rs, gs, bs, cols, newmaxval, format);
-            zeroAccum(cols, format, rs, gs, bs);
-            fracrowtofill = 1.0;
-	    }
-
-        /* Now scale vertScaledRow horizontally into newxelrow and write
-           it out. 
-        */
-
-        if (newcols == cols)	/* shortcut X scaling if possible */
-            pnm_writepnmrow(stdout, vertScaledRow, newcols, 
-                            newmaxval, newformat, 0);
-        else {
-            float stretch;
-
-            horizontal_scale(vertScaledRow, newxelrow, cols, newcols, xscale, 
-                             format, newmaxval, &stretch);
-            
-            if (verbose && row == 0)
-                pm_message("%f of right column stretched due to "
-                           "arithmetic imprecision", 
-                           stretch);
-            
-            pnm_writepnmrow(stdout, newxelrow, newcols, 
-                            newmaxval, newformat, 0 );
-        }
-	}
-    pnm_freerow(newxelrow);
-    pnm_freerow(xelrow);
-    pnm_freerow(vertScaledRow);
-}
-
-
-
-static void
-scaleWithoutMixing(FILE * const ifP,
-                   int const cols, int const rows,
-                   xelval const maxval, int const format,
-                   int const newcols, int const newrows,
-                   xelval const newmaxval, int const newformat,
-                   float const xscale, float const yscale) {
-/*----------------------------------------------------------------------------
-   Scale the image on input file 'ifP' (which is described by 
-   'cols', 'rows', 'format', and 'maxval') by xscale horizontally and
-   yscale vertically and write the result to standard output as format
-   'newformat' and with maxval 'newmaxval'.
-
-   The input file is positioned past the header, to the beginning of the
-   raster.  The output file is too.
-
-   Don't mix colors from different input pixels together in the output
-   pixels.  Each output pixel is an exact copy of some corresponding 
-   input pixel.
------------------------------------------------------------------------------*/
-    xel* xelrow;  /* An input row */
-    xel* newxelrow;
-    int row;
-    int rowInXelrow;
-
-    xelrow = pnm_allocrow(cols); 
-    rowInXelrow = -1;
-
-    newxelrow = pnm_allocrow(newcols);
-
-    for (row = 0; row < newrows; ++row) {
-        int col;
-        
-        int const inputRow = (int) (row / yscale);
-
-        for (; rowInXelrow < inputRow; ++rowInXelrow) 
-            pnm_readpnmrow(ifP, xelrow, cols, newmaxval, format);
-        
-
-        for (col = 0; col < newcols; ++col) {
-            int const inputCol = (int) (col / xscale);
-            
-            newxelrow[col] = xelrow[inputCol];
-        }
-
-        pnm_writepnmrow(stdout, newxelrow, newcols, 
-                        newmaxval, newformat, 0 );
-	}
-    pnm_freerow(xelrow);
-    pnm_freerow(newxelrow);
-}
-
-
-
-int
-main(int argc, char **argv ) {
-
-    struct cmdline_info cmdline;
-    FILE* ifP;
-    int rows, cols, format, newformat, newrows, newcols;
-    xelval maxval, newmaxval;
-    float xscale, yscale;
-
-    pnm_init( &argc, argv );
-
-    parse_command_line(argc, argv, &cmdline);
-
-    ifP = pm_openr(cmdline.input_filespec);
-
-    pnm_readpnminit( ifP, &cols, &rows, &maxval, &format );
-
-    /* Promote PBM files to PGM. */
-    if ( PNM_FORMAT_TYPE(format) == PBM_TYPE ) {
-        newformat = PGM_TYPE;
-        newmaxval = PGM_MAXMAXVAL;
-        pm_message( "promoting from PBM to PGM" );
-	} else {
-        newformat = format;
-        newmaxval = maxval;
-    }
-    compute_output_dimensions(cmdline, rows, cols, &newrows, &newcols);
-
-    /* We round the scale factor down so that we never fill up the 
-       output while (a fractional pixel of) input remains unused.  Instead,
-       we will run out of input while (a fractional pixel of) output is 
-       unfilled -- which is easier for our algorithm to handle.
-       */
-    xscale = (float) newcols / cols;
-    yscale = (float) newrows / rows;
-
-    if (cmdline.verbose) {
-        pm_message("Scaling by %f horizontally to %d columns.", 
-                   xscale, newcols );
-        pm_message("Scaling by %f vertically to %d rows.", 
-                   yscale, newrows);
-    }
-
-    if (xscale * cols < newcols - 1 ||
-        yscale * rows < newrows - 1) 
-        pm_error("Arithmetic precision of this program is inadequate to "
-                 "do the specified scaling.  Use a smaller input image "
-                 "or a slightly different scale factor.");
-
-    pnm_writepnminit(stdout, newcols, newrows, newmaxval, newformat, 0);
-
-    if (cmdline.nomix) 
-        scaleWithoutMixing(ifP, cols, rows, maxval, format,
-                           newcols, newrows, newmaxval, newformat, 
-                           xscale, yscale);
-    else
-        scaleWithMixing(ifP, cols, rows, maxval, format,
-                        newcols, newrows, newmaxval, newformat, 
-                        xscale, yscale, cmdline.verbose);
-
-    pm_close(ifP);
-    pm_close(stdout);
-    
-    exit(0);
-}
diff --git a/editor/pnmscalefixed.c b/editor/pnmscalefixed.c
index d562c670..8deb65d7 100644
--- a/editor/pnmscalefixed.c
+++ b/editor/pnmscalefixed.c
@@ -20,6 +20,8 @@
 */
  
 #include <math.h>
+
+#include "pm_c_util.h"
 #include "pnm.h"
 #include "shhopt.h"
 
diff --git a/editor/pnmshear.c b/editor/pnmshear.c
index ae104db9..359df299 100644
--- a/editor/pnmshear.c
+++ b/editor/pnmshear.c
@@ -12,9 +12,12 @@
 
 #define _XOPEN_SOURCE   /* get M_PI in math.h */
 
+#include <assert.h>
 #include <math.h>
 #include <string.h>
 
+#include "pm_c_util.h"
+#include "ppm.h"
 #include "pnm.h"
 #include "shhopt.h"
 
@@ -28,26 +31,34 @@ struct cmdline_info {
     const char *       input_filespec;  /* Filespec of input file */
     double       angle;           /* requested shear angle, in radians */
     unsigned int noantialias;     /* -noantialias option */
+    const char * background;      /* NULL if none */
 };
 
 
 
 static void
-parse_command_line(int argc, char ** argv,
-                   struct cmdline_info *cmdlineP) {
+parseCommandLine(int argc, char ** argv,
+                 struct cmdline_info *cmdlineP) {
 
     optStruct3 opt;
     unsigned int option_def_index = 0;
     optEntry *option_def = malloc(100*sizeof(optEntry));
 
+    unsigned int backgroundSpec;
+
     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;
 
     optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
-    
+
+    if (!backgroundSpec)
+        cmdlineP->background = NULL;
+
     if (argc-1 < 1)
         pm_error("Need an argument:  the shear angle.\n");
     else {
@@ -69,9 +80,14 @@ parse_command_line(int argc, char ** argv,
 }
 
 
+
 static void
-makeNewXel(xel * const outputXelP, xel const curXel, xel const prevXel,
-           double const fracnew0, double const omfracnew0, int const format) {
+makeNewXel(xel *  const outputXelP,
+           xel    const curXel,
+           xel    const prevXel,
+           double const fracnew0,
+           double const omfracnew0,
+           int    const format) {
 /*----------------------------------------------------------------------------
    Create an output xel as *outputXel, which is part curXel and part
    prevXel, the part given by the fractions omfracnew0 and fracnew0,
@@ -81,35 +97,40 @@ makeNewXel(xel * const outputXelP, xel const curXel, xel const prevXel,
    The format of the pixel is 'format'.
 -----------------------------------------------------------------------------*/
 
-    switch ( PNM_FORMAT_TYPE(format) ) {
+    switch (PNM_FORMAT_TYPE(format)) {
     case PPM_TYPE:
-        PPM_ASSIGN( *outputXelP,
-                    ( fracnew0 * PPM_GETR(prevXel) 
-                      + omfracnew0 * PPM_GETR(curXel) 
-                      + HALFSCALE ) / SCALE,
-                    ( fracnew0 * PPM_GETG(prevXel) 
-                      + omfracnew0 * PPM_GETG(curXel) 
-                      + HALFSCALE ) / SCALE,
-                    ( fracnew0 * PPM_GETB(prevXel) 
-                      + omfracnew0 * PPM_GETB(curXel) 
-                      + HALFSCALE ) / SCALE );
+        PPM_ASSIGN(*outputXelP,
+                   (fracnew0 * PPM_GETR(prevXel) 
+                    + omfracnew0 * PPM_GETR(curXel) 
+                    + HALFSCALE) / SCALE,
+                   (fracnew0 * PPM_GETG(prevXel) 
+                    + omfracnew0 * PPM_GETG(curXel) 
+                    + HALFSCALE) / SCALE,
+                   (fracnew0 * PPM_GETB(prevXel) 
+                    + omfracnew0 * PPM_GETB(curXel) 
+                    + HALFSCALE) / SCALE );
         break;
         
     default:
-        PNM_ASSIGN1( *outputXelP,
-                     ( fracnew0 * PNM_GET1(prevXel) 
-                       + omfracnew0 * PNM_GET1(curXel) 
-                       + HALFSCALE ) / SCALE );
+        PNM_ASSIGN1(*outputXelP,
+                    (fracnew0 * PNM_GET1(prevXel) 
+                     + omfracnew0 * PNM_GET1(curXel) 
+                     + HALFSCALE) / SCALE );
         break;
     }
 }
 
 
+
 static void
-shear_row(xel * const xelrow, int const cols, 
-          xel * const newxelrow, int const newcols, 
-          double const shearCols,
-          int const format, xel const bgxel, bool const antialias) {
+shearRow(xel *        const xelrow,
+         unsigned int const cols, 
+         xel *        const newxelrow,
+         unsigned int const newcols, 
+         double       const shearCols,
+         int          const format,
+         xel          const bgxel,
+         bool         const antialias) {
 /*----------------------------------------------------------------------------
    Shear the row 'xelrow' by 'shearCols' columns, and return the result as
    'newxelrow'.  They are 'cols' and 'newcols' columns wide, respectively.
@@ -122,47 +143,68 @@ shear_row(xel * const xelrow, int const cols,
    The format of the input xels (which implies something about the
    output xels too) is 'format'.
 -----------------------------------------------------------------------------*/
-    int const intShearCols = (int) shearCols;
+    unsigned int const intShearCols = (unsigned int) shearCols;
+
+    assert(shearCols >= 0.0);
         
-    if ( antialias ) {
-        const long fracnew0 = ( shearCols - intShearCols ) * SCALE;
+    if (antialias) {
+        const long fracnew0 = (shearCols - intShearCols) * SCALE;
         const long omfracnew0 = SCALE - fracnew0;
 
-        int col;
+        unsigned int col;
         xel prevXel;
             
-        for ( col = 0; col < newcols; ++col )
+        for (col = 0; col < newcols; ++col)
             newxelrow[col] = bgxel;
 
         prevXel = bgxel;
-        for ( col = 0; col < cols; ++col){
+        for (col = 0; col < cols; ++col) {
             makeNewXel(&newxelrow[intShearCols + col],
                        xelrow[col], prevXel, fracnew0, omfracnew0,
                        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);
     } else {
-        int col;
-        for ( col = 0; col < intShearCols; ++col )
+        unsigned int col;
+        for (col = 0; col < intShearCols; ++col)
             newxelrow[col] = bgxel;
-        for ( col = 0; col < cols; ++col )
+        for (col = 0; col < cols; ++col)
             newxelrow[intShearCols+col] = xelrow[col];
-        for ( col = intShearCols + cols; col < newcols; ++col )
+        for (col = intShearCols + cols; col < newcols; ++col)
             newxelrow[col] = bgxel;
     }
 }
 
 
+static xel
+backgroundColor(const char * const backgroundColorName,
+                xel *        const topRow,
+                int          const cols,
+                xelval       const maxval,
+                int          const format) {
+
+    xel retval;
+
+    if (backgroundColorName) {
+        retval = pnm_parsecolorxel(backgroundColorName, maxval, format);
+    } else 
+        retval = pnm_backgroundxelrow(topRow, cols, maxval, format);
+
+    return retval;
+}
+
+
 
 int
 main(int argc, char * argv[]) {
-    FILE* ifp;
-    xel* xelrow;
-    xel* newxelrow;
+
+    FILE * ifP;
+    xel * xelrow;
+    xel * newxelrow;
     xel bgxel;
     int rows, cols, format; 
     int newformat, newcols; 
@@ -172,57 +214,55 @@ main(int argc, char * argv[]) {
 
     struct cmdline_info cmdline;
 
-    pnm_init( &argc, argv );
+    pnm_init(&argc, argv);
 
-    parse_command_line( argc, argv, &cmdline );
+    parseCommandLine(argc, argv, &cmdline);
 
-    ifp = pm_openr( cmdline.input_filespec );
+    ifP = pm_openr(cmdline.input_filespec);
 
-    pnm_readpnminit( ifp, &cols, &rows, &maxval, &format );
-    xelrow = pnm_allocrow( cols );
+    pnm_readpnminit(ifP, &cols, &rows, &maxval, &format);
+    xelrow = pnm_allocrow(cols);
 
     /* Promote PBM files to PGM. */
-    if ( !cmdline.noantialias && PNM_FORMAT_TYPE(format) == PBM_TYPE ) {
+    if (!cmdline.noantialias && PNM_FORMAT_TYPE(format) == PBM_TYPE) {
         newformat = PGM_TYPE;
         newmaxval = PGM_MAXMAXVAL;
-        pm_message( "promoting from PBM to PGM - "
-                    "use -noantialias to avoid this" );
+        pm_message("promoting from PBM to PGM - "
+                   "use -noantialias to avoid this");
     } else {
         newformat = format;
         newmaxval = maxval;
     }
 
-    shearfac = tan( cmdline.angle );
-    if ( shearfac < 0.0 )
-        shearfac = -shearfac;
+    shearfac = fabs(tan(cmdline.angle));
 
     newcols = rows * shearfac + cols + 0.999999;
 
-    pnm_writepnminit( stdout, newcols, rows, newmaxval, newformat, 0 );
-    newxelrow = pnm_allocrow( newcols );
-
-    for ( row = 0; row < rows; ++row ) {
+    pnm_writepnminit(stdout, newcols, rows, newmaxval, newformat, 0);
+    newxelrow = pnm_allocrow(newcols);
+    
+    for (row = 0; row < rows; ++row) {
         double shearCols;
 
-        pnm_readpnmrow( ifp, xelrow, cols, newmaxval, format );
+        pnm_readpnmrow(ifP, xelrow, cols, newmaxval, format);
 
-        if ( row == 0 )
-            bgxel = pnm_backgroundxelrow( xelrow, cols, newmaxval, format );
+        if (row == 0)
+            bgxel = backgroundColor(cmdline.background,
+                                    xelrow, cols, newmaxval, format);
 
-        if ( cmdline.angle > 0.0 )
+        if (cmdline.angle > 0.0)
             shearCols = row * shearfac;
         else
-            shearCols = ( rows - row ) * shearfac;
+            shearCols = (rows - row) * shearfac;
 
-        shear_row(xelrow, cols, newxelrow, newcols, 
+        shearRow(xelrow, cols, newxelrow, newcols, 
                   shearCols, format, bgxel, !cmdline.noantialias);
 
-        pnm_writepnmrow( stdout, newxelrow, newcols, newmaxval, newformat, 0 );
+        pnm_writepnmrow(stdout, newxelrow, newcols, newmaxval, newformat, 0);
     }
+    
+    pm_close(ifP);
+    pm_close(stdout);
 
-    pm_close( ifp );
-    pm_close( stdout );
-
-    exit( 0 );
+    return 0;
 }
-
diff --git a/editor/pnmsmooth.c b/editor/pnmsmooth.c
index a18511c7..16d8ec33 100644
--- a/editor/pnmsmooth.c
+++ b/editor/pnmsmooth.c
@@ -23,12 +23,12 @@
 #include <unistd.h>
 #include <string.h>
 #include <errno.h>
-#include <sys/wait.h>
 
 #include "pm_c_util.h"
 #include "mallocvar.h"
 #include "shhopt.h"
 #include "nstring.h"
+#include "pm_system.h"
 #include "pnm.h"
 
 
@@ -36,7 +36,7 @@ struct cmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
-    const char *inputFilespec;  /* Filespec of input file */
+    const char * inputFilespec;  /* Filespec of input file */
     unsigned int width;
     unsigned int height;
     const char * dump;
@@ -176,38 +176,6 @@ writeConvolutionImage(FILE *       const cofp,
 
 
 
-static void
-runPnmconvol(const char * const inputFilespec,
-             const char * const convolutionImageFilespec) {
-
-    /* fork a Pnmconvol process */
-    pid_t rc;
-
-    rc = fork();
-    if (rc < 0)
-        pm_error("fork() failed.  errno=%d (%s)", errno, strerror(errno));
-    else if (rc == 0) {
-        /* child process executes following code */
-
-        execlp("pnmconvol",
-               "pnmconvol", convolutionImageFilespec, inputFilespec,
-               NULL);
-
-        pm_error("error executing pnmconvol command.  errno=%d (%s)",
-                 errno, strerror(errno));
-    } else {
-        /* This is the parent */
-        pid_t const childPid = rc;
-
-        int status;
-
-        /* wait for child to finish */
-        while (wait(&status) != childPid);
-    }
-}
-
-
-
 int
 main(int argc, char ** argv) {
 
@@ -232,7 +200,8 @@ main(int argc, char ** argv) {
     if (cmdline.dump) {
         /* We're done.  Convolution image is in user's file */
     } else {
-        runPnmconvol(cmdline.inputFilespec, tempfileName);
+        pm_system_lp("pnmconvol", NULL, NULL, NULL, NULL,
+                     "pnmconvol", tempfileName, cmdline.inputFilespec, NULL);
 
         unlink(tempfileName);
         strfree(tempfileName);
diff --git a/editor/pnmstitch.c b/editor/pnmstitch.c
index 61f02a04..45aee2f4 100644
--- a/editor/pnmstitch.c
+++ b/editor/pnmstitch.c
@@ -216,7 +216,7 @@ struct cmdlineInfo {
     unsigned int verbose;
 };
 
-static char minus[] = "-";
+
 
 static void
 parseCommandLine ( int argc, char ** argv,
@@ -293,10 +293,10 @@ parseCommandLine ( int argc, char ** argv,
         /* NOTREACHED */
     } else {
         if (argc-1 == 0) {
-            cmdlineP->leftFilespec = minus;
-            cmdlineP->rightFilespec = minus;
+            cmdlineP->leftFilespec = "-";
+            cmdlineP->rightFilespec = "-";
         } else if (argc-1 == 1) {
-            cmdlineP->leftFilespec = minus;
+            cmdlineP->leftFilespec = "-";
             cmdlineP->rightFilespec = argv[1];
         } else {
             cmdlineP->leftFilespec = argv[1];
@@ -311,7 +311,7 @@ parseCommandLine ( int argc, char ** argv,
         } else if (outputSpec) {
             cmdlineP->outputFilespec = outputOpt;
         } else {
-            cmdlineP->outputFilespec = minus;
+            cmdlineP->outputFilespec = "-";
         }
     }
 } /* parseCommandLine() - end */
@@ -439,7 +439,7 @@ readinit(const char * const name)
     else {
         FILE * ifP;
 
-        if (strcmp(name,minus) == 0) {
+        if (streq(name, "-")) {
             ifP = stdin;
             image->name = strdup("<stdin>");
         } else {
@@ -468,7 +468,7 @@ readinit(const char * const name)
 static bool
 writeinit(Image * image)
 {
-    if (strcmp(image->name,minus) == 0) {
+    if (streq(image->name, "-")) {
         image->pam.file = stdout;
         strfree(image->name);
         image->name = strdup("<stdout>");
diff --git a/editor/pnmtile.c b/editor/pnmtile.c
index 96bf658d..21512b36 100644
--- a/editor/pnmtile.c
+++ b/editor/pnmtile.c
@@ -10,54 +10,105 @@
 ** implied warranty.
 */
 
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "shhopt.h"
 #include "pnm.h"
 
-int
-main( argc, argv )
-    int argc;
-    char* argv[];
-    {
-    FILE* ifp;
-    xel** xels;
-    register xel* xelrow;
-    xelval maxval;
-    int rows, cols, format, width, height, row, col;
-    const char* const usage = "width height [pnmfile]";
 
-    pnm_init( &argc, argv );
 
-    if ( argc < 3 || argc > 4 )
-	pm_usage( usage );
+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 height;
+};
+
+
+
+static void
+parseCommandLine(int argc, const char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
 
-    if ( sscanf( argv[1], "%d", &width ) != 1 )
-	pm_usage( usage );
-    if ( sscanf( argv[2], "%d", &height ) != 1 )
-	pm_usage( usage );
+    MALLOCARRAY_NOFAIL(option_def, 100);
 
-    if ( width < 1 )
-	pm_error( "width is less than 1" );
-    if ( height < 1 )
-	pm_error( "height is less than 1" );
+    option_def_index = 0;   /* incremented by OPTENT3 */
 
-    if ( argc == 4 )
-	ifp = pm_openr( argv[3] );
-    else
-	ifp = stdin;
+    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 */
 
-    xels = pnm_readpnm( ifp, &cols, &rows, &maxval, &format );
-    pm_close( ifp );
+    OPTENTINIT;
 
-    xelrow = pnm_allocrow( width );
+    optParseOptions3(&argc, (char**)argv, opt, sizeof opt, 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
-    pnm_writepnminit( stdout, width, height, maxval, format, 0 );
-    for ( row = 0; row < height; ++row )
-	{
-	for ( col = 0; col < width; ++col )
-	    xelrow[col] = xels[row % rows][col % cols];
-	pnm_writepnmrow( stdout, xelrow, width, maxval, format, 0 );
-	}
+    if (argc-1 < 2)
+        pm_error("You must specify at least two parameters: "
+                 "width and height.  You specified %u",
+                 argc-1);
+    else {
+        cmdlineP->width  = pm_parse_width(argv[1]);
+        cmdlineP->height = pm_parse_height(argv[2]);
 
-    pm_close( stdout );
+        if (argc-1 > 2) {
+            cmdlineP->inputFileName = argv[3];
 
-    exit( 0 );
+            if (argc-1 > 3)
+                pm_error("There are at most three arguments: "
+                         "width, height, file name.  You specified %u",
+                         argc-1);
+        } else 
+            cmdlineP->inputFileName = "-";
     }
+}
+
+
+
+int
+main(int argc, const char ** argv) {
+
+    struct cmdlineInfo cmdline;
+    FILE * ifP;
+    xel ** xels;
+    xel * xelrow;
+    xelval maxval;
+    int rows, cols;
+    int format;
+    unsigned int row;
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    xels = pnm_readpnm(ifP, &cols, &rows, &maxval, &format);
+    pm_close(ifP);
+
+    xelrow = pnm_allocrow(cmdline.width);
+
+    pnm_writepnminit(stdout, cmdline.width, cmdline.height, maxval, format, 0);
+    for (row = 0; row < cmdline.height; ++row) {
+        unsigned int col;
+        for (col = 0; col < cmdline.width; ++col)
+            xelrow[col] = xels[row % rows][col % cols];
+        pnm_writepnmrow(stdout, xelrow, cmdline.width, maxval, format, 0);
+    }
+
+    pm_close(stdout);
+
+    return 0;
+}
diff --git a/editor/ppm3d.c b/editor/ppm3d.c
deleted file mode 100644
index c37ceeb1..00000000
--- a/editor/ppm3d.c
+++ /dev/null
@@ -1,138 +0,0 @@
-/* ppmto3d.c - convert a portable pixmap to a portable graymap
-**
-** Copyright (C) 1989 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 "ppm.h"
-#include "lum.h"
-
-static void
-computeGrayscaleRow(const pixel * const inputRow,
-                    gray *        const outputRow,
-                    pixval        const maxval,
-                    unsigned int  const cols) {
-
-    if (maxval <= 255) {
-        unsigned int col;
-        /* Use fast approximation to 0.299 r + 0.587 g + 0.114 b. */
-        for (col = 0; col < cols; ++col)
-            outputRow[col] = ppm_fastlumin(inputRow[col]);
-    } else {
-        unsigned int col;
-        /* Can't use fast approximation, so fall back on floats. */
-        for (col = 0; col < cols; ++col)
-            outputRow[col] = PPM_LUMIN(inputRow[col]) + 0.5;
-    }
-}
-
-
-
-int
-main (int argc, char *argv[]) {
-
-    int offset; 
-    int cols, rows, row;
-    pixel* pixelrow;
-    pixval maxval;
-
-    FILE* Lifp;
-    pixel* Lpixelrow;
-    gray* Lgrayrow;
-    int Lrows, Lcols, Lformat;
-    pixval Lmaxval;
-   
-    FILE* Rifp;
-    pixel* Rpixelrow;
-    gray* Rgrayrow;
-    int Rrows, Rcols, Rformat;
-    pixval Rmaxval;
-   
-    ppm_init (&argc, argv);
-
-    if (argc-1 > 3 || argc-1 < 2) 
-        pm_error("Wrong number of arguments (%d).  Arguments are "
-                 "leftppmfile rightppmfile [horizontal_offset]", argc-1);
-
-    Lifp = pm_openr (argv[1]);
-    Rifp = pm_openr (argv[2]);
-
-    if (argc-1 >= 3) 
-        offset = atoi (argv[3]);
-    else
-        offset = 30;
-
-    ppm_readppminit (Lifp, &Lcols, &Lrows, &Lmaxval, &Lformat);
-    ppm_readppminit (Rifp, &Rcols, &Rrows, &Rmaxval, &Rformat);
-    
-    if ((Lcols != Rcols) || (Lrows != Rrows) || 
-        (Lmaxval != Rmaxval) || 
-        (PPM_FORMAT_TYPE(Lformat) != PPM_FORMAT_TYPE(Rformat)))
-        pm_error ("Pictures are not of same size and format");
-    
-    cols = Lcols;
-    rows = Lrows;
-    maxval = Lmaxval;
-   
-    ppm_writeppminit (stdout, cols, rows, maxval, 0);
-    Lpixelrow = ppm_allocrow (cols);
-    Lgrayrow = pgm_allocrow (cols);
-    Rpixelrow = ppm_allocrow (cols);
-    Rgrayrow = pgm_allocrow (cols);
-    pixelrow = ppm_allocrow (cols);
-
-    for (row = 0; row < rows; ++row) {
-        ppm_readppmrow(Lifp, Lpixelrow, cols, maxval, Lformat);
-        ppm_readppmrow(Rifp, Rpixelrow, cols, maxval, Rformat);
-
-        computeGrayscaleRow(Lpixelrow, Lgrayrow, maxval, cols);
-        computeGrayscaleRow(Rpixelrow, Rgrayrow, maxval, cols);
-        {
-            int col;
-            gray* LgP;
-            gray* RgP;
-            pixel* pP;
-            for (col = 0, pP = pixelrow, LgP = Lgrayrow, RgP = Rgrayrow;
-                 col < cols + offset;
-                 ++col) {
-            
-                if (col < offset/2)
-                    ++LgP;
-                else if (col >= offset/2 && col < offset) {
-                    const pixval Blue = (pixval) (float) *LgP;
-                    const pixval Red = (pixval) 0;
-                    PPM_ASSIGN (*pP, Red, Blue, Blue);
-                    ++LgP;
-                    ++pP;
-                } else if (col >= offset && col < cols) {
-                    const pixval Red = (pixval) (float) *RgP;
-                    const pixval Blue = (pixval) (float) *LgP;
-                    PPM_ASSIGN (*pP, Red, Blue, Blue);
-                    ++LgP;
-                    ++RgP;
-                    ++pP;
-                } else if (col >= cols && col < cols + offset/2) {
-                    const pixval Blue = (pixval) 0;
-                    const pixval Red = (pixval) (float) *RgP;
-                    PPM_ASSIGN (*pP, Red, Blue, Blue);
-                    ++RgP;
-                    ++pP;
-                } else
-                    ++RgP;
-            }
-        }    
-        ppm_writeppmrow(stdout, pixelrow, cols, maxval, 0);
-    }
-
-    pm_close(Lifp);
-    pm_close(Rifp);
-    pm_close(stdout);
-
-    return 0;
-}
diff --git a/editor/ppmbrighten.c b/editor/ppmbrighten.c
index 93649082..9bcf7bb5 100644
--- a/editor/ppmbrighten.c
+++ b/editor/ppmbrighten.c
@@ -11,6 +11,7 @@
 ** implied warranty.
 */
 
+#include "pm_c_util.h"
 #include "ppm.h"
 #include "shhopt.h"
 #include "mallocvar.h"
@@ -21,7 +22,7 @@ struct cmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
-    const char *inputFilespec;  /* '-' if stdin */
+    const char * inputFilespec;  /* '-' if stdin */
     float saturation;
     float value;
     unsigned int normalize;
@@ -29,10 +30,9 @@ struct cmdlineInfo {
 
 
 
-
 static void
-parseCommandLine (int argc, char ** argv,
-                  struct cmdlineInfo *cmdlineP) {
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    parse program command line described in Unix standard form by argc
    and argv.  Return the information in the options as *cmdlineP.  
@@ -270,9 +270,9 @@ int
 main(int argc, char * argv[]) {
 
     struct cmdlineInfo cmdline;
-    FILE *ifP;
+    FILE * ifP;
     pixval minValue, maxValue;
-    pixel *pixelrow;
+    pixel * pixelrow;
     pixval maxval;
     int rows, cols, format, row;
 
diff --git a/editor/ppmchange.c b/editor/ppmchange.c
index 6d3839bd..f0d2cc38 100644
--- a/editor/ppmchange.c
+++ b/editor/ppmchange.c
@@ -13,6 +13,7 @@
 **     28 Jan 94 -  Added multiple color substitution function.
 */
 
+#include "pm_c_util.h"
 #include "ppm.h"
 #include "shhopt.h"
 #include "mallocvar.h"
@@ -144,22 +145,22 @@ changeRow(const pixel * const inrow,
    The input row is 'inrow'.  The output is returned as 'outrow', in
    storage which must be already allocated.  Both are 'cols' columns wide.
 -----------------------------------------------------------------------------*/
-    int col;
+    unsigned int col;
 
     for (col = 0; col < cols; ++col) {
-        int i;
-        int have_match; /* logical: It's a color user said to change */
+        unsigned int i;
+        bool haveMatch; /* logical: It's a color user said to change */
         pixel newcolor;  
         /* Color to which we must change current pixel.  Undefined unless
-           'have_match' is true.
+           'haveMatch' is true.
         */
 
-        have_match = FALSE;  /* haven't found a match yet */
-        for (i = 0; i < ncolors && !have_match; ++i) {
-            have_match = colormatch(inrow[col], colorfrom[i], closeness);
+        haveMatch = FALSE;  /* haven't found a match yet */
+        for (i = 0; i < ncolors && !haveMatch; ++i) {
+            haveMatch = colormatch(inrow[col], colorfrom[i], closeness);
             newcolor = colorto[i];
         }
-        if (have_match)
+        if (haveMatch)
             outrow[col] = newcolor;
         else if (remainder_specified)
             outrow[col] = remainder_color;
diff --git a/editor/ppmcolormask.c b/editor/ppmcolormask.c
index 2829e634..0d7a214c 100644
--- a/editor/ppmcolormask.c
+++ b/editor/ppmcolormask.c
@@ -61,7 +61,7 @@ parseColorOpt(const char *         const colorOpt,
         const char * token;
         token = strsepN(&cursor, ",");
         if (token) {
-            if (STRNEQ(token, "bk:", 3)) {
+            if (strneq(token, "bk:", 3)) {
                 cmdlineP->maskColor[colorCount].matchType = MATCH_BK;
                 cmdlineP->maskColor[colorCount].u.bkColor =
                     ppm_bk_color_from_name(&token[3]);
diff --git a/editor/ppmdraw.c b/editor/ppmdraw.c
index 0dd03bc9..3bd271a6 100644
--- a/editor/ppmdraw.c
+++ b/editor/ppmdraw.c
@@ -515,7 +515,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
         if (drawCommandP == NULL)
             pm_error("Out of memory to parse '%s' command", verb);
 
-        if (STREQ(verb, "setpos")) {
+        if (streq(verb, "setpos")) {
             drawCommandP->verb = VERB_SETPOS;
             if (commandTokens.count < 3)
                 pm_error("Not enough tokens for a 'setpos' command.  "
@@ -524,22 +524,22 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                 drawCommandP->u.setposArg.x = atoi(commandTokens.token[1]);
                 drawCommandP->u.setposArg.y = atoi(commandTokens.token[2]);
             }
-        } else if (STREQ(verb, "setlinetype")) {
+        } else if (streq(verb, "setlinetype")) {
             drawCommandP->verb = VERB_SETLINETYPE;
             if (commandTokens.count < 2)
                 pm_error("Not enough tokens for a 'setlinetype' command.  "
                          "Need %u.  Got %u", 2, commandTokens.count);
             else {
                 const char * const typeArg = commandTokens.token[1];
-                if (STREQ(typeArg, "normal"))
+                if (streq(typeArg, "normal"))
                     drawCommandP->u.setlinetypeArg.type = PPMD_LINETYPE_NORMAL;
-                else if (STREQ(typeArg, "normal"))
+                else if (streq(typeArg, "normal"))
                     drawCommandP->u.setlinetypeArg.type = 
                         PPMD_LINETYPE_NODIAGS;
                 else
                     pm_error("Invalid type");
             }
-        } else if (STREQ(verb, "setlineclip")) {
+        } else if (streq(verb, "setlineclip")) {
             drawCommandP->verb = VERB_SETLINECLIP;
             if (commandTokens.count < 2)
                 pm_error("Not enough tokens for a 'setlineclip' command.  "
@@ -547,7 +547,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
             else
                 drawCommandP->u.setlineclipArg.clip =
                     atoi(commandTokens.token[1]);
-        } else if (STREQ(verb, "setcolor")) {
+        } else if (streq(verb, "setcolor")) {
             drawCommandP->verb = VERB_SETCOLOR;
             if (commandTokens.count < 2)
                 pm_error("Not enough tokens for a 'setcolor' command.  "
@@ -555,7 +555,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
             else
                 drawCommandP->u.setcolorArg.colorName =
                     strdup(commandTokens.token[1]);
-        } else if (STREQ(verb, "setfont")) {
+        } else if (streq(verb, "setfont")) {
             drawCommandP->verb = VERB_SETFONT;
             if (commandTokens.count < 2)
                 pm_error("Not enough tokens for a 'setfont' command.  "
@@ -563,7 +563,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
             else
                 drawCommandP->u.setfontArg.fontFileName =
                     strdup(commandTokens.token[1]);
-        } else if (STREQ(verb, "line")) {
+        } else if (streq(verb, "line")) {
             drawCommandP->verb = VERB_LINE;
             if (commandTokens.count < 5)
                 pm_error("Not enough tokens for a 'line' command.  "
@@ -574,7 +574,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                 drawCommandP->u.lineArg.x1 = atoi(commandTokens.token[3]);
                 drawCommandP->u.lineArg.y1 = atoi(commandTokens.token[4]);
             } 
-        } else if (STREQ(verb, "line_here")) {
+        } else if (streq(verb, "line_here")) {
             drawCommandP->verb = VERB_LINE_HERE;
             if (commandTokens.count < 3)
                 pm_error("Not enough tokens for a 'line_here' command.  "
@@ -585,7 +585,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                 argP->right = atoi(commandTokens.token[1]);
                 argP->down = atoi(commandTokens.token[2]);
             } 
-       } else if (STREQ(verb, "spline3")) {
+       } else if (streq(verb, "spline3")) {
             drawCommandP->verb = VERB_SPLINE3;
             if (commandTokens.count < 7)
                 pm_error("Not enough tokens for a 'spline3' command.  "
@@ -600,7 +600,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                 argP->x2 = atoi(commandTokens.token[5]);
                 argP->y2 = atoi(commandTokens.token[6]);
             } 
-        } else if (STREQ(verb, "circle")) {
+        } else if (streq(verb, "circle")) {
             drawCommandP->verb = VERB_CIRCLE;
             if (commandTokens.count < 4)
                 pm_error("Not enough tokens for a 'circle' command.  "
@@ -611,7 +611,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                 argP->cy     = atoi(commandTokens.token[2]);
                 argP->radius = atoi(commandTokens.token[3]);
             } 
-        } else if (STREQ(verb, "filledrectangle")) {
+        } else if (streq(verb, "filledrectangle")) {
             drawCommandP->verb = VERB_FILLEDRECTANGLE;
             if (commandTokens.count < 5)
                 pm_error("Not enough tokens for a 'filledrectangle' command.  "
@@ -624,7 +624,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                 argP->width  = atoi(commandTokens.token[3]);
                 argP->height = atoi(commandTokens.token[4]);
             } 
-        } else if (STREQ(verb, "text")) {
+        } else if (streq(verb, "text")) {
             drawCommandP->verb = VERB_TEXT;
             if (commandTokens.count < 6)
                 pm_error("Not enough tokens for a 'text' command.  "
@@ -638,7 +638,7 @@ parseDrawCommand(struct tokenSet             const commandTokens,
                 if (drawCommandP->u.textArg.text == NULL)
                     pm_error("Out of storage parsing 'text' command");
             }
-        } else if (STREQ(verb, "text_here")) {
+        } else if (streq(verb, "text_here")) {
             drawCommandP->verb = VERB_TEXT_HERE;
             if (commandTokens.count < 4)
                 pm_error("Not enough tokens for a 'text_here' command.  "
@@ -705,7 +705,7 @@ processToken(const char *      const scriptText,
     memcpy(token, &scriptText[tokenStart], tokenLength);
     token[tokenLength] = '\0';
     
-    if (STREQ(token, ";")) {
+    if (streq(token, ";")) {
         disposeOfCommandTokens(tokenSetP, scriptP);
         free(token);
     } else {
diff --git a/editor/ppmfade b/editor/ppmfade
index 9bd122e9..fbc62968 100755
--- a/editor/ppmfade
+++ b/editor/ppmfade
@@ -32,14 +32,7 @@ my $last_file = "undefined";
 my $base_name = "fade";		# default base name of output files
 my $image = "ppm";		# default output storage format
 my $mode = $SPREAD;		# default fading mode
-#
-#  Check those command line args.
-#
-sub usage();
 
-if (@ARGV == 0) {
-    usage();
-}
 
 my $n;  # argument number
 
@@ -49,7 +42,7 @@ for ($n = 0; $n < @ARGV; $n++) {
         $first_file = $ARGV[$n];
         if (-e $first_file) {
         } else {
-            print "I can't find $first_file\n";
+            print "I can't find first file '$first_file'\n";
             exit 20;
         }
     } elsif ($ARGV[$n] eq "-l") {
@@ -57,7 +50,7 @@ for ($n = 0; $n < @ARGV; $n++) {
         $last_file = $ARGV[$n];
         if (-e $last_file) {
         } else {
-            print "I can't find $last_file\n";
+            print "I can't find last file '$last_file'\n";
             exit 20;
         }
     } elsif ($ARGV[$n] eq "-base") {
@@ -298,14 +291,3 @@ for ($i = 1; $i <= $nframes; $i++) {
 system("rm junk*$$.ppm");
 
 exit(0);
-
-
-
-sub usage() {
-   print "Usage: ppmfade [-f first_file] [-l last_file]\n";
-   print "               [-spread|-relief|-oil|-edge|-bentley|-block]\n";
-   print "               [-base basename]\n";
-   print "Notes: Default base: fade\n";
-   print "       The resulting image files will be named fade.NNNN.ppm.\n";
-   exit(100);
-}
diff --git a/editor/ppmquantall b/editor/ppmquantall
index bc314d4d..4807de8c 100755
--- a/editor/ppmquantall
+++ b/editor/ppmquantall
@@ -71,9 +71,8 @@ for i in ${files[@]}; do
 done
 
 tempdir="${TMPDIR-/tmp}/ppmquantall.$$"
-mkdir $tempdir || { echo "Could not create temporary file. Exiting."; exit 1;}
-chmod 700 $tempdir
-
+mkdir -m 0700 $tempdir || \
+  { echo "Could not create temporary file. Exiting."; exit 1;}
 trap 'rm -rf $tempdir' 0 1 3 15
 
 all=$tempdir/pqa.all.$$
diff --git a/editor/ppmshift.c b/editor/ppmshift.c
deleted file mode 100644
index 1f8a599b..00000000
--- a/editor/ppmshift.c
+++ /dev/null
@@ -1,137 +0,0 @@
-
-/*********************************************************************/
-/* ppmshift -  shift lines of a picture left or right by x pixels    */
-/* Frank Neumann, October 1993                                       */
-/* V1.1 16.11.1993                                                   */
-/*                                                                   */
-/* version history:                                                  */
-/* V1.0    11.10.1993  first version                                 */
-/* V1.1    16.11.1993  Rewritten to be NetPBM.programming conforming */
-/*********************************************************************/
-
-#include "ppm.h"
-
-/* global variables */
-#ifdef AMIGA
-static char *version = "$VER: ppmshift 1.1 (16.11.93)"; /* Amiga version identification */
-#endif
-
-/**************************/
-/* start of main function */
-/**************************/
-int main(argc, argv)
-int argc;
-char *argv[];
-{
-	FILE* ifp;
-	time_t timenow;
-	int argn, rows, cols, format, i = 0, j = 0;
-	pixel *srcrow, *destrow;
-	pixel *pP = NULL, *pP2 = NULL;
-	pixval maxval;
-	int shift, nowshift;
-	const char * const usage = "shift [ppmfile]\n        shift: maximum number of pixels to shift a line by\n";
-
-	/* 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", &shift) != 1)
-		pm_usage(usage);
-	if (shift < 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;
-
-	if (argn != argc)
-		pm_usage(usage);
-
-	/* read first data from file */
-	ppm_readppminit(ifp, &cols, &rows, &maxval, &format);
-
-	if (shift > cols)
-    {
-		shift = cols;
-        pm_message("shift amount is larger than picture width - reset to %d", shift);
-    }
-
-	/* no error checking required here, ppmlib does it all for us */
-	srcrow = ppm_allocrow(cols);
-
-	/* allocate a row of pixel data for the new pixels */
-	destrow = ppm_allocrow(cols);
-
-	ppm_writeppminit(stdout, cols, rows, maxval, 0);
-
-	/* get time of day to feed the random number generator */
-	timenow = time(NULL);
-	srand(timenow);
-
-	/** 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 (i = 0; i < rows; i++)
-	{
-		if (shift != 0)
-			nowshift = (rand() % (shift+1)) - ((shift+1) / 2);
-		else
-			nowshift = 0;
-
-		ppm_readppmrow(ifp, srcrow, cols, maxval, format);
-
-		pP = srcrow;
-		pP2 = destrow;
-
-		/* 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)
-		{
-			pP+= abs(nowshift);
-			for (j = 0; j < cols; j++)
-			{
-				PPM_ASSIGN(*pP2, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
-				pP2++;
-                if (j < (cols+nowshift)-1)
-					pP++;
-			}
-		}
-		/* if the shift value is 0 or positive, the first <nowshift> pixels of the */
-		/* destination line are filled with the first pixel from the source line,  */
-		/* and the rest of the source line is copied to the dest line              */
-		else
-		{
-			for (j = 0; j < cols; j++)
-			{
-				PPM_ASSIGN(*pP2, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
-				pP2++;
-                if (j >= nowshift)
-					pP++;
-			}
-		}
-
-		/* write out one line of graphic data */
-		ppm_writeppmrow(stdout, destrow, cols, maxval, 0);
-	}
-
-	pm_close(ifp);
-	ppm_freerow(srcrow);
-	ppm_freerow(destrow);
-
-	exit(0);
-}
-
diff --git a/editor/ppmspread.c b/editor/ppmspread.c
deleted file mode 100644
index 569d1266..00000000
--- a/editor/ppmspread.c
+++ /dev/null
@@ -1,127 +0,0 @@
-/*********************************************************************/
-/* ppmspread -  randomly displace a PPM's pixels by a certain amount */
-/* Frank Neumann, October 1993                                       */
-/* V1.1 16.11.1993                                                   */
-/*                                                                   */
-/* version history:                                                  */
-/* V1.0 12.10.1993    first version                                  */
-/* V1.1 16.11.1993    Rewritten to be NetPBM.programming conforming  */
-/*********************************************************************/
-
-#include <string.h>
-#include "ppm.h"
-
-/* global variables */
-#ifdef AMIGA
-static char *version = "$VER: ppmspread 1.1 (16.11.93)"; /* Amiga version identification */
-#endif
-
-/**************************/
-/* start of main function */
-/**************************/
-int main(argc, argv)
-int argc;
-char *argv[];
-{
-	FILE* ifp;
-	int argn, rows, cols, i, j;
-	int xdis, ydis, xnew, ynew;
-	pixel **destarray, **srcarray;
-	pixel *pP, *pP2;
-	pixval maxval;
-	pixval r1, g1, b1;
-	int amount;
-	time_t timenow;
-	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;
-
-	if (argn != argc)
-		pm_usage(usage);
-
-	/* read entire picture into buffer */
-	srcarray = ppm_readppm(ifp, &cols, &rows, &maxval);
-
-	/* allocate an entire picture buffer for dest picture */
-	destarray = ppm_allocarray(cols, rows);
-
-	/* clear out the buffer */
-	for (i=0; i < rows; i++)
-		memset(destarray[i], 0, cols * sizeof(pixel));
-
-	/* set seed for random number generator */
-	/* get time of day to feed the random number generator */
-	timenow = time(NULL);
-	srand(timenow);
-
-	/* start displacing pixels */
-	for (i = 0; i < rows; i++)
-	{
-		pP = srcarray[i];
-
-		for (j = 0; j < cols; j++)
-		{
-			xdis = (rand() % (amount+1)) - ((amount+1) / 2);
-			ydis = (rand() % (amount+1)) - ((amount+1) / 2);
-
-			xnew = j + xdis;
-			ynew = i + 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[i] + j;
-				PPM_ASSIGN(*pP2, r1, g1, b1);
-			}
-			else
-			{
-                /* displaced pixel is out of bounds; leave the old pixel there */
-                pP2 = destarray[i] + j;
-				PPM_ASSIGN(*pP2, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
-			}
-			pP++;
-		}
-	}
-
-	/* write out entire dest picture in one go */
-	ppm_writeppm(stdout, destarray, cols, rows, maxval, 0);
-
-	pm_close(ifp);
-	ppm_freearray(srcarray, rows);
-	ppm_freearray(destarray, rows);
-
-	exit(0);
-}
-
diff --git a/editor/specialty/Makefile b/editor/specialty/Makefile
new file mode 100644
index 00000000..76befbb4
--- /dev/null
+++ b/editor/specialty/Makefile
@@ -0,0 +1,55 @@
+ifeq ($(SRCDIR)x,x)
+  SRCDIR = $(CURDIR)/../..
+  BUILDDIR = $(SRCDIR)
+endif
+SUBDIR = editor/specialty
+VPATH=.:$(SRCDIR)/$(SUBDIR)
+
+include $(BUILDDIR)/config.mk
+
+PORTBINARIES = pamdeinterlace \
+	       pammixinterlace \
+	       pamoil \
+	       pampop9 \
+	       pbmlife \
+	       pgmabel \
+	       pgmbentley \
+	       pgmmorphconv \
+	       pnmindex \
+	       ppm3d \
+	       ppmglobe \
+	       ppmntsc \
+	       ppmrelief \
+	       ppmshift \
+	       ppmspread \
+	       ppmtv \
+
+# We don't include programs that have special library dependencies in the
+# merge scheme, because we don't want those dependencies to prevent us
+# from building all the other programs.
+
+NOMERGEBINARIES = 
+MERGEBINARIES = $(PORTBINARIES)
+
+
+BINARIES = $(MERGEBINARIES) $(NOMERGEBINARIES)
+SCRIPTS =
+
+OBJECTS = $(BINARIES:%=%.o)
+
+MERGE_OBJECTS = $(MERGEBINARIES:%=%.o2)
+
+.PHONY: all
+all: $(BINARIES)
+
+include $(SRCDIR)/common.mk
+
+install.bin: install.bin.local
+
+.PHONY: install.bin.local
+install.bin.local: $(PKGDIR)/bin
+# Remember that $(SYMLINK) might just be a copy command.
+# pamoil replaced pgmoil in June 2001.
+	cd $(PKGDIR)/bin ; \
+	rm -f pgmoil$(EXE) ; \
+	$(SYMLINK) pamoil$(EXE) pgmoil$(EXE)
diff --git a/editor/pamdeinterlace.c b/editor/specialty/pamdeinterlace.c
index db893708..7c6b123c 100644
--- a/editor/pamdeinterlace.c
+++ b/editor/specialty/pamdeinterlace.c
@@ -8,6 +8,7 @@
   Contributed to the public domain.
 ******************************************************************************/
 
+#include "pm_c_util.h"
 #include "pam.h"
 #include "shhopt.h"
 #include "mallocvar.h"
diff --git a/editor/specialty/pammixinterlace.c b/editor/specialty/pammixinterlace.c
new file mode 100644
index 00000000..9f98b406
--- /dev/null
+++ b/editor/specialty/pammixinterlace.c
@@ -0,0 +1,345 @@
+/******************************************************************************
+                             pammixinterlace
+*******************************************************************************
+  De-interlace an image by merging adjacent rows.
+   
+  Copyright (C) 2007 Bruce Guenter, FutureQuest, Inc.
+
+  Permission to use, copy, modify, and distribute this software and its
+  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.
+
+******************************************************************************/
+
+#define _BSD_SOURCE    /* Make sure strcaseeq() is in nstring.h */
+
+#include <string.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "shhopt.h"
+#include "pam.h"
+
+
+
+static sample
+clamp(sample const val,
+      sample const maxval) {
+
+    return val < 0 ? 0 : val > maxval ? maxval : val;
+}
+
+
+
+static bool
+distant(long const above,
+        long const mid,
+        long const below) {
+
+    return abs(mid - (above + below) / 2) > abs(above - below);
+}
+
+
+
+static void
+filterLinearBlend(tuple *      const outputrow,
+                  tuple **     const tuplerowWindow,
+                  unsigned int const width,
+                  unsigned int const depth,
+                  bool         const adaptive,
+                  sample       const maxval) {
+
+    unsigned int col;
+
+    for (col = 0; col < width; ++col) {
+        unsigned int plane;
+
+        for (plane = 0; plane < depth; ++plane) {
+            long const above = tuplerowWindow[0][col][plane];
+            long const mid   = tuplerowWindow[1][col][plane];
+            long const below = tuplerowWindow[2][col][plane];
+
+            sample out;
+
+            if (!adaptive || distant(above, mid, below))
+                out = (above + mid * 2 + below) / 4;
+            else
+                out = mid;
+            
+            outputrow[col][plane] = out;
+        }
+    }
+}
+
+
+
+static void
+filterFfmpeg(tuple *      const outputrow,
+             tuple **     const tuplerowWindow,
+             unsigned int const width,
+             unsigned int const depth,
+             bool         const adaptive,
+             sample       const maxval) {
+
+    unsigned int col;
+    
+    for (col = 0; col < width; ++col) {
+        unsigned int plane;
+        
+        for (plane = 0; plane < depth; ++plane) {
+            long const above = tuplerowWindow[1][col][plane];
+            long const mid   = tuplerowWindow[2][col][plane];
+            long const below = tuplerowWindow[3][col][plane];
+
+            sample out;
+            
+            if (!adaptive || distant(above, mid, below)) {
+                long const a = (- (long)tuplerowWindow[0][col][plane]
+                                + above * 4
+                                + mid * 2
+                                + below * 4
+                                - (long)tuplerowWindow[4][col][plane]) / 8;
+                out = clamp(a, maxval);
+            } else
+                out = mid;
+
+            outputrow[col][plane] = out;
+        }
+    }
+}
+
+
+
+static void
+filterFIR(tuple *      const outputrow,
+          tuple **     const tuplerowWindow,
+          unsigned int const width,
+          unsigned int const depth,
+          bool         const adaptive,
+          sample       const maxval) {
+
+    unsigned int col;
+
+    for (col = 0; col < width; ++col) {
+        unsigned int plane;
+
+        for (plane = 0; plane < depth; ++plane) {
+
+            long const above = tuplerowWindow[1][col][plane];
+            long const mid   = tuplerowWindow[2][col][plane];
+            long const below = tuplerowWindow[3][col][plane];
+
+            sample out;
+
+            if (!adaptive || distant(above, mid, below)) {
+                long const a = (- (long)tuplerowWindow[0][col][plane]
+                                + above * 2
+                                + mid * 6
+                                + below * 2
+                                - (long)tuplerowWindow[4][col][plane]) / 8;
+                out = clamp(a, maxval);
+            } else
+                out = mid;
+            
+            outputrow[col][plane] = out;
+        }
+    }
+}
+
+
+
+struct filter {
+    const char * name;          /* The command-line name of the filter */
+    unsigned int rows;   /* The number of rows the filter operates on */
+    void (*filter)(tuple *, tuple **,
+                   unsigned int width, unsigned int depth,
+                   bool adaptive, sample maxval);
+};
+
+static struct filter filters[] = {
+    { "fir", 5, filterFIR }, /* FIR is cleanest and default filter */
+    { "ffmpeg", 5, filterFfmpeg },
+    { "linear", 3, filterLinearBlend },
+};
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;  /* Names of input files */
+    struct filter * filterP;
+    unsigned int adaptive;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optStruct3 opt;  /* set by OPTENT3 */
+    optEntry * option_def;
+    unsigned int option_def_index;
+    const char * filterName;
+    unsigned int filterSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "filter",   OPT_STRING, &filterName, &filterSpec, 0);
+    OPTENT3(0, "adaptive", OPT_FLAG, NULL, &cmdlineP->adaptive, 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 */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+    /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (!filterSpec)
+        cmdlineP->filterP = &filters[0];
+    else {
+        unsigned int i;
+        cmdlineP->filterP = NULL;
+        for (i = 0; i < sizeof filters / sizeof(struct filter); ++i) {
+            if (strcaseeq(filterName, filters[i].name)) {
+                cmdlineP->filterP = &filters[i];
+                break;
+            }
+        }
+        if (!cmdlineP->filterP)
+            pm_error("The filter name '%s' is not known.", filterName);
+    }
+    
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFileName = argv[1];
+    else
+        pm_error("You specified too many arguments (%d).  The only "
+                 "argument is the optional input file specification.",
+                 argc-1);
+}
+
+
+
+static void
+allocateRowWindowBuffer(struct pam * const pamP,
+                        tuple **     const tuplerowWindow,
+                        unsigned int const rows) {
+
+    unsigned int row;
+
+    for (row = 0; row < rows; ++row)
+        tuplerowWindow[row] = pnm_allocpamrow(pamP);
+}
+
+
+
+static void
+freeRowWindowBuffer(tuple **     const tuplerowWindow,
+                    unsigned int const rows) {
+
+    unsigned int row;
+
+    for (row = 0; row < rows; ++row)
+        pnm_freepamrow(tuplerowWindow[row]);
+}
+
+
+
+static void
+slideWindowDown(tuple **     const tuplerowWindow,
+                unsigned int const rows) {
+/*----------------------------------------------------------------------------
+  Slide the rows-line tuple row window tuplerowWindow[] down one by moving
+  pointers.
+-----------------------------------------------------------------------------*/
+    tuple * const oldrow0 = tuplerowWindow[0];
+
+    unsigned int i;
+
+    for (i = 0; i < rows-1; ++i)
+        tuplerowWindow[i] = tuplerowWindow[i+1];
+
+    tuplerowWindow[i] = oldrow0;
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    FILE * ifP;
+    struct cmdlineInfo cmdline;
+    struct pam inpam;  
+    struct pam outpam;
+    tuple * tuplerowWindow[5];
+    tuple * outputrow;
+    unsigned int rows;
+    
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    rows = cmdline.filterP->rows;
+
+    ifP = pm_openr(cmdline.inputFileName);
+    
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    outpam = inpam;    /* Initial value -- most fields should be same */
+    outpam.file = stdout;
+
+    pnm_writepaminit(&outpam);
+
+    allocateRowWindowBuffer(&inpam, tuplerowWindow, rows);
+    outputrow = pnm_allocpamrow(&outpam);
+
+    if (inpam.height < rows) {
+        unsigned int row;
+        pm_message("WARNING: Image height less than %d.  No mixing done.",
+                   rows);
+        for (row = 0; row < inpam.height; ++row) {
+            pnm_readpamrow(&inpam, tuplerowWindow[0]);
+            pnm_writepamrow(&outpam, tuplerowWindow[0]);
+        }
+    } else {
+        unsigned int row;
+
+    for (row = 0; row < rows-1; ++row)
+        pnm_readpamrow(&inpam, tuplerowWindow[row]);
+
+    /* Pass through first unfilterable rows */
+    for (row = 0; row < rows/2; ++row)
+        pnm_writepamrow(&outpam, tuplerowWindow[row]);
+
+    for (row = rows / 2 + 1; row < inpam.height - rows / 2 + 1; ++row) {
+        pnm_readpamrow(&inpam, tuplerowWindow[rows-1]);
+        cmdline.filterP->filter(outputrow, tuplerowWindow,
+                                inpam.width, inpam.depth,
+                                cmdline.adaptive, inpam.maxval);
+        pnm_writepamrow(&outpam, outputrow);
+        
+        slideWindowDown(tuplerowWindow, rows);
+    }
+    
+    /* Pass through last rows */
+    for (row = rows/2; row < rows-1; ++row)
+        pnm_writepamrow(&outpam, tuplerowWindow[row]);
+    }
+
+    freeRowWindowBuffer(tuplerowWindow, rows);
+    pnm_freepamrow(outputrow);
+    pm_close(inpam.file);
+    pm_close(outpam.file);
+    
+    return 0;
+}
diff --git a/editor/pamoil.c b/editor/specialty/pamoil.c
index 6cb8d3ac..6cb8d3ac 100644
--- a/editor/pamoil.c
+++ b/editor/specialty/pamoil.c
diff --git a/editor/pampop9.c b/editor/specialty/pampop9.c
index d6c61e4f..d6c61e4f 100644
--- a/editor/pampop9.c
+++ b/editor/specialty/pampop9.c
diff --git a/editor/pbmlife.c b/editor/specialty/pbmlife.c
index be34cc69..be34cc69 100644
--- a/editor/pbmlife.c
+++ b/editor/specialty/pbmlife.c
diff --git a/editor/pgmabel.c b/editor/specialty/pgmabel.c
index 4914c4be..4914c4be 100644
--- a/editor/pgmabel.c
+++ b/editor/specialty/pgmabel.c
diff --git a/editor/specialty/pgmbentley.c b/editor/specialty/pgmbentley.c
new file mode 100644
index 00000000..aed92074
--- /dev/null
+++ b/editor/specialty/pgmbentley.c
@@ -0,0 +1,74 @@
+/* pgmbentley.c - read a portable graymap and smear it according to brightness
+**
+** Copyright (C) 1990 by Wilson Bent (whb@hoh-2.att.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 "pm_c_util.h"
+#include "pgm.h"
+
+
+static unsigned int const N = 4;
+
+
+int
+main(int argc, const char * argv[]) {
+
+    FILE * ifP;
+    int rows, cols;
+    gray maxval;
+    gray ** gin;
+    gray ** gout;
+    unsigned int row;
+    const char * inputFileName;
+
+    pm_proginit(&argc, argv);
+
+    if (argc-1 < 1)
+        inputFileName = "-";
+    else {
+        inputFileName = argv[1];
+
+        if (argc-1 > 1)
+            pm_error("There are no options and only one argument.  "
+                     "You specified %u", argc-1);
+    }
+	ifP = pm_openr(inputFileName);
+
+    gin = pgm_readpgm(ifP, &cols, &rows, &maxval);
+
+    pm_close(ifP);
+
+    gout = pgm_allocarray(cols, rows);
+
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        for (col = 0; col < cols; ++col)
+            gout[row][col] = 0;
+    }
+
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+
+        for (col = 0; col < cols; ++col) {
+            unsigned int const brow = MIN(rows-1, row + gin[row][col] / N);
+
+            gout[brow][col] = gin[row][col];
+	    }
+    }
+     
+    pgm_writepgm(stdout, gout, cols, rows, maxval, 0);
+
+    pm_close(stdout);
+    pgm_freearray(gout, rows);
+
+    return 0;
+}
diff --git a/editor/pgmmorphconv.c b/editor/specialty/pgmmorphconv.c
index abc4e718..abc4e718 100644
--- a/editor/pgmmorphconv.c
+++ b/editor/specialty/pgmmorphconv.c
diff --git a/editor/pnmindex.c b/editor/specialty/pnmindex.c
index cb7d3702..ca1da18c 100644
--- a/editor/pnmindex.c
+++ b/editor/specialty/pnmindex.c
@@ -23,10 +23,12 @@
 #include <sys/stat.h>
 
 
-#include "pnm.h"
+#include "pm_config.h"
+#include "pm_c_util.h"
 #include "shhopt.h"
 #include "mallocvar.h"
 #include "nstring.h"
+#include "pnm.h"
 
 struct cmdlineInfo {
     /* All the information the user supplied in the command line,
@@ -200,7 +202,7 @@ makeTempDir(const char ** const tempDirP) {
 
     asprintfN(&mytmpdir, "%s/pnmindex_%d", tmpdir, getpid());
 
-    rc = mkdir(mytmpdir, 0700);
+    rc = pm_mkdir(mytmpdir, 0700);
     if (rc != 0)
         pm_error("Unable to create temporary file directory '%s'.  mkdir() "
                  "fails with errno %d (%s)",
diff --git a/editor/specialty/ppm3d.c b/editor/specialty/ppm3d.c
new file mode 100644
index 00000000..d9ada365
--- /dev/null
+++ b/editor/specialty/ppm3d.c
@@ -0,0 +1,308 @@
+/*=============================================================================
+                                   ppmto3d
+===============================================================================
+  This program converts two PPM images into an anaglyph stereogram image PPM.
+  (for viewing with red/blue 3D glasses).
+
+=============================================================================*/
+
+#include <assert.h>
+
+#include "pm_c_util.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+#include "ppm.h"
+#include "lum.h"
+
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * leftInputFileName;  /* '-' if stdin */
+    const char * rghtInputFileName;  /* '-' if stdin */
+    int offset;
+    unsigned int color;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   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 optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+    unsigned int offsetSpec;
+    const char * offsetArg;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "color",   OPT_FLAG,   NULL,
+            &cmdlineP->color,   0);
+    OPTENT3(0, "offset",  OPT_INT,    &cmdlineP->offset,
+            &offsetSpec,  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 */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+    
+    if (argc-1 < 2)
+        pm_error("You must specify at least two arguments: left and right "
+                 "input file names.  You specified %u", argc-1);
+    else {
+        cmdlineP->leftInputFileName = argv[1];
+        cmdlineP->rghtInputFileName = argv[2];
+
+        if (argc-1 > 2) {
+            offsetArg = argv[3];
+
+            if (argc-1 > 3)
+                pm_error("Program takes at most 3 arguments:  left and "
+                         "right input file names and offset.  "
+                         "You specified %u", argc-1);
+        } else
+            offsetArg = NULL;
+    }
+
+    if (offsetArg && offsetSpec)
+        pm_error("You cannot specify both -offset and the offset "
+                 "argument (i.e. with -offset, there is at most "
+                 "two arguments: left and right input file names");
+    else if (!offsetArg && !offsetSpec)
+        cmdlineP->offset = 0;
+    else if (offsetArg)
+        cmdlineP->offset = atoi(offsetArg);
+}
+
+
+
+static void
+computeGrayscaleRow(const pixel * const inputRow,
+                    gray *        const outputRow,
+                    pixval        const maxval,
+                    unsigned int  const cols) {
+
+    if (maxval <= 255) {
+        unsigned int col;
+        /* Use fast approximation to 0.299 r + 0.587 g + 0.114 b. */
+        for (col = 0; col < cols; ++col)
+            outputRow[col] = ppm_fastlumin(inputRow[col]);
+    } else {
+        unsigned int col;
+        /* Can't use fast approximation, so fall back on floats. */
+        for (col = 0; col < cols; ++col)
+            outputRow[col] = PPM_LUMIN(inputRow[col]) + 0.5;
+    }
+}
+
+
+
+static void
+compute3dRowMono(gray *       const lGrayrow,
+                 gray *       const rGrayrow,
+                 pixel *      const pixelrow,
+                 unsigned int const cols,
+                 int          const offset) {
+    
+    unsigned int col;
+    gray *  lgP;
+    gray *  rgP;
+    pixel * pP;
+
+    assert(abs(offset) <= cols);
+
+    for (col = 0, pP = pixelrow, lgP = lGrayrow, rgP = rGrayrow;
+         col < cols + offset;
+         ++col) {
+            
+        if ((int)col < offset/2)
+            ++lgP;
+        else if ((int)col < offset) {
+            PPM_ASSIGN(*pP, 0, *lgP, *lgP);
+            ++lgP;
+            ++pP;
+        } else if (col < cols) {
+            PPM_ASSIGN(*pP, *rgP, *lgP, *lgP);
+            ++lgP;
+            ++rgP;
+            ++pP;
+        } else if (col < cols + offset/2) {
+            PPM_ASSIGN(*pP, *rgP, 0, 0);
+            ++rgP;
+            ++pP;
+        } else {
+            assert(col < cols + offset);
+            ++rgP;
+        }
+    }
+}    
+
+
+
+static void
+compute3dRowColor(pixel *      const lPixelrow,
+                  pixel *      const rPixelrow,
+                  pixel *      const pixelrow,
+                  unsigned int const cols,
+                  unsigned int const offset) {
+    
+    unsigned int col;
+    pixel * lP;
+    pixel * rP;
+    pixel * pP;
+
+    assert(abs(offset) <= cols);
+
+    for (col = 0, pP = pixelrow, lP = lPixelrow, rP = rPixelrow;
+         col < cols + offset;
+         ++col) {
+            
+        if ((int)col < offset/2)
+            ++lP;
+        else if ((int)col < offset) {
+            PPM_ASSIGN(*pP, 0, PPM_GETG(*lP), PPM_GETB(*lP));
+            ++lP;
+            ++pP;
+        } else if (col < cols) {
+            PPM_ASSIGN(*pP, PPM_GETR(*rP), PPM_GETG(*lP), PPM_GETB(*lP));
+            ++lP;
+            ++rP;
+            ++pP;
+        } else if (col < cols + offset/2) {
+            PPM_ASSIGN(*pP, PPM_GETR(*rP), 0, 0);
+            ++rP;
+            ++pP;
+        } else {
+            assert(col < cols + offset);
+            ++rP;
+        }
+    }
+}    
+
+
+
+static void
+write3dRaster(FILE *       const ofP,
+              FILE *       const lIfP,
+              FILE *       const rIfP,
+              unsigned int const cols,
+              unsigned int const rows,
+              pixval       const maxval,
+              int          const lFormat,
+              int          const rFormat,
+              int          const offset,
+              bool         const color) {
+
+    pixel * lPixelrow;
+    gray *  lGrayrow;
+    pixel * rPixelrow;
+    gray *  rGrayrow;
+    pixel * pixelrow;
+
+    unsigned int row;
+
+    assert(abs(offset) < cols);
+
+    lPixelrow = ppm_allocrow (cols);
+    lGrayrow  = pgm_allocrow (cols);
+    rPixelrow = ppm_allocrow (cols);
+    rGrayrow  = pgm_allocrow (cols);
+    pixelrow  = ppm_allocrow (cols);
+
+    for (row = 0; row < rows; ++row) {
+        ppm_readppmrow(lIfP, lPixelrow, cols, maxval, lFormat);
+        ppm_readppmrow(rIfP, rPixelrow, cols, maxval, rFormat);
+
+        computeGrayscaleRow(lPixelrow, lGrayrow, maxval, cols);
+        computeGrayscaleRow(rPixelrow, rGrayrow, maxval, cols);
+
+        if (color)
+            compute3dRowColor(lPixelrow, rPixelrow, pixelrow, cols, offset);
+        else
+            compute3dRowMono(lGrayrow, rGrayrow, pixelrow, cols, offset);
+
+        ppm_writeppmrow(ofP, pixelrow, cols, maxval, 0);
+    }
+
+    ppm_freerow(pixelrow);
+    pgm_freerow(rGrayrow);
+    ppm_freerow(rPixelrow);
+    pgm_freerow(lGrayrow);
+    ppm_freerow(lPixelrow);
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * lIfP;
+    FILE * rIfP;
+
+    int cols, rows;
+    pixval maxval;
+
+    int lRows, lCols;
+    int lFormat;
+    pixval lMaxval;
+   
+    int rRows, rCols;
+    int rFormat;
+    pixval rMaxval;
+   
+    ppm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    lIfP = pm_openr(cmdline.leftInputFileName);
+    rIfP = pm_openr(cmdline.rghtInputFileName);
+
+    ppm_readppminit(lIfP, &lCols, &lRows, &lMaxval, &lFormat);
+    ppm_readppminit(rIfP, &rCols, &rRows, &rMaxval, &rFormat);
+    
+    if ((lCols != rCols) || (lRows != rRows) || 
+        (lMaxval != rMaxval) || 
+        (PPM_FORMAT_TYPE(lFormat) != PPM_FORMAT_TYPE(rFormat)))
+        pm_error ("Pictures are not of same size and format");
+    
+    cols   = lCols;
+    rows   = lRows;
+    maxval = lMaxval;
+
+    if (abs(cmdline.offset) >= cols)
+        pm_error("Magnitude of -offset (%u columns) is not less than "
+                 "width of images "
+                 "(%u columns)", abs(cmdline.offset), cols);
+   
+    ppm_writeppminit(stdout, cols, rows, maxval, 0);
+
+    write3dRaster(stdout, lIfP, rIfP, cols, rows, maxval,
+                  lFormat, rFormat, cmdline.offset, cmdline.color);
+
+    pm_close(lIfP);
+    pm_close(rIfP);
+    pm_close(stdout);
+
+    return 0;
+}
+
diff --git a/editor/ppmglobe.c b/editor/specialty/ppmglobe.c
index ee1a57c3..82fae5fb 100644
--- a/editor/ppmglobe.c
+++ b/editor/specialty/ppmglobe.c
@@ -15,6 +15,7 @@
 #include <unistd.h>
 #include <math.h>
 
+#include "pm_c_util.h"
 #include "ppm.h"
 #include "colorname.h"
 #include "shhopt.h"
diff --git a/editor/ppmntsc.c b/editor/specialty/ppmntsc.c
index b9f2ac2f..ae3bcfe9 100644
--- a/editor/ppmntsc.c
+++ b/editor/specialty/ppmntsc.c
@@ -45,6 +45,8 @@
 #include <stdio.h>
 #include <math.h>
 #include <string.h>
+
+#include "pm_c_util.h"
 #include "ppm.h"
 #include "mallocvar.h"
 #include "shhopt.h"
diff --git a/editor/ppmrelief.c b/editor/specialty/ppmrelief.c
index 1c408aec..1c408aec 100644
--- a/editor/ppmrelief.c
+++ b/editor/specialty/ppmrelief.c
diff --git a/editor/specialty/ppmshift.c b/editor/specialty/ppmshift.c
new file mode 100644
index 00000000..a765daa5
--- /dev/null
+++ b/editor/specialty/ppmshift.c
@@ -0,0 +1,132 @@
+
+/*********************************************************************/
+/* ppmshift -  shift lines of a picture left or right by x pixels    */
+/* Frank Neumann, October 1993                                       */
+/* V1.1 16.11.1993                                                   */
+/*                                                                   */
+/* version history:                                                  */
+/* V1.0    11.10.1993  first version                                 */
+/* V1.1    16.11.1993  Rewritten to be NetPBM.programming conforming */
+/*********************************************************************/
+
+#include "ppm.h"
+
+/**************************/
+/* start of main function */
+/**************************/
+int
+main(int    argc,
+     char * argv[]) {
+
+    FILE * ifP;
+    unsigned int row;
+    int argn, rows, cols, format;
+    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";
+
+    /* 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;
+
+    if (argn != argc)
+        pm_usage(usage);
+
+    /* read first data from file */
+    ppm_readppminit(ifP, &cols, &rows, &maxval, &format);
+
+    if (shiftArg > cols) {
+        shift = cols;
+        pm_message("shift amount is larger than picture width - reset to %d",
+                   shift);
+    } else
+        shift = shiftArg;
+
+    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;
+            }
+        }
+
+        ppm_writeppmrow(stdout, destrow, cols, maxval, 0);
+    }
+
+    pm_close(ifP);
+    ppm_freerow(srcrow);
+    ppm_freerow(destrow);
+
+    return 0;
+}
diff --git a/editor/specialty/ppmspread.c b/editor/specialty/ppmspread.c
new file mode 100644
index 00000000..6753f4fe
--- /dev/null
+++ b/editor/specialty/ppmspread.c
@@ -0,0 +1,117 @@
+/*********************************************************************/
+/* ppmspread -  randomly displace a PPM's pixels by a certain amount */
+/* Frank Neumann, October 1993                                       */
+/* V1.1 16.11.1993                                                   */
+/*                                                                   */
+/* version history:                                                  */
+/* V1.0 12.10.1993    first version                                  */
+/* V1.1 16.11.1993    Rewritten to be NetPBM.programming conforming  */
+/*********************************************************************/
+
+#include <string.h>
+
+#include "ppm.h"
+
+
+
+int
+main(int    argc,
+     char * argv[]) {
+
+    FILE * ifP;
+    int argn, rows, cols;
+    unsigned int row;
+    pixel ** destarray, ** srcarray;
+    pixel * pP;
+    pixel * pP2;
+    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;
+
+    if (argn != argc)
+        pm_usage(usage);
+
+    srcarray = ppm_readppm(ifP, &cols, &rows, &maxval);
+
+    destarray = ppm_allocarray(cols, rows);
+
+    /* clear out the buffer */
+    for (row = 0; row < rows; ++row)
+        memset(destarray[row], 0, cols * sizeof(pixel));
+
+    srand(pm_randseed());
+
+    /* start displacing 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);
+
+            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;
+        }
+    }
+
+    ppm_writeppm(stdout, destarray, cols, rows, maxval, 0);
+
+    pm_close(ifP);
+    ppm_freearray(srcarray, rows);
+    ppm_freearray(destarray, rows);
+
+    return 0;
+}
diff --git a/editor/ppmtv.c b/editor/specialty/ppmtv.c
index da25102a..da25102a 100644
--- a/editor/ppmtv.c
+++ b/editor/specialty/ppmtv.c