about summary refs log tree commit diff
path: root/editor
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2023-12-28 19:53:34 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2023-12-28 19:53:34 +0000
commit5d16663331afd0bc2edaeb2e49042dc219ce9c2f (patch)
tree476fbb2ab4311d4bb6d65b250825e254a7a2c1ef /editor
parent42f0bf8e7f1ff88000a3584c265e6f1631662ec4 (diff)
downloadnetpbm-mirror-5d16663331afd0bc2edaeb2e49042dc219ce9c2f.tar.gz
netpbm-mirror-5d16663331afd0bc2edaeb2e49042dc219ce9c2f.tar.xz
netpbm-mirror-5d16663331afd0bc2edaeb2e49042dc219ce9c2f.zip
promote Development to Advanced
git-svn-id: http://svn.code.sf.net/p/netpbm/code/advanced@4827 9d0c8265-081b-0410-96cb-a4ca84ce46f8
Diffstat (limited to 'editor')
-rw-r--r--editor/Makefile2
-rw-r--r--editor/pnmhisteq.c25
-rwxr-xr-xeditor/pnmmargin31
-rw-r--r--editor/pnmpad.c810
-rw-r--r--editor/pnmremap.c48
-rw-r--r--editor/pnmscalefixed.c7
-rw-r--r--editor/ppmdither.c96
-rw-r--r--editor/specialty/pamdeinterlace.c27
-rw-r--r--editor/specialty/pamoil.c233
-rw-r--r--editor/specialty/pnmindex.c338
10 files changed, 1126 insertions, 491 deletions
diff --git a/editor/Makefile b/editor/Makefile
index 0027832c..5929fc71 100644
--- a/editor/Makefile
+++ b/editor/Makefile
@@ -121,5 +121,5 @@ mergecomptrylist:
 	echo "TRY(\"pnmcut\",     main_pamcut);"     >>$@
 	echo "TRY(\"pnmscale\",   main_pamscale);"   >>$@
 	echo "TRY(\"pnmcomp\",    main_pamcomp);"    >>$@
-	echo "TRY(\"pnmcat\",     main_pamcomp);"    >>$@
+	echo "TRY(\"pnmcat\",     main_pamcat);"     >>$@
 
diff --git a/editor/pnmhisteq.c b/editor/pnmhisteq.c
index a339f73f..c2e39089 100644
--- a/editor/pnmhisteq.c
+++ b/editor/pnmhisteq.c
@@ -1,15 +1,14 @@
-/*
-                 pnmhisteq.c
-
-           Equalize histogram for a PNM image
+/*=============================================================================
+                              pnmhisteq
+===============================================================================
+  Equalize histogram for a PNM image
 
   By Bryan Henderson 2005.09.10, based on ideas from the program of
   the same name by John Walker (kelvin@fourmilab.ch) -- March MVM.
   WWW home page: http://www.fourmilab.ch/ in 1995.
 
   This program is contributed to the public domain by its author.
-*/
-
+=============================================================================*/
 #include <string.h>
 
 #include "pm_c_util.h"
@@ -18,7 +17,7 @@
 #include "mallocvar.h"
 
 
-struct cmdlineInfo {
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
@@ -34,8 +33,8 @@ struct cmdlineInfo {
 
 
 static void
-parseCommandLine(int argc, char ** argv,
-                 struct cmdlineInfo * const cmdlineP) {
+parseCommandLine(int argc, const char ** argv,
+                 struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that the file spec array we return is stored in the storage that
    was passed to us as the argv array.
@@ -68,7 +67,7 @@ parseCommandLine(int argc, char ** argv,
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
     opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
 
-    pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
 
@@ -497,9 +496,9 @@ writeMap(const char * const wmapFileName,
 
 
 int
-main(int argc, char * argv[]) {
+main(int argc, const char ** const argv) {
 
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
     FILE * ifP;
     gray * lumamap;           /* Luminosity map */
     unsigned int * lumahist;  /* Histogram of luminosity values */
@@ -509,7 +508,7 @@ main(int argc, char * argv[]) {
     xel ** xels;              /* Pixel array */
     unsigned int pixelCount;
 
-    pnm_init(&argc, argv);
+    pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
 
diff --git a/editor/pnmmargin b/editor/pnmmargin
index 94321c7f..6f82ddbe 100755
--- a/editor/pnmmargin
+++ b/editor/pnmmargin
@@ -11,15 +11,6 @@
 # documentation.  This software is provided "as is" without express or
 # implied warranty.
 
-tempdir=$(mktemp -d "${TMPDIR:-/tmp}/netpbm.XXXXXXXX") ||
-    { echo "Could not create temporary file. Exiting." 1>&2; exit 1; }
-trap 'rm -rf $tempdir' 0 1 3 15
-
-tmp1=$tempdir/pnmm1
-tmp2=$tempdir/pnmm2
-tmp3=$tempdir/pnmm3
-tmp4=$tempdir/pnmm4
-
 color="-gofigure"
 plainopt=""
 
@@ -79,37 +70,31 @@ if [ ${2-""} ] ; then
     exit 1
 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
-
 if [ $size -eq 0 ] ; then
     # Zero margin; just copy input to output
-    pamtopnm $plainopt $tmp1;
+    pamtopnm $plainopt $@;
 else
     # If -white or -black, write output with pnmpad and exit.
     # Otherwise construct spacer files.
 
     case "$color" in
         -gofigure )
-        pamcut 0 0 1 1 $tmp1 | pnmtile $size 1 > $tmp2
+        pnmpad $plainopt -detect-background \
+            -left=$size -right=$size -top=$size -bottom=$size $@
+        exit
         ;;
         -white | -black )
         pnmpad $plainopt $color \
-            -left=$size -right=$size -top=$size -bottom=$size $tmp1
+            -left=$size -right=$size -top=$size -bottom=$size $@
         exit
         ;;
         * )
-        ppmmake $color $size 1 > $tmp2
+        pnmpad $plainopt -color $color \
+            -left=$size -right=$size -top=$size -bottom=$size $@
+        exit
         ;;
     esac
-    pamflip -rotate90 $tmp2 > $tmp3
-
-    # Cat things together.
-    pamcat -leftright $tmp2 $tmp1 $tmp2 > $tmp4
-    pamcat -topbottom $plainopt $tmp3 $tmp4 $tmp3
 fi
-
-
diff --git a/editor/pnmpad.c b/editor/pnmpad.c
index 10b69d03..94ea0ed1 100644
--- a/editor/pnmpad.c
+++ b/editor/pnmpad.c
@@ -1,13 +1,15 @@
-/* pnmpad.c - add border to sides of a portable anymap
+/* pnmpad.c - add border to sides of a PNM
    ** AJCD 4/9/90
  */
 
+#include <stdbool.h>
 #include <assert.h>
 #include <string.h>
 #include <stdio.h>
 
 #include "pm_c_util.h"
 #include "mallocvar.h"
+#include <nstring.h>
 #include "shhopt.h"
 #include "pnm.h"
 
@@ -16,11 +18,54 @@
        arithmetic overflow
     */
 
-struct cmdlineInfo {
+enum PadType {
+    /* PAD_COLOR means the specified padding is not black, white, or gray --
+       even regardless of whether user specified it with a -color option.
+       E.g. -color=black is PAD_BLACK.
+    */
+    PAD_BLACK,
+    PAD_WHITE,
+    PAD_GRAY,
+    PAD_COLOR,
+    PAD_DETECTED_BG,
+    PAD_EXTEND_EDGE
+};
+
+enum PromoteType {
+    PROMOTE_NONE,
+    PROMOTE_FORMAT,
+    PROMOTE_ALL
+};
+
+
+
+static enum PadType
+padTypeFmColorStr(const char * const colorStr) {
+
+    pixel const color = ppm_parsecolor(colorStr, PPM_OVERALLMAXVAL);
+
+    enum PadType retval;
+
+    if (PPM_ISGRAY(color)) {
+        if (PPM_GETR(color) == 0)
+            retval = PAD_BLACK;
+        else if  (PPM_GETR(color) == PPM_OVERALLMAXVAL)
+            retval = PAD_WHITE;
+        else
+            retval = PAD_GRAY;
+    } else
+        retval = PAD_COLOR;
+
+    return retval;
+}
+
+
+
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
-    const char * input_filespec;  /* Filespecs of input files */
+    const char * inputFileNm;  /* Names of input files */
     unsigned int xsize;
     unsigned int xsizeSpec;
     unsigned int ysize;
@@ -37,7 +82,9 @@ struct cmdlineInfo {
     float yalign;
     unsigned int mwidth;
     unsigned int mheight;
-    unsigned int white;     /* >0: pad white; 0: pad black */
+    enum PadType padType;
+    const char * color;
+    enum PromoteType promote;
     unsigned int reportonly;
     unsigned int verbose;
 };
@@ -45,8 +92,45 @@ struct cmdlineInfo {
 
 
 static void
+validateNoOldOptionSyntax( int const argc, const char ** const argv) {
+/*----------------------------------------------------------------------------
+  Reject obsolete command line syntax, e.g. "pnmpad -l50".
+
+  Starting in Netpbm 9.25 (February 2002), this resulted in a warning message
+  and was no longer documented.  Starting in Netpbm 11.05 (December 2023),
+  it is no longer accepted.
+
+  It was too hard to maintain.
+-----------------------------------------------------------------------------*/
+    bool isOld;
+
+    isOld = false;  /* initial assumption */
+
+    if (argc > 1 && argv[1][0] == '-') {
+        if (argv[1][1] == 't' || argv[1][1] == 'b'
+            || argv[1][1] == 'l' || argv[1][1] == 'r') {
+            if (argv[1][2] >= '0' && argv[1][2] <= '9')
+                isOld = true;
+        }
+    }
+    if (argc > 2 && argv[2][0] == '-') {
+        if (argv[2][1] == 't' || argv[2][1] == 'b'
+            || argv[2][1] == 'l' || argv[2][1] == 'r') {
+            if (argv[2][2] >= '0' && argv[2][2] <= '9')
+                isOld = true;
+        }
+    }
+    if (isOld)
+        pm_error("Old-style Unix options (e.g. \"-l50\") "
+                 "not accepted by current 'pnmpad'.  "
+                 "Use e.g. \"-l 50\" instead");
+}
+
+
+
+static void
 parseCommandLine(int argc, const char ** argv,
-                 struct cmdlineInfo * const cmdlineP) {
+                 struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that the file spec array we return is stored in the storage that
    was passed to us as the argv array.
@@ -57,8 +141,12 @@ parseCommandLine(int argc, const char ** argv,
     optStruct3 opt;
 
     unsigned int option_def_index;
-    unsigned int blackOpt;
+    unsigned int black, white, extend_edge;
     unsigned int xalignSpec, yalignSpec, mwidthSpec, mheightSpec;
+    unsigned int colorSpec;
+    unsigned int detect_background;
+    unsigned int promoteSpec;
+    const char * promoteOpt;
 
     MALLOCARRAY_NOFAIL(option_def, 100);
 
@@ -88,28 +176,89 @@ parseCommandLine(int argc, const char ** argv,
     OPTENT3(0,   "valign",    OPT_FLOAT,   &cmdlineP->yalign,
             &yalignSpec,           0);
     OPTENT3(0,   "mwidth",    OPT_UINT,    &cmdlineP->mwidth,
-            &mwidthSpec,         0);
+            &mwidthSpec,           0);
     OPTENT3(0,   "mheight",   OPT_UINT,    &cmdlineP->mheight,
-            &mheightSpec,        0);
+            &mheightSpec,          0);
     OPTENT3(0,   "black",     OPT_FLAG,    NULL,
-            &blackOpt,           0);
+            &black,                0);
     OPTENT3(0,   "white",     OPT_FLAG,    NULL,
-            &cmdlineP->white,    0);
+            &white,                0);
+    OPTENT3(0,   "color",     OPT_STRING,  &cmdlineP->color,
+            &colorSpec,            0);
+    OPTENT3(0,   "extend-edge", OPT_FLAG,  NULL,
+            &extend_edge,          0);
+    OPTENT3(0,   "detect-background",  OPT_FLAG,  NULL,
+            &detect_background,    0);
+    OPTENT3(0,   "promote",     OPT_STRING, &promoteOpt,
+            &promoteSpec,          0);
     OPTENT3(0,   "reportonly", OPT_FLAG,   NULL,
-            &cmdlineP->reportonly,   0);
-    OPTENT3(0,   "verbose",   OPT_FLAG,    NULL,
-            &cmdlineP->verbose,  0);
+            &cmdlineP->reportonly, 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 */
+    opt.short_allowed = false;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = false;  /* We have no parms that are negative numbers */
 
     pm_optParseOptions3(&argc, (char **)argv, opt, sizeof opt, 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
-    if (blackOpt && cmdlineP->white)
-        pm_error("You cannot specify both -black and -white");
+    if (!colorSpec)
+        cmdlineP->color = NULL;
+
+    if (black + white + colorSpec + detect_background + extend_edge > 1)
+        pm_error("You can specify only one one of -black, -white, "
+                 "-color -detect-background and -extend-edge");
+    else if (black)
+        cmdlineP->padType = PAD_BLACK;
+    else if (white)
+        cmdlineP->padType = PAD_WHITE;
+    else if (colorSpec)
+        cmdlineP->padType = padTypeFmColorStr(cmdlineP->color);
+    else if (extend_edge)
+        cmdlineP->padType = PAD_EXTEND_EDGE;
+    else if (detect_background)
+        cmdlineP->padType = PAD_DETECTED_BG;
+    else {
+        cmdlineP->padType = PAD_BLACK;
+        if (cmdlineP->verbose)
+            pm_message("No pad color specified.  Padding with black.");
+    }
 
+    if (promoteSpec) {
+        if (!colorSpec)
+            pm_error("-promote requires -color");
+        else {
+            switch (cmdlineP->padType) {
+            case PAD_COLOR:
+            case PAD_GRAY:
+                if (streq(promoteOpt, "none"))
+                    cmdlineP->promote = PROMOTE_NONE;
+                else if (streq(promoteOpt, "format"))
+                    cmdlineP->promote = PROMOTE_FORMAT;
+                else if (streq(promoteOpt, "all"))
+                    cmdlineP->promote = PROMOTE_ALL;
+                else
+                    pm_error("Invalid value for -promote.  "
+                             "Valid values are 'none', 'format', and 'all'");
+                break;
+            case PAD_BLACK:
+            case PAD_WHITE:
+                cmdlineP->promote = PROMOTE_NONE;
+                break;
+            case PAD_DETECTED_BG:
+            case PAD_EXTEND_EDGE:
+                assert(false);  /* Because we have -color */
+                break;
+            }
+        }
+    } else {
+        if (cmdlineP->padType == PAD_COLOR ||
+            cmdlineP->padType == PAD_GRAY) {
+            cmdlineP->promote = PROMOTE_ALL;
+        } else
+            cmdlineP->promote = PROMOTE_NONE;
+    }
     if (cmdlineP->topSpec > 1)
        pm_error("You can specify -top only once");
 
@@ -171,87 +320,15 @@ parseCommandLine(int argc, const char ** argv,
         pm_error("This program takes at most 1 parameter.  You specified %d",
                  argc-1);
     else if (argc-1 == 1)
-        cmdlineP->input_filespec = argv[1];
+        cmdlineP->inputFileNm = argv[1];
     else
-        cmdlineP->input_filespec = "-";
+        cmdlineP->inputFileNm = "-";
 }
 
 
 
 static void
-parseCommandLineOld(int argc, const char ** argv,
-                    struct cmdlineInfo * const cmdlineP) {
-
-    /* This syntax was abandoned in February 2002. */
-    pm_message("Warning: old style options are deprecated!");
-
-    cmdlineP->xsize = cmdlineP->ysize = 0;
-    cmdlineP->left = cmdlineP->right = cmdlineP->top = cmdlineP->bottom = 0;
-    cmdlineP->xalign = cmdlineP->yalign = 0.5;
-    cmdlineP->white = cmdlineP->verbose = FALSE;
-    cmdlineP->reportonly = FALSE;
-    cmdlineP->topSpec = cmdlineP->bottomSpec =
-        cmdlineP->leftSpec = cmdlineP->rightSpec = TRUE;
-    cmdlineP->mwidth = cmdlineP->mheight = 1;
-
-    while (argc >= 2 && argv[1][0] == '-') {
-        if (strcmp(argv[1]+1,"black") == 0) cmdlineP->white = FALSE;
-        else if (strcmp(argv[1]+1,"white") == 0) cmdlineP->white = TRUE;
-        else switch (argv[1][1]) {
-        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;
-        default:
-            pm_usage("[-white|-black] [-l#] [-r#] [-t#] [-b#] [pnmfile]");
-        }
-        argc--, argv++;
-    }
-
-    cmdlineP->xsizeSpec = (cmdlineP->xsize > 0);
-    cmdlineP->ysizeSpec = (cmdlineP->ysize > 0);
-
-    if (argc > 2)
-        pm_usage("[-white|-black] [-l#] [-r#] [-t#] [-b#] [pnmfile]");
-
-    if (argc == 2)
-        cmdlineP->input_filespec = argv[1];
-    else
-        cmdlineP->input_filespec = "-";
-}
-
-
-
-static void
-validateHorizontalSize(struct cmdlineInfo const cmdline,
+validateHorizontalSize(struct CmdlineInfo const cmdline,
                        unsigned int       const cols) {
 /*----------------------------------------------------------------------------
    Abort the program if the padding parameters in 'cmdline', applied to
@@ -279,7 +356,7 @@ validateHorizontalSize(struct cmdlineInfo const cmdline,
                  "for this program to compute");
 
     if (cmdline.xsizeSpec &&
-        (double) cmdline.xsize + (double) mwidthMaxPad> MAX_WIDTHHEIGHT)
+        (double) cmdline.xsize + (double) mwidthMaxPad > MAX_WIDTHHEIGHT)
         pm_error("Given padding parameters make output width too large "
                  "for this program to compute");
 }
@@ -400,7 +477,7 @@ computePadSizesOneDim(unsigned int   const unpaddedSize,
 
 
 static void
-computeHorizontalPadSizes(struct cmdlineInfo const cmdline,
+computeHorizontalPadSizes(struct CmdlineInfo const cmdline,
                           unsigned int       const cols,
                           unsigned int *     const lpadP,
                           unsigned int *     const rpadP) {
@@ -419,7 +496,7 @@ computeHorizontalPadSizes(struct cmdlineInfo const cmdline,
 
 
 static void
-validateVerticalSize(struct cmdlineInfo const cmdline,
+validateVerticalSize(struct CmdlineInfo const cmdline,
                      unsigned int       const rows) {
 /*----------------------------------------------------------------------------
    Abort the program if the padding parameters in 'cmdline', applied to
@@ -457,7 +534,7 @@ validateVerticalSize(struct cmdlineInfo const cmdline,
 
 
 static void
-computeVerticalPadSizes(struct cmdlineInfo const cmdline,
+computeVerticalPadSizes(struct CmdlineInfo const cmdline,
                         int                const rows,
                         unsigned int *     const tpadP,
                         unsigned int *     const bpadP) {
@@ -476,7 +553,7 @@ computeVerticalPadSizes(struct cmdlineInfo const cmdline,
 
 
 static void
-computePadSizes(struct cmdlineInfo const cmdline,
+computePadSizes(struct CmdlineInfo const cmdline,
                 int                const cols,
                 int                const rows,
                 unsigned int *     const lpadP,
@@ -512,6 +589,205 @@ reportPadSizes(int          const inCols,
 
 
 
+static unsigned char
+bitPeek(const unsigned char * const bitrow,
+        unsigned int          const position) {
+/*----------------------------------------------------------------------------
+  Value of pixel (bit) of 'bitrow' at 'position'.
+-----------------------------------------------------------------------------*/
+    bool retval;
+
+    unsigned int const charPosition = position / 8;
+    unsigned int const bitPosition  = position % 8;
+
+    retval =  (bitrow[charPosition] >> (7 - bitPosition)) & 0x01;
+
+    return retval;
+}
+
+
+
+static void
+extendLeftPbm(unsigned char * const bitrow,
+              unsigned int    const lpad) {
+/*----------------------------------------------------------------------------
+  Add 'lpad' pixels of padding the the left side of PBM row 'bitrow' by
+  duplicating leftmost pixel.
+-----------------------------------------------------------------------------*/
+    unsigned int const padCharCt  = lpad / 8;
+    unsigned int const fractBitCt = lpad % 8;
+    unsigned int const color      = bitPeek(bitrow, lpad);
+
+    unsigned int colChar;
+
+    for (colChar = 0; colChar < padCharCt; ++colChar)
+        bitrow[colChar] = 0xff * color;
+
+    if (fractBitCt > 0) {
+        bitrow[colChar] = (unsigned char)
+            (0xff * color) << (8-fractBitCt) |
+            (bitrow[colChar] & (0xff >> fractBitCt));
+    }
+}
+
+
+
+static void
+extendRightPbm(unsigned char * const bitrow,
+               unsigned int    const lcols,
+               unsigned int    const rpad) {
+/*----------------------------------------------------------------------------
+  Add 'rpad' pixels of padding the the left side of PBM row 'bitrow',
+  which is 'lcols' columns wide, by duplicating leftmost pixel.
+-----------------------------------------------------------------------------*/
+    unsigned int  const rpad0       = (8 - lcols % 8) % 8;
+    unsigned int  const rpad1       = rpad - rpad0;
+    unsigned int  const rpad1CharCt =
+        rpad1 > 0 ? pbm_packed_bytes(rpad1) : 0;
+    unsigned int  const lastColChar = lcols / 8 - (rpad0 == 0 ? 1 : 0);
+    unsigned char const fillColor   = bitPeek(bitrow, lcols - 1);
+
+    unsigned int colChar;
+
+    if (rpad0 > 0) {
+        if (fillColor == 0x1) {
+            bitrow[lastColChar] =
+                bitrow[lastColChar] | (bitrow[lastColChar] - 1);
+        } else if (lcols % 8 > 0) {
+            /* This is essentially pbm_cleanrowend_packed(bitrow, lcols); */
+
+            unsigned int const last = pbm_packed_bytes(lcols) - 1;
+
+            bitrow[last] >>= 8 - lcols % 8;
+            bitrow[last] <<= 8 - lcols % 8;
+        }
+    }
+
+    for (colChar = 0; colChar < rpad1CharCt; ++colChar)
+        bitrow[lastColChar + colChar +1] = 0xff * fillColor;
+
+    if (fillColor == 0x1)
+        pbm_cleanrowend_packed(bitrow, lcols + rpad);
+}
+
+
+
+static void
+extendEdgePbm(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,
+              FILE *       const ofP) {
+/*----------------------------------------------------------------------------
+  Fast extend-edge routine for PBM
+-----------------------------------------------------------------------------*/
+    unsigned char * const newbitrow = pbm_allocrow_packed(newcols);
+
+    unsigned int row;
+
+    pbm_writepbminit(stdout, newcols, rows + tpad + bpad, 0);
+
+    /* Write top row tpad + 1 times */
+    if (rpad > 0)
+        newbitrow[(cols + lpad) / 8] = 0x00;
+
+    pbm_readpbmrow_bitoffset(ifP, newbitrow, cols, format, lpad);
+
+    if (lpad > 0)
+        extendLeftPbm(newbitrow, lpad);
+    if (rpad > 0)
+        extendRightPbm(newbitrow, lpad + cols, rpad);
+
+    pbm_cleanrowend_packed(newbitrow, newcols);
+
+    for (row = 0; row < tpad + 1; ++row)
+        pbm_writepbmrow_packed(ofP, newbitrow, newcols, 0);
+
+    /* Read rows, shift and write with left and right margins added.
+
+       Alter left/right margin only when the color of leftmost/rightmost
+       pixel is different from that of the previous row.
+    */
+
+    for (row = 1;  row < rows; ++row) {
+        pbm_readpbmrow_bitoffset(ifP, newbitrow, cols, format, lpad);
+
+        if (lpad > 0 &&
+            (bitPeek(newbitrow, lpad - 1) != bitPeek(newbitrow, lpad)))
+            extendLeftPbm(newbitrow, lpad);
+        if (rpad > 0 &&
+            (bitPeek(newbitrow, lpad + cols -1) !=
+             bitPeek(newbitrow, lpad + cols)))
+            extendRightPbm(newbitrow, lpad + cols, rpad);
+        pbm_writepbmrow_packed(ofP, newbitrow, newcols, 0);
+    }
+
+    /* Write bottom margin */
+    for (row = 0; row < bpad; ++row)
+        pbm_writepbmrow_packed(ofP, newbitrow, newcols, 0);
+
+    pnm_freerow(newbitrow);
+}
+
+
+
+static void
+extendEdgeGeneral(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,
+                  FILE *       const ofP) {
+/*----------------------------------------------------------------------------
+  General extend-edge routine (logic works for PBM)
+-----------------------------------------------------------------------------*/
+    xel * const xelrow = pnm_allocrow(newcols);
+
+    unsigned int row, col;
+
+    pnm_writepnminit(ofP, newcols, rows + tpad + bpad, maxval, format, 0);
+
+    pnm_readpnmrow(ifP, &xelrow[lpad], cols, maxval, format);
+
+    for (col = 0; col < lpad; ++col)
+        xelrow[col] = xelrow[lpad];
+    for (col = lpad + cols; col < newcols; ++col)
+        xelrow[col] = xelrow[lpad + cols - 1];
+
+    for (row = 0; row < tpad + 1; ++row)
+        pnm_writepnmrow(ofP, xelrow, newcols, maxval, format, 0);
+
+    for (row = 1; row < rows; ++row) {
+        unsigned int col;
+
+        pnm_readpnmrow(ifP, &xelrow[lpad], cols, maxval, format);
+
+        for (col = 0; col < lpad; ++col)
+            xelrow[col] = xelrow[lpad];
+        for (col = lpad + cols; col < newcols; ++col)
+            xelrow[col] = xelrow[lpad + cols - 1];
+
+        pnm_writepnmrow(ofP, xelrow, newcols, maxval, format, 0);
+    }
+
+    for (row = 0; row < bpad; ++row)
+        pnm_writepnmrow(ofP, xelrow, newcols, maxval, format, 0);
+
+    pnm_freerow(xelrow);
+}
+
+
+
 static void
 padPbm(FILE *       const ifP,
        unsigned int const cols,
@@ -522,7 +798,8 @@ padPbm(FILE *       const ifP,
        unsigned int const rpad,
        unsigned int const tpad,
        unsigned int const bpad,
-       bool         const colorWhite) {
+       enum PadType const padType,
+       FILE *       const ofP) {
 /*----------------------------------------------------------------------------
   Fast padding routine for PBM
 -----------------------------------------------------------------------------*/
@@ -530,7 +807,7 @@ padPbm(FILE *       const ifP,
     unsigned char * const newrow = pbm_allocrow_packed(newcols);
 
     unsigned char const padChar =
-        0xff * (colorWhite ? PBM_WHITE : PBM_BLACK);
+        0xff * (padType == PAD_WHITE ? PBM_WHITE : PBM_BLACK);
 
     unsigned int const newColChars = pbm_packed_bytes(newcols);
 
@@ -546,70 +823,280 @@ padPbm(FILE *       const ifP,
         newrow[newColChars-1] <<= 8 - newcols % 8;
     }
 
-    pbm_writepbminit(stdout, newcols, rows + tpad + bpad, 0);
+    pbm_writepbminit(ofP, newcols, rows + tpad + bpad, 0);
 
     /* Write top margin */
     for (row = 0; row < tpad; ++row)
-        pbm_writepbmrow_packed(stdout, bgrow, newcols, 0);
+        pbm_writepbmrow_packed(ofP, 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);
+        pbm_writepbmrow_packed(ofP, newrow, newcols, 0);
     }
 
     pnm_freerow(newrow);
 
     /* Write bottom margin */
     for (row = 0; row < bpad; ++row)
-        pbm_writepbmrow_packed(stdout, bgrow, newcols, 0);
+        pbm_writepbmrow_packed(ofP, bgrow, newcols, 0);
 
     pnm_freerow(bgrow);
 }
 
 
+
+static xel
+regressColor(int  const format,
+             int  const maxval,
+             xel  const color) {
+
+    gray const grayval = (gray) ppm_luminosity(color);
+
+    xel retval;
+
+    switch (PNM_FORMAT_TYPE(format)) {
+    case PBM_TYPE:
+      {
+        gray const threshold = maxval / 2;
+
+        retval = grayval > threshold ?
+            pnm_whitexel(maxval, format) : pnm_blackxel(maxval, format);
+      }
+        break;
+    case PGM_TYPE:
+        retval.b = grayval;
+        retval.r = retval.g = 0;
+        break;
+    case PPM_TYPE:
+        retval = color;
+        break;
+    default:
+        pm_error("internal error -- impossible value of PNM format "
+                 "type in regressColor");
+    }
+    return retval;
+}
+
+
+
+static void
+computeOutputFormatAndBackground(int              const format,
+                                 int              const maxval,
+                                 enum PadType     const padType,
+                                 const char *     const colorStr,
+                                 enum PromoteType const promoteType,
+                                 int *            const newformatP,
+                                 pixval *         const newmaxvalP,
+                                 xel *            const backgroundP) {
+
+    int    newformat;
+    xelval newmaxval;
+    xel    background;
+
+    switch (promoteType) {
+    case PROMOTE_NONE:
+        newformat = format;
+        newmaxval = maxval;
+
+        switch (padType) {
+        case PAD_BLACK:
+            background = pnm_blackxel(newmaxval, newformat);
+            break;
+        case PAD_WHITE:
+            background = pnm_whitexel(newmaxval, newformat);
+            break;
+        case PAD_GRAY:
+        case PAD_COLOR:
+            background = regressColor(format, maxval,
+                                      ppm_parsecolor(colorStr, newmaxval));
+            break;
+        case PAD_DETECTED_BG:
+        case PAD_EXTEND_EDGE:
+            pm_error("internal error -- impossible value of 'promoteType' "
+                     "in computeOutputFormatAndBackground");
+        } break;
+
+    case PROMOTE_FORMAT:
+    case PROMOTE_ALL: {
+        newmaxval = promoteType == PROMOTE_ALL ? MAX(255, maxval) : maxval;
+
+        switch (padType) {
+        case PAD_BLACK:
+            newformat = format;
+            background = pnm_blackxel(newmaxval, newformat);
+            break;
+        case PAD_WHITE:
+            newformat = format;
+            background = pnm_whitexel(newmaxval, newformat);
+            break;
+        case PAD_GRAY:
+            newformat =
+                PNM_FORMAT_TYPE(format) == PBM_TYPE ? PGM_TYPE : format;
+            background = ppm_parsecolor(colorStr, newmaxval);
+            break;
+        case PAD_COLOR:
+            newformat = PPM_TYPE;
+            background = ppm_parsecolor(colorStr, newmaxval);
+            break;
+        case PAD_DETECTED_BG:
+        case PAD_EXTEND_EDGE:
+            pm_error("internal error -- impossible value of 'promoteType' "
+                     "in computeOutputFormatAndBackground");
+        } break;
+    }
+    }
+
+    *newformatP  = newformat;
+    *newmaxvalP  = newmaxval;
+    *backgroundP = background;
+}
+
+
+
 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) {
+padDetectedBg(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,
+              FILE *       const ofP) {
 /*----------------------------------------------------------------------------
-  General padding routine (logic works for PBM)
+  Pad using top left pixel as background color.
+
+  (There is no special PBM routine for this case)
 -----------------------------------------------------------------------------*/
     xel * const bgrow  = pnm_allocrow(newcols);
+        /* A row of background color (for top and bottom padding) */
     xel * const xelrow = pnm_allocrow(newcols);
-    xel background;
+        /* A row of padded output image.  Left and right padding is
+           constant; middle varies from row to row.
+        */
+    xel * const window = &xelrow[lpad];
+        /* The foreground part of the current row */
+
+    unsigned int row;;
+
+    pnm_writepnminit(ofP, newcols, rows + tpad + bpad, maxval, format, 0);
+
+    pnm_readpnmrow(ifP, window, cols, maxval, format);
+
+    {
+        /* Set 'bgrow' and constant parts of 'xelrow' */
+        xel const background = window[0];
+
+        unsigned int col;
+
+        for (col = 0; col < newcols; ++col)
+            bgrow[col] = background;
+
+        for (col = 0; col < lpad; ++col)
+            xelrow[col] = background;
+
+        for (col = lpad + cols; col < newcols; ++col)
+            xelrow[col] = background;
+    }
+
+    /* Write top padding */
+    for (row = 0; row < tpad; ++row)
+        pnm_writepnmrow(ofP, bgrow, newcols, maxval, format, 0);
+
+    /* Write body of image */
+    pnm_writepnmrow(ofP, xelrow, newcols, maxval, format, 0);
+
+    for (row = 1; row < rows; ++row) {
+        pnm_readpnmrow(ifP, window, cols, maxval, format);
+        pnm_writepnmrow(ofP, xelrow, newcols, maxval, format, 0);
+    }
+    pnm_freerow(xelrow);
+
+    /* Write bottom padding */
+    for (row = 0; row < bpad; ++row)
+        pnm_writepnmrow(ofP, bgrow, newcols, maxval, format, 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,
+           enum PadType     const padType,
+           const char *     const colorStr,
+           enum PromoteType const promoteType,
+           FILE *           const ofP) {
+/*----------------------------------------------------------------------------
+  General padding routine (logic works for PBM)
+-----------------------------------------------------------------------------*/
+    xel * const bgrow = pnm_allocrow(newcols);
+        /* A row of background color (for top and bottom padding) */
+
+    int    newformat;
+    pixval newmaxval;
+    xel    background;
+
     unsigned int row, col;
 
-    if (colorWhite)
-        background = pnm_whitexel(maxval, format);
-    else
-        background = pnm_blackxel(maxval, format);
+    computeOutputFormatAndBackground(
+        format, maxval, padType, colorStr, promoteType,
+        &newformat, &newmaxval, &background);
+
+    if (newformat != format)
+        pm_message("Promoting to %s", pnm_formattypenm(newformat));
 
     for (col = 0; col < newcols; ++col)
-        xelrow[col] = bgrow[col] = background;
+        bgrow[col] = background;
 
-    pnm_writepnminit(stdout, newcols, rows + tpad + bpad, maxval, format, 0);
+    pnm_writepnminit(ofP, newcols, rows + tpad + bpad,
+                     newmaxval, newformat, 0);
+
+    /* Write top padding */
 
     for (row = 0; row < tpad; ++row)
-        pnm_writepnmrow(stdout, bgrow, newcols, maxval, format, 0);
+        pnm_writepnmrow(ofP, bgrow, newcols, newmaxval, newformat, 0);
 
-    for (row = 0; row < rows; ++row) {
-        pnm_readpnmrow(ifP, &xelrow[lpad], cols, maxval, format);
-        pnm_writepnmrow(stdout, xelrow, newcols, maxval, format, 0);
+    /* Write body of image */
+    {
+        xel * const xelrow = pnm_allocrow(newcols);
+            /* A row of padded output image.  Left and right padding is
+               constant; middle varies from row to row.
+            */
+        xel * const window = &xelrow[lpad];
+            /* The foreground part of the current row */
+
+        /* Initial value for 'xelrow': all background */
+        for (col = 0; col < newcols; ++col)
+            xelrow[col] = bgrow[col] = background;
+
+        for (row = 0; row < rows; ++row) {
+            pnm_readpnmrow(ifP, window, cols, maxval, format);
+            if (maxval != newmaxval || format != newformat) {
+                pnm_promoteformatrow(window, cols, maxval, format,
+                                     newmaxval, newformat);
+            }
+            pnm_writepnmrow(ofP, xelrow, newcols, newmaxval, newformat, 0);
+        }
+        pnm_freerow(xelrow);
     }
 
+    /* Write bottom padding */
     for (row = 0; row < bpad; ++row)
-        pnm_writepnmrow(stdout, bgrow, newcols, maxval, format, 0);
+        pnm_writepnmrow(ofP, bgrow, newcols, newmaxval, newformat, 0);
 
-    pnm_freerow(xelrow);
     pnm_freerow(bgrow);
 }
 
@@ -618,43 +1105,25 @@ padGeneral(FILE *       const ifP,
 int
 main(int argc, const char ** argv) {
 
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
     FILE * ifP;
 
     xelval maxval;
     int rows, cols, newcols, format;
-    bool depr_cmd; /* use deprecated commandline interface */
     unsigned int lpad, rpad, tpad, bpad;
 
     pm_proginit(&argc, argv);
 
-    /* detect deprecated options */
-    depr_cmd = FALSE;  /* initial assumption */
-    if (argc > 1 && argv[1][0] == '-') {
-        if (argv[1][1] == 't' || argv[1][1] == 'b'
-            || argv[1][1] == 'l' || argv[1][1] == 'r') {
-            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') {
-            if (argv[2][2] >= '0' && argv[2][2] <= '9')
-                depr_cmd = TRUE;
-        }
-    }
+    validateNoOldOptionSyntax(argc, argv);
 
-    if (depr_cmd)
-        parseCommandLineOld(argc, argv, &cmdline);
-    else
-        parseCommandLine(argc, argv, &cmdline);
+    parseCommandLine(argc, argv, &cmdline);
 
-    ifP = pm_openr(cmdline.input_filespec);
+    ifP = pm_openr(cmdline.inputFileNm);
 
     pnm_readpnminit(ifP, &cols, &rows, &maxval, &format);
 
-    if (cmdline.verbose) pm_message("image WxH = %dx%d", cols, rows);
+    if (cmdline.verbose)
+        pm_message("image WxH = %dx%d", cols, rows);
 
     computePadSizes(cmdline, cols, rows, &lpad, &rpad, &tpad, &bpad);
 
@@ -663,15 +1132,40 @@ main(int argc, const char ** argv) {
     if (cmdline.reportonly)
         reportPadSizes(cols, rows, lpad, rpad, tpad, bpad);
     else {
-        if (PNM_FORMAT_TYPE(format) == PBM_TYPE)
-            padPbm(ifP, cols, rows, format, newcols, lpad, rpad, tpad, bpad,
-                   !!cmdline.white);
-        else
-            padGeneral(ifP, cols, rows, maxval, format,
-                       newcols, lpad, rpad, tpad, bpad, !!cmdline.white);
+        switch (cmdline.padType) {
+        case PAD_EXTEND_EDGE:
+            if (PNM_FORMAT_TYPE(format) == PBM_TYPE)
+                extendEdgePbm(ifP, cols, rows, format,
+                              newcols, lpad, rpad, tpad, bpad, stdout);
+            else
+                extendEdgeGeneral(ifP, cols, rows, maxval, format,
+                                  newcols, lpad, rpad, tpad, bpad, stdout);
+            break;
+        case PAD_DETECTED_BG:
+            padDetectedBg(ifP, cols, rows, maxval, format,
+                          newcols, lpad, rpad, tpad, bpad, stdout);
+            break;
+        case PAD_BLACK:
+        case PAD_WHITE:
+        case PAD_GRAY:
+        case PAD_COLOR:
+            if (PNM_FORMAT_TYPE(format) == PBM_TYPE &&
+                (cmdline.padType == PAD_WHITE || cmdline.padType == PAD_BLACK))
+                padPbm(ifP, cols, rows, format,
+                       newcols, lpad, rpad, tpad, bpad, cmdline.padType,
+                       stdout);
+            else
+                padGeneral(ifP, cols, rows, maxval, format,
+                           newcols, lpad, rpad, tpad, bpad, cmdline.padType,
+                           cmdline.color, cmdline.promote, stdout);
+            break;
+        }
     }
 
     pm_close(ifP);
 
     return 0;
 }
+
+
+
diff --git a/editor/pnmremap.c b/editor/pnmremap.c
index e5b59d04..38e203f7 100644
--- a/editor/pnmremap.c
+++ b/editor/pnmremap.c
@@ -468,7 +468,7 @@ fserr_init(struct pam *   const pamP,
 -----------------------------------------------------------------------------*/
     unsigned int plane;
 
-    unsigned int const fserrSize = pamP->width + 2;
+    unsigned int const fserrSz = pamP->width + 2;
 
     fserrP->width = pamP->width;
 
@@ -482,20 +482,20 @@ fserr_init(struct pam *   const pamP,
                  "for depth %u", pamP->depth);
 
     for (plane = 0; plane < pamP->depth; ++plane) {
-        MALLOCARRAY(fserrP->thiserr[plane], fserrSize);
+        MALLOCARRAY(fserrP->thiserr[plane], fserrSz);
         if (fserrP->thiserr[plane] == NULL)
             pm_error("Out of memory allocating Floyd-Steinberg structures "
-                     "for Plane %u, size %u", plane, fserrSize);
-        MALLOCARRAY(fserrP->nexterr[plane], fserrSize);
+                     "for Plane %u, size %u", plane, fserrSz);
+        MALLOCARRAY(fserrP->nexterr[plane], fserrSz);
         if (fserrP->nexterr[plane] == NULL)
             pm_error("Out of memory allocating Floyd-Steinberg structures "
-                     "for Plane %u, size %u", plane, fserrSize);
+                     "for Plane %u, size %u", plane, fserrSz);
     }
 
     if (random.init != RANDOM_NONE)
-        randomizeError(fserrP->thiserr, fserrSize, pamP->depth, random);
+        randomizeError(fserrP->thiserr, fserrSz, pamP->depth, random);
     else
-        zeroError(fserrP->thiserr, fserrSize, pamP->depth);
+        zeroError(fserrP->thiserr, fserrSz, pamP->depth);
 
     fserr_setForward(fserrP);
 }
@@ -1014,7 +1014,7 @@ static void
 copyRaster(struct pam *   const inpamP,
            struct pam *   const outpamP,
            tupletable     const colormap,
-           unsigned int   const colormapSize,
+           unsigned int   const colormapSz,
            bool           const floyd,
            struct Random  const random,
            tuple          const defaultColor,
@@ -1049,7 +1049,7 @@ copyRaster(struct pam *   const inpamP,
 
     usehash = TRUE;
 
-    createColormapFinder(outpamP, colormap, colormapSize, &colorFinderP);
+    createColormapFinder(outpamP, colormap, colormapSz, &colorFinderP);
 
     if (floyd)
         fserr_init(inpamP, &fserr, random);
@@ -1082,13 +1082,13 @@ static void
 remap(FILE *             const ifP,
       const struct pam * const outpamCommonP,
       tupletable         const colormap,
-      unsigned int       const colormapSize,
+      unsigned int       const colormapSz,
       bool               const floyd,
       struct Random      const random,
       tuple              const defaultColor,
       bool               const verbose) {
 /*----------------------------------------------------------------------------
-   Remap the pixels from the raster on *ifP to the 'colormapSize' colors in
+   Remap the pixels from the raster on *ifP to the 'colormapSz' colors in
    'colormap'.
 
    Where the input pixel's color is in the map, just use that for the output.
@@ -1129,7 +1129,7 @@ remap(FILE *             const ifP,
         */
         pnm_setminallocationdepth(&inpam, outpam.depth);
 
-        copyRaster(&inpam, &outpam, colormap, colormapSize, floyd,
+        copyRaster(&inpam, &outpam, colormap, colormapSz, floyd,
                    random, defaultColor, &missingCount);
 
         if (verbose)
@@ -1142,15 +1142,15 @@ remap(FILE *             const ifP,
 
 
 static void
-processMapFile(const char *   const mapFileName,
+processMapFile(const char *   const mapFileNm,
                struct pam *   const outpamCommonP,
                tupletable *   const colormapP,
-               unsigned int * const colormapSizeP,
+               unsigned int * const colormapSzP,
                tuple *        const firstColorP) {
 /*----------------------------------------------------------------------------
-   Read a color map from the file named 'mapFileName'.  It's a map that
+   Read a color map from the file named 'mapFileNm'.  It's a map that
    associates each color in that file with a unique whole number.  Return the
-   map as *colormapP, with the number of entries in it as *colormapSizeP.
+   map as *colormapP, with the number of entries in it as *colormapSzP.
 
    Also determine the first color (top left) in the map file and return that
    as *firstColorP.
@@ -1160,11 +1160,11 @@ processMapFile(const char *   const mapFileName,
     tuple ** maptuples;
     tuple firstColor;
 
-    mapfile = pm_openr(mapFileName);
+    mapfile = pm_openr(mapFileNm);
     maptuples = pnm_readpam(mapfile, &mappam, PAM_STRUCT_SIZE(tuple_type));
     pm_close(mapfile);
 
-    computeColorMapFromMap(&mappam, maptuples, colormapP, colormapSizeP);
+    computeColorMapFromMap(&mappam, maptuples, colormapP, colormapSzP);
 
     firstColor = pnm_allocpamtuple(&mappam);
     pnm_assigntuple(&mappam, firstColor, maptuples[0][0]);
@@ -1180,15 +1180,15 @@ processMapFile(const char *   const mapFileName,
 
 static void
 getSpecifiedMissingColor(struct pam * const pamP,
-                         const char * const colorName,
+                         const char * const colorNm,
                          tuple *      const specColorP) {
 
     tuple specColor;
 
     specColor = pnm_allocpamtuple(pamP);
 
-    if (colorName) {
-        pixel const color = ppm_parsecolor(colorName, pamP->maxval);
+    if (colorNm) {
+        pixel const color = ppm_parsecolor(colorNm, pamP->maxval);
         if (pamP->depth == 3) {
             specColor[PAM_RED_PLANE] = PPM_GETR(color);
             specColor[PAM_GRN_PLANE] = PPM_GETG(color);
@@ -1218,7 +1218,7 @@ main(int argc, const char * argv[] ) {
            across all output images.
         */
     tupletable colormap;
-    unsigned int colormapSize;
+    unsigned int colormapSz;
     tuple specColor;
         /* A tuple of the color the user specified to use for input colors
            that are not in the colormap.  Arbitrary tuple if he didn't
@@ -1239,7 +1239,7 @@ main(int argc, const char * argv[] ) {
     ifP = pm_openr(cmdline.inputFilespec);
 
     processMapFile(cmdline.mapFilespec, &outpamCommon,
-                   &colormap, &colormapSize, &firstColor);
+                   &colormap, &colormapSz, &firstColor);
 
     getSpecifiedMissingColor(&outpamCommon, cmdline.missingcolor, &specColor);
 
@@ -1255,7 +1255,7 @@ main(int argc, const char * argv[] ) {
         break;
     }
 
-    remap(ifP, &outpamCommon, colormap, colormapSize,
+    remap(ifP, &outpamCommon, colormap, colormapSz,
           cmdline.floyd, cmdline.random, defaultColor,
           cmdline.verbose);
 
diff --git a/editor/pnmscalefixed.c b/editor/pnmscalefixed.c
index 7d33a9c2..1a89a2e5 100644
--- a/editor/pnmscalefixed.c
+++ b/editor/pnmscalefixed.c
@@ -19,6 +19,7 @@
 **
 */
 
+#include <limits.h>
 #include <math.h>
 
 #include "pm_c_util.h"
@@ -215,7 +216,7 @@ compute_output_dimensions(const struct cmdline_info cmdline,
                           int * newrowsP, int * newcolsP) {
 
     if (cmdline.pixels) {
-        if (rows * cols <= cmdline.pixels) {
+        if (rows <= cmdline.pixels / cols) {
             *newrowsP = rows;
             *newcolsP = cols;
         } else {
@@ -446,6 +447,10 @@ main(int argc, char **argv ) {
        unfilled.  We can address that by stretching, whereas the other
        case would require throwing away some of the input.
     */
+    if (newcols > INT_MAX / SCALE)
+        pm_error("New image width (%d) is uncomputably large", newcols);
+    if (newrows > INT_MAX / SCALE)
+        pm_error("New image height (%d) is uncomputably large", newrows);
     sxscale = SCALE * newcols / cols;
     syscale = SCALE * newrows / rows;
 
diff --git a/editor/ppmdither.c b/editor/ppmdither.c
index df94cf34..2619c8b7 100644
--- a/editor/ppmdither.c
+++ b/editor/ppmdither.c
@@ -1,5 +1,5 @@
 /*=============================================================================
-                                 pamdither
+                                 ppmdither
 ===============================================================================
   By Bryan Henderson, July 2006.
 
@@ -23,7 +23,7 @@
 #define MAX_DITH_POWER (((unsigned)sizeof(unsigned int)*8 - 1) / 2)
 
 
-struct colorResolution {
+struct ColorResolution {
     unsigned int c[3];
         /* comp[PAM_RED_PLANE] is number of distinct red levels, etc. */
 };
@@ -38,7 +38,7 @@ struct CmdlineInfo {
     */
     const char * inputFileName;  /* File name of input file */
     unsigned int dim;
-    struct colorResolution colorRes;
+    struct ColorResolution colorRes;
     unsigned int verbose;
 };
 
@@ -139,7 +139,7 @@ typedef struct {
    0-3, and blue value of 0-1 to a tuple with maxval 255.  So you can
    ask it to scale (1,1,1) and it responds with (85, 85, 255).
 -----------------------------------------------------------------------------*/
-    struct colorResolution colorRes;
+    struct ColorResolution colorRes;
         /* Number of values of each color component possible, i.e. maxval
            plus 1
         */
@@ -148,7 +148,7 @@ typedef struct {
            certain function (see scaler_scale()) of the input red, green, and
            blue values.
         */
-} scaler;
+} Scaler;
 
 
 
@@ -186,10 +186,10 @@ allocScalerMap(unsigned int const size) {
 
 static void
 scaler_create(sample                 const outputMaxval,
-              struct colorResolution const colorRes,
-              scaler **              const scalerPP) {
+              struct ColorResolution const colorRes,
+              Scaler **              const scalerPP) {
 
-    scaler * scalerP;
+    Scaler * scalerP;
     unsigned int mapSize;
 
     if (UINT_MAX / colorRes.c[RED] / colorRes.c[GRN] / colorRes.c[BLU] < 1)
@@ -240,7 +240,7 @@ scaler_create(sample                 const outputMaxval,
 
 
 static void
-scaler_destroy(scaler * const scalerP) {
+scaler_destroy(Scaler * const scalerP) {
 
     free(scalerP->out);
 
@@ -250,7 +250,7 @@ scaler_destroy(scaler * const scalerP) {
 
 
 static tuple
-scaler_scale(const scaler * const scalerP,
+scaler_scale(const Scaler * const scalerP,
              unsigned int   const red,
              unsigned int   const grn,
              unsigned int   const blu) {
@@ -336,45 +336,37 @@ dithValue(unsigned int const yArg,
 
 
 static unsigned int **
-dithMatrix(unsigned int const dithPower) {
+newDithMatrix(unsigned int const dithPower) {
 /*----------------------------------------------------------------------------
    Create the dithering matrix for dimension 'dithDim'.
 
    Return it in newly malloc'ed storage.
 
-   Note that we assume 'dithPower' is small enough that the 'dithMatSize'
-   computed within fits in an int.  Otherwise, results are undefined.
+   Note that we assume 'dithPower' is not greater than the number of bits in
+   an unsigned int.
 -----------------------------------------------------------------------------*/
     unsigned int const dithDim = 1 << dithPower;
 
     unsigned int ** dithMat;
+    unsigned int y;
 
     assert(dithPower < sizeof(unsigned int) * 8);
 
-    {
-        unsigned int const dithMatSize =
-            (dithDim * sizeof(*dithMat)) + /* pointers */
-            (dithDim * dithDim * sizeof(**dithMat)); /* data */
-
-        dithMat = malloc(dithMatSize);
-
-        if (dithMat == NULL)
-            pm_error("Out of memory.  "
-                     "Cannot allocate %u bytes for dithering matrix.",
-                     dithMatSize);
-    }
-    {
-        unsigned int * const rowStorage = (unsigned int *)&dithMat[dithDim];
-        unsigned int y;
-        for (y = 0; y < dithDim; ++y)
-            dithMat[y] = &rowStorage[y * dithDim];
-    }
-    {
-        unsigned int y;
+    MALLOCARRAY(dithMat, dithDim);
+    if (!dithMat)
+        pm_error("Cannot allocate %u-row dithering matrix index", dithDim);
+    else {
         for (y = 0; y < dithDim; ++y) {
-            unsigned int x;
-            for (x = 0; x < dithDim; ++x)
-                dithMat[y][x] = dithValue(y, x, dithPower);
+            MALLOCARRAY(dithMat[y], dithDim);
+            if (!dithMat[y])
+                pm_error("Failed to allocate %uth row of "
+                         "%ux%u dithering matrix", y, dithDim, dithDim);
+            else {
+                unsigned int x;
+
+                for (x = 0; x < dithDim; ++x)
+                    dithMat[y][x] = dithValue(y, x, dithPower);
+            }
         }
     }
     return dithMat;
@@ -383,9 +375,25 @@ dithMatrix(unsigned int const dithPower) {
 
 
 static void
+freeDithMatrix(unsigned int ** const dithMat,
+               unsigned int    const dithPower) {
+
+    unsigned int const dithDim = 1 << dithPower;
+
+    unsigned int y;
+
+    for (y = 0; y < dithDim; ++y)
+        free(dithMat[y]);
+
+    free(dithMat);
+}
+
+
+
+static void
 validateNoDitherOverflow(unsigned int           const ditherMatrixArea,
                          struct pam *           const inpamP,
-                         struct colorResolution const colorRes) {
+                         struct ColorResolution const colorRes) {
 /*----------------------------------------------------------------------------
    Validate that we'll be able to do the dithering calculations based on
    the parameters above without busting out of an integer.
@@ -409,10 +417,10 @@ validateNoDitherOverflow(unsigned int           const ditherMatrixArea,
 static void
 ditherRow(struct pam *           const inpamP,
           const tuple *          const inrow,
-          const scaler *         const scalerP,
+          const Scaler *         const scalerP,
           unsigned int **        const ditherMatrix,
           unsigned int           const ditherMatrixArea,
-          struct colorResolution const colorRes,
+          struct ColorResolution const colorRes,
           unsigned int           const row,
           unsigned int           const modMask,
           struct pam *           const outpamP,
@@ -447,9 +455,9 @@ ditherRow(struct pam *           const inpamP,
 
 static void
 ditherImage(struct pam *           const inpamP,
-            const scaler *         const scalerP,
+            const Scaler *         const scalerP,
             unsigned int           const dithPower,
-            struct colorResolution const colorRes,
+            struct ColorResolution const colorRes,
             struct pam *           const outpamP,
             tuple ***              const outTuplesP) {
 
@@ -460,7 +468,7 @@ ditherImage(struct pam *           const inpamP,
        /* And this into N to compute N % dithDim cheaply, since we
           know (though the compiler doesn't) that dithDim is a power of 2
        */
-    unsigned int ** const ditherMatrix = dithMatrix(dithPower);
+    unsigned int ** const ditherMatrix = newDithMatrix(dithPower);
 
     tuple * inrow;
     tuple ** outTuples;
@@ -490,7 +498,7 @@ ditherImage(struct pam *           const inpamP,
                   colorRes, row, modMask,
                   outpamP, outTuples[row]);
     }
-    free(ditherMatrix);
+    freeDithMatrix(ditherMatrix, dithPower);
     pnm_freepamrow(inrow);
     *outTuplesP = outTuples;
 }
@@ -504,7 +512,7 @@ main(int           argc,
     struct CmdlineInfo cmdline;
     FILE * ifP;
     tuple ** outTuples;        /* Output image */
-    scaler * scalerP;
+    Scaler * scalerP;
     struct pam inpam;
     struct pam outpam;
 
diff --git a/editor/specialty/pamdeinterlace.c b/editor/specialty/pamdeinterlace.c
index 666daa16..a21484a9 100644
--- a/editor/specialty/pamdeinterlace.c
+++ b/editor/specialty/pamdeinterlace.c
@@ -8,6 +8,8 @@
   Contributed to the public domain.
 ******************************************************************************/
 
+#include <stdbool.h>
+
 #include "pm_c_util.h"
 #include "pam.h"
 #include "shhopt.h"
@@ -15,7 +17,7 @@
 
 enum evenodd {EVEN, ODD};
 
-struct cmdlineInfo {
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
@@ -24,9 +26,10 @@ struct cmdlineInfo {
 };
 
 
+
 static void
-parseCommandLine(int argc, char ** argv,
-                 struct cmdlineInfo *cmdlineP) {
+parseCommandLine(int argc, const char ** argv,
+                 struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that the file spec array we return is stored in the storage that
    was passed to us as the argv array.
@@ -44,10 +47,10 @@ parseCommandLine(int argc, char ** argv,
     OPTENT3(0,   "takeodd",  OPT_FLAG, NULL, &takeodd,  0);
 
     opt.opt_table = option_def;
-    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
-    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+    opt.short_allowed = false;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = false;  /* We have no parms that are negative numbers */
 
-    pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
     free(option_def);
@@ -65,26 +68,24 @@ parseCommandLine(int argc, char ** argv,
     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.",
+        pm_error("You specified too many arguments (%u).  The only "
+                 "possible argument is the optional input file specification.",
                  argc-1);
 }
 
 
 
-
-
 int
-main(int argc, char *argv[]) {
+main(int argc, const char ** argv) {
 
     FILE * ifP;
     tuple * tuplerow;   /* Row from input image */
     unsigned int row;
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
     struct pam inpam;
     struct pam outpam;
 
-    pnm_init( &argc, argv );
+    pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
 
diff --git a/editor/specialty/pamoil.c b/editor/specialty/pamoil.c
index 2618ac12..d7b76e85 100644
--- a/editor/specialty/pamoil.c
+++ b/editor/specialty/pamoil.c
@@ -1,4 +1,4 @@
-/* pgmoil.c - read a portable pixmap and turn into an oil painting
+/* pgmoil.c - read a PPM image and turn into an oil painting
 **
 ** Copyright (C) 1990 by Wilson Bent (whb@hoh-2.att.com)
 ** Shamelessly butchered into a color version by Chris Sheppard
@@ -12,60 +12,153 @@
 ** implied warranty.
 */
 
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
-#include "pam.h"
+
 #include "mallocvar.h"
+#include "shhopt.h"
+#include "pam.h"
+
+
+
+struct CmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileNm;
+    unsigned int n;
+};
+
+
+
+static void
+parseCommandLine(int argc, const char ** argv,
+                 struct CmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optStruct3 opt;  /* set by OPTENT3 */
+    optEntry * option_def;
+    unsigned int option_def_index;
+
+    unsigned int nSpec;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0,   "n", OPT_UINT, &cmdlineP->n, &nSpec, 0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = false;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = false;  /* We have no parms that are negative numbers */
+
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    free(option_def);
+
+    if (!nSpec)
+        cmdlineP->n = 3;
+
+    if (argc-1 < 1)
+        cmdlineP->inputFileNm = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFileNm = argv[1];
+    else
+        pm_error("You specified too many arguments (%u).  The only "
+                 "possible argument is the optional input file specification.",
+                 argc-1);
+}
+
+
+
+static void
+computeRowHist(struct pam   const inpam,
+               tuple **     const tuples,
+               unsigned int const smearFactor,
+               unsigned int const plane,
+               unsigned int const row,
+               unsigned int const col,
+               sample *     const hist) {
+/*----------------------------------------------------------------------------
+  Compute hist[] - frequencies, in the neighborhood of row 'row', column
+  'col', in plane 'plane', of each sample value
+-----------------------------------------------------------------------------*/
+    sample i;
+    int drow;
+
+    for (i = 0; i <= inpam.maxval; ++i)
+        hist[i] = 0;
+
+    for (drow = row - smearFactor; drow <= row + smearFactor; ++drow) {
+        if (drow >= 0 && drow < inpam.height) {
+            int dcol;
+
+            for (dcol = col - smearFactor;
+                 dcol <= col + smearFactor;
+                 ++dcol) {
+                if (dcol >= 0 && dcol < inpam.width)
+                    ++hist[tuples[drow][dcol][plane]];
+            }
+        }
+    }
+}
+
+
+
+static sample
+modalValue(sample * const hist,
+           sample   const maxval) {
+/*----------------------------------------------------------------------------
+  The sample value that occurs most often according to histogram hist[].
+-----------------------------------------------------------------------------*/
+    sample modalval;
+    unsigned int maxfreq;
+    sample sampleval;
+
+    for (sampleval = 0, maxfreq = 0, modalval = 0;
+         sampleval <= maxval;
+         ++sampleval) {
+
+        if (hist[sampleval] > maxfreq) {
+            maxfreq = hist[sampleval];
+            modalval = sampleval;
+        }
+    }
+    return modalval;
+}
+
+
 
 static void
-convertRow(struct pam const inpam, tuple ** const tuples,
-           tuple * const tuplerow, int const row, int const smearFactor,
-           int * const hist) {
+convertRow(struct pam     const inpam,
+           tuple **       const tuples,
+           tuple *        const tuplerow,
+           unsigned int   const row,
+           unsigned int   const smearFactor,
+           sample *       const hist) {
+/*----------------------------------------------------------------------------
+   'hist' is a working buffer inpam.width wide.
+-----------------------------------------------------------------------------*/
+    unsigned int plane;
+
+    for (plane = 0; plane < inpam.depth; plane++) {
+        unsigned int col;
 
-    int sample;
-    for (sample = 0; sample < inpam.depth; sample++) {
-        int col;
         for (col = 0; col < inpam.width; ++col)  {
-            int i;
-            int drow;
-            int modalval;
+            sample modalval;
                 /* The sample value that occurs most often in the neighborhood
-                   of the pixel being examined
+                   of column 'col' of row 'row', in plane 'plane'.
                 */
 
-            /* Compute hist[] - frequencies, in the neighborhood, of each
-               sample value
-            */
-            for (i = 0; i <= inpam.maxval; ++i) hist[i] = 0;
-
-            for (drow = row - smearFactor; drow <= row + smearFactor; ++drow) {
-                if (drow >= 0 && drow < inpam.height) {
-                    int dcol;
-                    for (dcol = col - smearFactor;
-                         dcol <= col + smearFactor;
-                         ++dcol) {
-                        if ( dcol >= 0 && dcol < inpam.width )
-                            ++hist[tuples[drow][dcol][sample]];
-                    }
-                }
-            }
-            {
-                /* Compute modalval */
-                int sampleval;
-                int maxfreq;
-
-                maxfreq = 0;
-                modalval = 0;
-
-                for (sampleval = 0; sampleval <= inpam.maxval; ++sampleval) {
-                    if (hist[sampleval] > maxfreq) {
-                        maxfreq = hist[sampleval];
-                        modalval = sampleval;
-                    }
-                }
-            }
-            tuplerow[col][sample] = modalval;
+            computeRowHist(inpam, tuples, smearFactor, plane, row, col, hist);
+
+            modalval = modalValue(hist, inpam.maxval);
+
+            tuplerow[col][plane] = modalval;
         }
     }
 }
@@ -73,44 +166,24 @@ convertRow(struct pam const inpam, tuple ** const tuples,
 
 
 int
-main(int argc, char *argv[] ) {
+main(int argc, const char ** argv) {
+
     struct pam inpam, outpam;
-    FILE* ifp;
-    tuple ** tuples;
-    tuple * tuplerow;
-    int * hist;
+    FILE * ifP;
+    tuple ** tuples;  /* malloc'ed */
+    tuple * tuplerow;  /* malloc'ed */
+    sample * hist;  /* malloc'ed */
         /* A buffer for the convertRow subroutine to use */
-    int argn;
     int row;
-    int smearFactor;
-    const char* const usage = "[-n <n>] [ppmfile]";
-
-    ppm_init( &argc, argv );
-
-    argn = 1;
-    smearFactor = 3;       /* DEFAULT VALUE */
-
-    /* Check for options. */
-    if ( argn < argc && argv[argn][0] == '-' ) {
-        if ( argv[argn][1] == 'n' ) {
-            ++argn;
-            if ( argn == argc || sscanf(argv[argn], "%d", &smearFactor) != 1 )
-                pm_usage( usage );
-        } else
-            pm_usage( usage );
-        ++argn;
-    }
-    if ( argn < argc ) {
-        ifp = pm_openr( argv[argn] );
-        ++argn;
-    } else
-        ifp = stdin;
+    struct CmdlineInfo cmdline;
+
+    pm_proginit(&argc, argv);
 
-    if ( argn != argc )
-        pm_usage( usage );
+    parseCommandLine(argc, argv, &cmdline);
 
-    tuples = pnm_readpam(ifp, &inpam, PAM_STRUCT_SIZE(tuple_type));
-    pm_close(ifp);
+    ifP = pm_openr(cmdline.inputFileNm);
+
+    tuples = pnm_readpam(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
 
     MALLOCARRAY(hist, inpam.maxval + 1);
     if (hist == NULL)
@@ -123,7 +196,7 @@ main(int argc, char *argv[] ) {
     tuplerow = pnm_allocpamrow(&inpam);
 
     for (row = 0; row < inpam.height; ++row) {
-        convertRow(inpam, tuples, tuplerow, row, smearFactor, hist);
+        convertRow(inpam, tuples, tuplerow, row, cmdline.n, hist);
         pnm_writepamrow(&outpam, tuplerow);
     }
 
@@ -131,7 +204,9 @@ main(int argc, char *argv[] ) {
     free(hist);
     pnm_freepamarray(tuples, &inpam);
 
+    pm_close(ifP);
     pm_close(stdout);
-    exit(0);
+    return 0;
 }
 
+
diff --git a/editor/specialty/pnmindex.c b/editor/specialty/pnmindex.c
index dcb183ef..0214ee8b 100644
--- a/editor/specialty/pnmindex.c
+++ b/editor/specialty/pnmindex.c
@@ -2,7 +2,7 @@
                                 pnmindex
 ==============================================================================
 
-  build a visual index of a bunch of PNM images
+  Build a visual index of a bunch of PNM images.
 
   This used to be a C shell program, and then a BASH program.  Neither
   were portable enough, and the program is too complex for either of
@@ -32,6 +32,7 @@
 #include "shhopt.h"
 #include "mallocvar.h"
 #include "nstring.h"
+#include "pm_system.h"
 #include "pnm.h"
 
 struct CmdlineInfo {
@@ -53,57 +54,6 @@ static bool verbose;
 
 
 
-static void PM_GNU_PRINTF_ATTR(1,2)
-systemf(const char * const fmt,
-        ...) {
-
-    va_list varargs;
-
-    size_t dryRunLen;
-
-    va_start(varargs, fmt);
-
-    dryRunLen = vsnprintf(NULL, 0, fmt, varargs);
-
-    va_end(varargs);
-
-    if (dryRunLen + 1 < dryRunLen)
-        /* arithmetic overflow */
-        pm_error("Command way too long");
-    else {
-        size_t const allocSize = dryRunLen + 1;
-        char * shellCommand;
-        shellCommand = malloc(allocSize);
-        if (shellCommand == NULL)
-            pm_error("Can't get storage for %u-character command",
-                     (unsigned)allocSize);
-        else {
-            va_list varargs;
-            size_t realLen;
-            int rc;
-
-            va_start(varargs, fmt);
-
-            realLen = vsnprintf(shellCommand, allocSize, fmt, varargs);
-
-            assert(realLen == dryRunLen);
-            va_end(varargs);
-
-            if (verbose)
-                pm_message("shell cmd: %s", shellCommand);
-
-            rc = system(shellCommand);
-            if (rc != 0)
-                pm_error("shell command '%s' failed.  rc %d",
-                         shellCommand, rc);
-
-            pm_strfree(shellCommand);
-        }
-    }
-}
-
-
-
 static const char *
 shellQuote(const char * const arg) {
 /*----------------------------------------------------------------------------
@@ -170,7 +120,7 @@ shellQuote(const char * const arg) {
 
 
 static void
-parseCommandLine(int argc, char ** argv,
+parseCommandLine(int argc, const char ** argv,
                  struct CmdlineInfo * const cmdlineP) {
 
     unsigned int option_def_index;
@@ -206,19 +156,28 @@ parseCommandLine(int argc, char ** argv,
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
     opt.allowNegNum = FALSE;
 
-    pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdline_p and others. */
 
     if (quant && cmdlineP->noquant)
-        pm_error("You can't specify both -quant and -noquat");
+        pm_error("You can't specify both -quant and -noquant");
 
-    if (!colorsSpec)
+    if (colorsSpec) {
+        if (cmdlineP->colors == 0)
+            pm_error("-colors value must be positive");
+    } else
         cmdlineP->colors = 256;
 
-    if (!sizeSpec)
+    if (sizeSpec) {
+        if (cmdlineP->size == 0)
+            pm_error("-size value must be positive");
+    } else
         cmdlineP->size = 100;
 
-    if (!acrossSpec)
+    if (acrossSpec) {
+        if (cmdlineP->across == 0)
+            pm_error("-across value must be positive");
+    } else
         cmdlineP->across = 6;
 
     if (!titleSpec)
@@ -340,85 +299,118 @@ static void
 makeTitle(const char * const title,
           unsigned int const rowNumber,
           bool         const blackBackground,
-          const char * const tempDir) {
+          const char * const dirNm) {
+/*----------------------------------------------------------------------------
+   Create a PBM file containing the text 'title'.
+
+   If 'blackBackground' is true, make it white on black; otherwise, black
+   on white.
 
+   Name the file like a thumbnail row file for row number 'rowNumber',
+   in directory named 'dirNm'.
+-----------------------------------------------------------------------------*/
     const char * const invertStage = blackBackground ? "| pnminvert " : "";
-    const char * const titleToken  = shellQuote(title);
 
     const char * fileName;
+    FILE * outFP;
+    struct bufferDesc titleBuf;
+    const char * shellCommand;
+    int termStatus;
 
-    fileName = rowFileName(tempDir, rowNumber);
-
-    /* This quoting is not adequate.  We really should do this without
-       a shell at all.
-    */
-    systemf("pbmtext %s %s > %s",
-            titleToken, invertStage, fileName);
+    titleBuf.size              = strlen(title);
+    titleBuf.buffer            = (unsigned char *)title;
+    titleBuf.bytesTransferredP = NULL;
 
+    fileName = rowFileName(dirNm, rowNumber);
+    outFP = pm_openw(fileName);
     pm_strfree(fileName);
-    pm_strfree(titleToken);
+
+    pm_asprintf(&shellCommand, "pbmtext %s", invertStage);
+
+    pm_system2(&pm_feed_from_memory, &titleBuf,
+               &pm_accept_to_filestream, outFP,
+               shellCommand, &termStatus);
+
+    pm_strfree(shellCommand);
+
+    if (termStatus != 0)
+        pm_error("Failed to generate title image");
+
+    pm_close(outFP);
 }
 
 
 
 static void
-copyImage(const char * const inputFileName,
-          const char * const outputFileName) {
+copyImage(const char * const inputFileNm,
+          const char * const outputFileNm) {
 
-    const char * const inputFileNmToken = shellQuote(inputFileName);
+    int termStatus;
 
-    systemf("cat %s > %s", inputFileNmToken, outputFileName);
+    pm_system2_lp("cat",
+                  &pm_feed_from_file, (void*)inputFileNm,
+                  &pm_accept_to_file, (void*)outputFileNm,
+                  &termStatus,
+                  "cat", NULL);
 
-    pm_strfree(inputFileNmToken);
+    if (termStatus != 0)
+        pm_error("'cat' to copy image '%s' to '%s' failed, "
+                 "termination status = %d",
+                 inputFileNm, outputFileNm, termStatus);
 }
 
 
 
 static void
-copyScaleQuantImage(const char * const inputFileName,
-                    const char * const outputFileName,
+copyScaleQuantImage(const char * const inputFileNm,
+                    const char * const outputFileNm,
                     int          const format,
                     unsigned int const size,
                     unsigned int const quant,
-                    unsigned int const colors) {
-
-    const char * const inputFileNmToken = shellQuote(inputFileName);
+                    unsigned int const colorCt) {
 
     const char * scaleCommand;
+    int termStatus;
 
     switch (PNM_FORMAT_TYPE(format)) {
     case PBM_TYPE:
         pm_asprintf(&scaleCommand,
-                    "pamscale -quiet -xysize %u %u %s "
-                    "| pgmtopbm > %s",
-                    size, size, inputFileNmToken, outputFileName);
+                    "pamscale -quiet -xysize %u %u "
+                    "| pamditherbw",
+                    size, size);
         break;
 
     case PGM_TYPE:
         pm_asprintf(&scaleCommand,
-                    "pamscale -quiet -xysize %u %u %s >%s",
-                    size, size, inputFileNmToken, outputFileName);
+                    "pamscale -quiet -xysize %u %u",
+                    size, size);
         break;
 
     case PPM_TYPE:
         if (quant)
             pm_asprintf(&scaleCommand,
-                        "pamscale -quiet -xysize %u %u %s "
-                        "| pnmquant -quiet %u > %s",
-                        size, size, inputFileNmToken, colors, outputFileName);
+                        "pamscale -quiet -xysize %u %u "
+                        "| pnmquant -quiet %u ",
+                        size, size, colorCt);
         else
             pm_asprintf(&scaleCommand,
-                        "pamscale -quiet -xysize %u %u %s >%s",
-                        size, size, inputFileNmToken, outputFileName);
+                        "pamscale -quiet -xysize %u %u ",
+                        size, size);
         break;
     default:
         pm_error("Unrecognized Netpbm format: %d", format);
     }
 
-    systemf("%s", scaleCommand);
+    pm_system2(pm_feed_from_file, (void*)inputFileNm,
+               pm_accept_to_file, (void*)outputFileNm,
+               scaleCommand,
+               &termStatus);
+
+    if (termStatus != 0)
+        pm_message("Shell command '%s' failed.  Termination status=%d",
+                   scaleCommand, termStatus);
 
     pm_strfree(scaleCommand);
-    pm_strfree(inputFileNmToken);
 }
 
 
@@ -485,10 +477,10 @@ thumbnailFileList(const char * const dirName,
 
 
 static void
-makeImageFile(const char * const thumbnailFileName,
-              const char * const inputFileName,
+makeImageFile(const char * const thumbnailFileNm,
+              const char * const inputFileNm,
               bool         const blackBackground,
-              const char * const outputFileName) {
+              const char * const outputFileNm) {
 /*----------------------------------------------------------------------------
    Create one thumbnail image.  It consists of the image in the file named
    'thumbnailFileName' with text of that name appended to the bottom.
@@ -501,15 +493,34 @@ makeImageFile(const char * const thumbnailFileName,
 -----------------------------------------------------------------------------*/
     const char * const blackWhiteOpt = blackBackground ? "-black" : "-white";
     const char * const invertStage   = blackBackground ? "| pnminvert " : "";
-    const char * inputFileNmToken    = shellQuote(inputFileName);
-
-    systemf("pbmtext %s %s"
-            "| pamcat %s -topbottom %s - "
-            "> %s",
-            inputFileNmToken, invertStage, blackWhiteOpt,
-            thumbnailFileName, outputFileName);
-
-    pm_strfree(inputFileNmToken);
+    const char * const thumbnailFileNmToken = shellQuote(thumbnailFileNm);
+
+    struct bufferDesc fileNmBuf;
+    const char * shellCommand;
+    int termStatus;
+
+    fileNmBuf.size              = strlen(inputFileNm);
+    fileNmBuf.buffer            = (unsigned char *)inputFileNm;
+    fileNmBuf.bytesTransferredP = NULL;
+
+    pm_asprintf(&shellCommand,
+                "pbmtext "
+                "%s"
+                "| pamcat %s -topbottom %s - ",
+                invertStage, blackWhiteOpt, thumbnailFileNmToken);
+
+    pm_system2(&pm_feed_from_memory, &fileNmBuf,
+               &pm_accept_to_file, (void*)outputFileNm,
+               shellCommand,
+               &termStatus);
+
+    if (termStatus != 0)
+        pm_error("Shell command '%s' to add file name to thumbnail image "
+                 "of file '%s' failed, termination Status = %d",
+                 shellCommand, inputFileNm, termStatus);
+
+    pm_strfree(thumbnailFileNmToken);
+    pm_strfree(shellCommand);
 }
 
 
@@ -519,17 +530,18 @@ makeThumbnail(const char *  const inputFileName,
               unsigned int  const size,
               bool          const black,
               bool          const quant,
-              unsigned int  const colors,
+              unsigned int  const colorCt,
               const char *  const tempDir,
               unsigned int  const row,
               unsigned int  const col,
               int *         const formatP) {
 
+    const char * const fileName = thumbnailFileName(tempDir, row, col);
+
     FILE * ifP;
     int imageCols, imageRows, format;
     xelval maxval;
     const char * tmpfile;
-    const char * fileName;
 
     ifP = pm_openr(inputFileName);
     pnm_readpnminit(ifP, &imageCols, &imageRows, &maxval, &format);
@@ -541,9 +553,7 @@ makeThumbnail(const char *  const inputFileName,
         copyImage(inputFileName, tmpfile);
     else
         copyScaleQuantImage(inputFileName, tmpfile, format,
-                            size, quant, colors);
-
-    fileName = thumbnailFileName(tempDir, row, col);
+                            size, quant, colorCt);
 
     makeImageFile(tmpfile, inputFileName, black, fileName);
 
@@ -594,40 +604,66 @@ unlinkRowFiles(const char * const dirName,
 
 static void
 combineIntoRowAndDelete(unsigned int const row,
-                        unsigned int const cols,
+                        unsigned int const colCt,
                         int          const maxFormatType,
                         bool         const blackBackground,
                         bool         const quant,
-                        unsigned int const colors,
+                        unsigned int const colorCt,
                         const char * const tempDir) {
+/*----------------------------------------------------------------------------
+   Combine the 'colCt' thumbnails for row 'row' into a PNM file in directory
+   'tempDir'.
+
+   Each thumbnail is from a specially named file in 'tempDir' whose name
+   indicates its position in the row, and the output is also specially named
+   with a name indicating it is row 'row'.
+
+   Where the thumnails are different heights, pad with black if
+   'blackBackground' is true; white otherwise.
 
+   If 'quant', color-quantize the result to have no more than 'colorCt'
+   colors, choosing the colors that best represent all the pixels in the
+   result.
+
+   'maxFormatType' is the most expressive format of all the thumbnail files;
+   results are undefined if it is not.
+-----------------------------------------------------------------------------*/
     const char * const blackWhiteOpt = blackBackground ? "-black" : "-white";
+    const char * const fileNm        = rowFileName(tempDir, row);
 
-    const char * fileName;
     const char * quantStage;
     const char * fileList;
+    const char * shellCommand;
+    int termStatus;
 
-    fileName = rowFileName(tempDir, row);
-
-    unlink(fileName);
+    unlink(fileNm);
 
     if (maxFormatType == PPM_TYPE && quant)
-        pm_asprintf(&quantStage, "| pnmquant -quiet %u ", colors);
+        pm_asprintf(&quantStage, "| pnmquant -quiet %u ", colorCt);
     else
         quantStage = strdup("");
 
-    fileList = thumbnailFileList(tempDir, row, cols);
+    fileList = thumbnailFileList(tempDir, row, colCt);
+
+    pm_asprintf(&shellCommand, "pamcat %s -leftright -jbottom %s "
+                "%s",
+                blackWhiteOpt, fileList, quantStage);
 
-    systemf("pamcat %s -leftright -jbottom %s "
-            "%s"
-            ">%s",
-            blackWhiteOpt, fileList, quantStage, fileName);
+    pm_system2(&pm_feed_null, NULL,
+               &pm_accept_to_file, (void*)fileNm,
+               shellCommand,
+               &termStatus);
+
+    if (termStatus != 0)
+        pm_error("Shell command '%s' to create row of thumbnails failed.  "
+                 "Termination status = %d", shellCommand, termStatus);
 
     pm_strfree(fileList);
     pm_strfree(quantStage);
-    pm_strfree(fileName);
+    pm_strfree(fileNm);
+    pm_strfree(shellCommand);
 
-    unlinkThumbnailFiles(tempDir, row, cols);
+    unlinkThumbnailFiles(tempDir, row, colCt);
 }
 
 
@@ -666,38 +702,70 @@ rowFileList(const char * const dirName,
 
 
 static void
-writeRowsAndDelete(unsigned int const rows,
+writeRowsAndDelete(unsigned int const rowCt,
                    int          const maxFormatType,
                    bool         const blackBackground,
                    bool         const quant,
-                   unsigned int const colors,
+                   unsigned int const colorCt,
                    const char * const tempDir) {
+/*----------------------------------------------------------------------------
+   Write the PNM image containing the 'rowCt' rows of thumbnails to Standard
+   Output.  Take each row of thumbnails from a specially named PNM file in
+   directory 'tempDir', and unlink it from that directory.
+
+   Where the rows are different widths, pad with black if 'blackBackground'
+   is true; white otherwise.
+
+   If 'quant', color-quantize the result to have no more than 'colorCt'
+   colors, choosing the colors that best represent all the pixels in the
+   result.
 
+   'maxFormatType' is the most expressive format of all the row files; results
+   are undefined if it is not.
+-----------------------------------------------------------------------------*/
     const char * const blackWhiteOpt = blackBackground ? "-black" : "-white";
+    const char * const plainOpt      = pm_plain_output ? "-plain" : "";
 
     const char * quantStage;
     const char * fileList;
+    const char * shellCommand;
+    int termStatus;
 
     if (maxFormatType == PPM_TYPE && quant)
-        pm_asprintf(&quantStage, "| pnmquant -quiet %u ", colors);
+        pm_asprintf(&quantStage, "| pnmquant -quiet %u ", colorCt);
     else
         quantStage = strdup("");
 
-    fileList = rowFileList(tempDir, rows);
+    fileList = rowFileList(tempDir, rowCt);
+
+    pm_asprintf(&shellCommand, "pamcat %s %s -topbottom %s %s",
+                plainOpt, blackWhiteOpt, fileList, quantStage);
+
+    /* Do pamcat/pnmquant command with no Standard Input and writing to
+       our Standard Output
+    */
+    pm_system2(&pm_feed_null, NULL,
+               NULL, NULL,
+               shellCommand,
+               &termStatus);
 
-    systemf("pamcat %s -topbottom %s %s",
-            blackWhiteOpt, fileList, quantStage);
+    if (termStatus != 0)
+        pm_error("Shell command '%s' to assemble %u rows of thumbnails and "
+                 "write them out failed; termination status = %d",
+                 shellCommand, rowCt, termStatus);
 
+    pm_strfree(shellCommand);
     pm_strfree(fileList);
     pm_strfree(quantStage);
 
-    unlinkRowFiles(tempDir, rows);
+    unlinkRowFiles(tempDir, rowCt);
 }
 
 
 
 int
-main(int argc, char *argv[]) {
+main(int argc, const char ** argv) {
+
     struct CmdlineInfo cmdline;
     const char * tempDir;
     int maxFormatType;
@@ -705,7 +773,7 @@ main(int argc, char *argv[]) {
     unsigned int rowsDone;
     unsigned int i;
 
-    pnm_init(&argc, argv);
+    pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
 
@@ -713,14 +781,15 @@ main(int argc, char *argv[]) {
 
     makeTempDir(&tempDir);
 
-    maxFormatType = PBM_TYPE;
-    colsInRow = 0;
-    rowsDone = 0;
+    rowsDone = 0;  /* initial value */
 
     if (cmdline.title)
         makeTitle(cmdline.title, rowsDone++, cmdline.black, tempDir);
 
-    for (i = 0; i < cmdline.inputFileCount; ++i) {
+    for (i = 0, colsInRow = 0, maxFormatType = PBM_TYPE;
+         i < cmdline.inputFileCount;
+         ++i) {
+
         const char * const inputFileName = cmdline.inputFileName[i];
 
         int format;
@@ -755,4 +824,3 @@ main(int argc, char *argv[]) {
 }
 
 
-