about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--analyzer/pnmhistmap.c4
-rw-r--r--buildtools/README.pkg10
-rw-r--r--buildtools/debian/README6
-rwxr-xr-xconverter/other/anytopnm2
-rw-r--r--converter/other/giftopnm.c35
-rw-r--r--converter/other/pngtopam.c14
-rw-r--r--converter/other/pnmtopalm/pnmtopalm.c6
-rw-r--r--converter/other/pnmtops.c16
-rw-r--r--converter/pgm/fstopgm.c235
-rw-r--r--converter/ppm/ilbmtoppm.c293
-rw-r--r--converter/ppm/picttoppm.c32
-rw-r--r--converter/ppm/ppmtoarbtxt.c1525
-rw-r--r--converter/ppm/ppmtobmp.c14
-rw-r--r--doc/HISTORY34
-rw-r--r--editor/pnmnorm.c70
-rw-r--r--generator/pamcrater.c192
-rw-r--r--generator/ppmrough.c11
-rw-r--r--lib/libpbm2.c10
-rw-r--r--lib/libpgm1.c3
-rw-r--r--lib/libpm.c13
-rw-r--r--lib/libpnm1.c3
-rw-r--r--lib/pm.h5
-rw-r--r--lib/util/matrix.c12
-rw-r--r--lib/util/nstring.c93
-rw-r--r--lib/util/nstring.h3
-rw-r--r--lib/util/vasprintf.c19
-rw-r--r--pm_config.in.h2
-rw-r--r--test/Test-Order2
-rwxr-xr-xtest/gif-roundtrip.test5
-rw-r--r--test/pambackground.ok3
-rwxr-xr-xtest/pambackground.test42
-rwxr-xr-xtest/pamcrater.test38
-rw-r--r--test/pamenlarge.ok2
-rwxr-xr-xtest/pamenlarge.test9
-rwxr-xr-xtest/png-roundtrip.test2
-rwxr-xr-xtest/ppmchange-roundtrip.test2
-rw-r--r--test/ppmchange.ok4
-rwxr-xr-xtest/ppmchange.test63
-rwxr-xr-xtest/ppmmix.test9
-rwxr-xr-xtest/symmetry.test3
-rw-r--r--version.mk4
41 files changed, 1955 insertions, 895 deletions
diff --git a/analyzer/pnmhistmap.c b/analyzer/pnmhistmap.c
index f41b0d51..fc1bbbde 100644
--- a/analyzer/pnmhistmap.c
+++ b/analyzer/pnmhistmap.c
@@ -426,7 +426,7 @@ ppmHist(FILE *       const ifP,
     clipHistogramAll(hist, histWidth, hmax);
 
     vscale = (double) histHeight / hmax;
-    if (verbose)
+    if (verbose && pm_have_float_format())
         pm_message("Done: height = %u, vertical scale factor = %g", 
                    hmax, vscale);
 
@@ -477,7 +477,7 @@ reportScale(unsigned int const histWidth,
 
     double const hscale = (float)(histWidth-1) / (range-1);
 
-    if (hscale - 1.0 < epsilon && verbose)
+    if (hscale - 1.0 < epsilon && verbose && pm_have_float_format())
         pm_message("Horizontal scale factor: %g", hscale);
 }
 
diff --git a/buildtools/README.pkg b/buildtools/README.pkg
index f098e04b..6761ecf3 100644
--- a/buildtools/README.pkg
+++ b/buildtools/README.pkg
@@ -145,11 +145,11 @@ entire installation when you don't want it anymore, and to keep
 multiple versions around.
 
 
-netpbm.pkgconfig
-----------------
+netpbm.pc
+---------
 
-You should create a file named 'netpbm.pkgconfig' out of the template file
-'pkgconfig_template' in the package directory, and install netpbm.pkgconfig in
+You should create a file named 'netpbm.pc' out of the template file
+'pkgconfig_template' in the package directory, and install netpbm.pc in
 your pkg-config directory (typically /usr/lib/pkgconfig).  Programs that want
 to find out where you installed some part of Netpbm can use a 'pkg-config
 netpbm ...' command to find out.  For example, a make file for a program that
@@ -175,4 +175,4 @@ Using netpbm-config, it's possible to have a viable Netpbm installation where
 netpbm.config is the only file in any default search path.
 
 The xxx-config concept (in general, not just Netpbm) has largely been replaced
-by the pkg-config concept (see netpbm.pkgconfig above).
+by the pkg-config concept (see netpbm.pc above).
diff --git a/buildtools/debian/README b/buildtools/debian/README
index 1c600c6b..02fae4b5 100644
--- a/buildtools/debian/README
+++ b/buildtools/debian/README
@@ -30,15 +30,15 @@ To install Netpbm as a Debian package:
 
   3) $ dpkg --install netpbm-sfXXXX.deb
 
-     (netpbm-sfXXXX.deb is the file created by 'makedeb', in the current
+     (netpbm-sfXXXX.deb is the file created by 'make deb', in the current
      directory).
 
 
 PREREQUSISITES
 --------------
 
-The following information was taken from the Wheezy version of Debian, in
-January 2014.
+The following information was taken from the Wheezy version (Version 7) of
+Debian, in January 2014.
 
 You don't actually need the current version of any of these.  For example,
 while we list package libjpeg8-dev, the package libjpeg62-dev works fine.
diff --git a/converter/other/anytopnm b/converter/other/anytopnm
index acf88136..f3a00793 100755
--- a/converter/other/anytopnm
+++ b/converter/other/anytopnm
@@ -379,7 +379,7 @@ case "$2" in
         ;;
 
     gif )
-        giftopnm "$file"
+        giftopnm -image=all "$file"
         ;;
 
     tiff )
diff --git a/converter/other/giftopnm.c b/converter/other/giftopnm.c
index 9a543532..9f4bc8a1 100644
--- a/converter/other/giftopnm.c
+++ b/converter/other/giftopnm.c
@@ -1674,6 +1674,28 @@ readImageData(FILE *       const ifP,
 
 
 static void
+warnUserNotSquare(unsigned int const aspectRatio) {
+
+    const char * baseMsg =
+        "warning - input pixels are not square, "
+        "but we are rendering them as square pixels "
+        "in the output";
+
+    if (pm_have_float_format()) {
+        float const r = ((float)aspectRatio + 15.0 ) / 64.0;
+
+        pm_message("%s.  To fix the output, run it through "
+                   "'pamscale -%cscale %g'",
+                   baseMsg,
+                   r < 1.0 ? 'x' : 'y',
+                   r < 1.0 ? 1.0 / r : r );
+    } else
+        pm_message("%s", baseMsg);
+}
+
+
+
+static void
 readGifHeader(FILE *             const gifFileP,
               struct gifScreen * const gifScreenP) {
 /*----------------------------------------------------------------------------
@@ -1734,17 +1756,8 @@ readGifHeader(FILE *             const gifFileP,
         }
     }
     
-    if (gifScreenP->AspectRatio != 0 && gifScreenP->AspectRatio != 49) {
-        float   r;
-        r = ( (float) gifScreenP->AspectRatio + 15.0 ) / 64.0;
-        pm_message("warning - input pixels are not square, "
-                   "but we are rendering them as square pixels "
-                   "in the output.  "
-                   "To fix the output, run it through "
-                   "'pnmscale -%cscale %g'",
-                   r < 1.0 ? 'x' : 'y',
-                   r < 1.0 ? 1.0 / r : r );
-    }
+    if (gifScreenP->AspectRatio != 0 && gifScreenP->AspectRatio != 49)
+        warnUserNotSquare(gifScreenP->AspectRatio);
 }
 
 
diff --git a/converter/other/pngtopam.c b/converter/other/pngtopam.c
index e6e68587..59b29f5f 100644
--- a/converter/other/pngtopam.c
+++ b/converter/other/pngtopam.c
@@ -1243,10 +1243,16 @@ warnNonsquarePixels(struct pngx * const pngxP,
             (float)pngx_xPixelsPerMeter(pngxP) / pngx_yPixelsPerMeter(pngxP);
 
         if (r != 1.0) {
-            pm_message ("warning - non-square pixels; "
-                        "to fix do a 'pamscale -%cscale %g'",
-                        r < 1.0 ? 'x' : 'y',
-                        r < 1.0 ? 1.0 / r : r );
+            const char * const baseMsg = "warning - non-square pixels";
+
+            if (pm_have_float_format())
+                pm_message("%s; to fix do a 'pamscale -%cscale %g'",
+                           baseMsg,
+                           r < 1.0 ? 'x' : 'y',
+                           r < 1.0 ? 1.0 / r : r);
+            else
+                pm_message("%s", baseMsg);
+
             *errorLevelP = PNMTOPNG_WARNING_LEVEL;
         }
     }
diff --git a/converter/other/pnmtopalm/pnmtopalm.c b/converter/other/pnmtopalm/pnmtopalm.c
index 67a17e54..7740a3ac 100644
--- a/converter/other/pnmtopalm/pnmtopalm.c
+++ b/converter/other/pnmtopalm/pnmtopalm.c
@@ -33,8 +33,8 @@ struct cmdline_info {
     /* 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 */
-    char *transparent;          /* -transparent value.  Null if unspec */
+    const char * inputFilespec;  /* Filespecs of input files */
+    const char * transparent;    /* -transparent value.  Null if unspec */
     unsigned int depth;         /* -depth value.  0 if unspec */
     unsigned int maxdepth;      /* -maxdepth value.  0 if unspec */
     enum compressionType compression;
@@ -289,7 +289,7 @@ formatName(int const format) {
         
 
 static void
-findTransparentColor(char *         const colorSpec, 
+findTransparentColor(const char *   const colorSpec, 
                      pixval         const newMaxval,
                      bool           const directColor, 
                      pixval         const maxval, 
diff --git a/converter/other/pnmtops.c b/converter/other/pnmtops.c
index 316b7626..cf6b2873 100644
--- a/converter/other/pnmtops.c
+++ b/converter/other/pnmtops.c
@@ -1125,6 +1125,19 @@ validateComputableBoundingBox(float const scols,
 
 
 static void
+warnUserRescaling(float const scale) {
+
+    const char * const baseMsg = "warning, image too large for page";
+
+    if (pm_have_float_format())
+        pm_message("%s; rescaling to %g", baseMsg, scale);
+    else
+        pm_message("%s; rescaling", baseMsg);
+}
+
+
+
+static void
 computeImagePosition(int     const dpiX, 
                      int     const dpiY, 
                      int     const icols, 
@@ -1231,8 +1244,7 @@ computeImagePosition(int     const dpiX,
         *srowsP = scale * rows * pixfacY;
     
         if (scale != requestedScale)
-            pm_message("warning, image too large for page, rescaling to %g", 
-                       scale );
+            warnUserRescaling(scale);
 
         /* Before May 2001, Pnmtops enforced a 5% margin around the page.
            If the image would be too big to leave a 5% margin, Pnmtops would
diff --git a/converter/pgm/fstopgm.c b/converter/pgm/fstopgm.c
index 2c636f41..1f574604 100644
--- a/converter/pgm/fstopgm.c
+++ b/converter/pgm/fstopgm.c
@@ -12,127 +12,144 @@
 
 #include <string.h>
 
+#include "pm.h"
 #include "pgm.h"
 
-static int gethexit ARGS(( FILE* ifp ));
+
+
+static int
+gethexit(FILE * const ifP) {
+
+    for ( ; ; ) {
+        unsigned int const i = getc(ifP);
+
+        if (i == EOF)
+            pm_error("EOF / read error");
+        else {
+            char const c = (char) i;
+            if (c >= '0' && c <= '9')
+                return c - '0';
+            else if (c >= 'A' && c <= 'F')
+                return c - 'A' + 10;
+            else if (c >= 'a' && c <= 'f')
+                return c - 'a' + 10;
+            else {
+                /* Ignore - whitespace. */
+            }
+        }
+    }
+}
+
+
+
+static void
+warnNonsquarePixels(unsigned int const cols,
+                    unsigned int const xcols,
+                    unsigned int const rows,
+                    unsigned int const xrows) {
+    
+    const char * const baseMsg = "warning, non-square pixels";
+
+    if (pm_have_float_format()) {
+        float const rowratio = (float) xrows / (float) rows;
+        float const colratio = (float) xcols / (float) cols;
+
+        pm_message("%s; to fix do a 'pamscale -%cscale %g'",
+                   baseMsg,
+                   rowratio > colratio ? 'y' : 'x',
+                   rowratio > colratio ? 
+                   rowratio / colratio : colratio / rowratio);
+    } else
+        pm_message("%s", baseMsg);
+}
+
+
 
 int
-main( argc, argv )
-int argc;
-char *argv[];
-    {
-    FILE *ifp;
-    register gray **grays, *gP;
-    int argn, row;
-    register int col;
-    int maxval;
-    int rows = 0, cols = 0, depth = 0, xrows = 0, xcols = 0, xdepth = 0;
+main(int argc, const char ** argv) {
+
+    FILE * ifP;
+    gray ** grays;
+    int argn;
+    int row;
+    gray maxval;
+    int rows, cols, depth;
+    int xrows, xcols, xdepth;
 #define STRSIZE 1000
-    char buf[STRSIZE], firstname[STRSIZE], lastname[STRSIZE], email[STRSIZE];
 
+    pm_proginit(&argc, argv);
 
-    pgm_init( &argc, argv );
+    rows = 0;
+    cols = 0;
+    depth = 0;
+
+    xrows = 0;
+    xcols = 0;
+    xdepth = 0;
 
     argn = 1;
 
-    if ( argn < argc )
-	{
-	ifp = pm_openr( argv[argn] );
-	argn++;
-	}
-    else
-	ifp = stdin;
+    if (argn < argc) {
+        ifP = pm_openr(argv[argn]);
+        argn++;
+    } else
+        ifP = stdin;
 
-    if ( argn != argc )
-	pm_usage( "[fsfile]" );
+    if (argn != argc)
+        pm_error("Too many arguments.  The only argument is the file name");
 
     /* Read the FaceSaver(tm) header. */
-    for ( ; ; )
-	{
-	if ( fgets( buf, STRSIZE, ifp ) == (char *) 0 )
-	    pm_error( "error reading header" );
-
-	/* Blank line ends header. */
-	if ( strlen( buf ) == 1 )
-	    break;
-
-	if ( sscanf( buf, "FirstName: %[^\n]", firstname ) == 1 )
-	    ;
-	else if ( sscanf( buf, "LastName: %[^\n]", lastname ) == 1 )
-	    ;
-	else if ( sscanf( buf, "E-mail: %[^\n]", email ) == 1 )
-	    ;
-	else if ( sscanf( buf, "PicData: %d %d %d\n",
-			  &cols, &rows, &depth ) == 3 )
-	    {
-	    if ( depth != 8 )
-		pm_error(
-		    "can't handle 'PicData' depth other than 8" );
-	    }
-	else if ( sscanf( buf, "Image: %d %d %d\n",
-			  &xcols, &xrows, &xdepth ) == 3 )
-	    {
-	    if ( xdepth != 8 )
-		pm_error(
-		    "can't handle 'Image' depth other than 8" );
-	    }
-	}
-    if ( cols <= 0 || rows <= 0 )
-	pm_error( "invalid header" );
-    maxval = pm_bitstomaxval( depth );
-    if ( maxval > PGM_OVERALLMAXVAL )
-	pm_error( "depth %d is too large.  Our maximum is %d",
-              maxval, PGM_OVERALLMAXVAL);
-    if ( xcols != 0 && xrows != 0 && ( xcols != cols || xrows != rows ) )
-	{
-	float rowratio, colratio;
-
-	rowratio = (float) xrows / (float) rows;
-	colratio = (float) xcols / (float) cols;
-	pm_message(
-	    "warning, non-square pixels; to fix do a 'pamscale -%cscale %g'",
-	    rowratio > colratio ? 'y' : 'x',
-	    rowratio > colratio ? rowratio / colratio : colratio / rowratio );
-	}
-
-    /* Now read the hex bits. */
-    grays = pgm_allocarray( cols, rows );
-    for ( row = rows - 1; row >= 0; row--)
-	{
-	for ( col = 0, gP = grays[row]; col < cols; col++, gP++ )
-	    {
-	    *gP = gethexit( ifp ) << 4;
-	    *gP += gethexit( ifp );
-	    }
-	}
-    pm_close( ifp );
-
-    /* And write out the graymap. */
-    pgm_writepgm( stdout, grays, cols, rows, (gray) maxval, 0 );
-    pm_close( stdout );
-
-    exit( 0 );
+    for ( ; ; ) {
+        char buf[STRSIZE];
+        char firstname[STRSIZE];
+        char lastname[STRSIZE];
+        char email[STRSIZE];
+
+        char * const rc = fgets(buf, STRSIZE, ifP);
+
+        if (rc  == NULL)
+            pm_error("error reading header");
+
+        /* Blank line ends header. */
+        if (strlen(buf) == 1)
+            break;
+
+        if (sscanf(buf, "FirstName: %[^\n]", firstname) == 1);
+        else if (sscanf(buf, "LastName: %[^\n]", lastname) == 1);
+        else if (sscanf(buf, "E-mail: %[^\n]", email ) == 1);
+        else if (sscanf(buf, "PicData: %d %d %d\n",
+                        &cols, &rows, &depth ) == 3) {
+            if (depth != 8)
+                pm_error("can't handle 'PicData' depth other than 8");
+        } else if (sscanf(buf, "Image: %d %d %d\n",
+                          &xcols, &xrows, &xdepth ) == 3) {
+            if (xdepth != 8)
+                pm_error("can't handle 'Image' depth other than 8");
+        }
     }
-
-static int
-gethexit( ifp )
-FILE *ifp;
-    {
-    register int i;
-    register char c;
-
-    for ( ; ; )
-	{
-	i = getc( ifp );
-	if ( i == EOF )
-	    pm_error( "EOF / read error" );
-	c = (char) i;
-	if ( c >= '0' && c <= '9' )
-	    return c - '0';
-	else if ( c >= 'A' && c <= 'F' )
-	    return c - 'A' + 10;
-	else if ( c >= 'a' && c <= 'f' )
-	    return c - 'a' + 10;
-	/* Else ignore - whitespace. */
-	}
+    if (cols <= 0 || rows <= 0)
+        pm_error("invalid header");
+    maxval = pm_bitstomaxval(depth);
+    if (maxval > PGM_OVERALLMAXVAL)
+        pm_error("depth %d is too large.  Our maximum is %d",
+                 maxval, PGM_OVERALLMAXVAL);
+    if (xcols != 0 && xrows != 0 && (xcols != cols || xrows != rows))
+        warnNonsquarePixels(cols, xcols, rows, xrows);
+
+    /* Read the hex bits. */
+    grays = pgm_allocarray(cols, rows);
+    for (row = rows - 1; row >= 0; --row) {
+        unsigned int col;
+        for (col = 0; col < cols; ++col) {
+            grays[row][col] =  gethexit(ifP) << 4;
+            grays[row][col] += gethexit(ifP);
+        }
     }
+    pm_close(ifP);
+
+    pgm_writepgm(stdout, grays, cols, rows, maxval, 0);
+    pm_close(stdout);
+
+    return 0;
+}
+
diff --git a/converter/ppm/ilbmtoppm.c b/converter/ppm/ilbmtoppm.c
index a898d705..662be0b5 100644
--- a/converter/ppm/ilbmtoppm.c
+++ b/converter/ppm/ilbmtoppm.c
@@ -89,7 +89,7 @@ static unsigned char *ilbmrow;
 static pixel *pixelrow;
 static FILE *maskfile = NULL;
 static bit *maskrow = NULL;
-static short wrotemask = 0;
+static bool wrotemask;
 static IFF_ID typeid;       /* ID_ILBM, ID_RGBN, ID_RGB8 */
 
 static char *transpName = NULL;  /* -transparent option value */
@@ -191,8 +191,8 @@ read_bytes(FILE *          const ifP,
 
 
 static unsigned char
-get_byte(ifp, iffid, counter)
-    FILE* ifp;
+get_byte(ifP, iffid, counter)
+    FILE* ifP;
     IFF_ID iffid;
     long *counter;
 {
@@ -203,9 +203,9 @@ get_byte(ifp, iffid, counter)
             pm_error("insufficient data in %s chunk", ID2string(iffid));
         --(*counter);
     }
-    i = getc(ifp);
+    i = getc(ifP);
     if( i == EOF )
-        readerr(ifp, iffid);
+        readerr(ifP, iffid);
 
     return (unsigned char) i;
 }
@@ -310,7 +310,7 @@ display_chunk(FILE *        const ifP,
 
 
 static void
-read_cmap(FILE *     const ifp,
+read_cmap(FILE *     const ifP,
           IFF_ID     const iffid,
           long       const chunksize,
           ColorMap * const cmap) {
@@ -320,7 +320,7 @@ read_cmap(FILE *     const ifp,
     colors = chunksize / 3;
     if( colors == 0 ) {
         pm_error("warning - empty %s colormap", ID2string(iffid));
-        skip_chunk(ifp, iffid, chunksize);
+        skip_chunk(ifP, iffid, chunksize);
     } else {
         unsigned int i;
         if( cmap->color )               /* prefer CMAP-chunk over CMYK-chunk */
@@ -330,30 +330,30 @@ read_cmap(FILE *     const ifp,
         
         for( i = 0; i < colors; ++i ) {
             int r, g, b;
-            r = get_byte(ifp, iffid, &chunksize);
-            g = get_byte(ifp, iffid, &chunksize);
-            b = get_byte(ifp, iffid, &chunksize);
+            r = get_byte(ifP, iffid, &chunksize);
+            g = get_byte(ifP, iffid, &chunksize);
+            b = get_byte(ifP, iffid, &chunksize);
             PPM_ASSIGN(cmap->color[i], r, g, b);
         }
-        chunk_end(ifp, iffid, chunksize);
+        chunk_end(ifP, iffid, chunksize);
     }
 }
 
 
 
 static void
-read_cmyk(FILE *     const ifp,
+read_cmyk(FILE *     const ifP,
           IFF_ID     const iffid,
           long       const chunksize,
           ColorMap * const cmap) {
 
     if( HAS_COLORMAP(cmap) ) {      /* prefer RGB color map */
-        skip_chunk(ifp, iffid, chunksize);
+        skip_chunk(ifP, iffid, chunksize);
     } else {
         long const colors = chunksize/4;
         if( colors == 0 ) {
             pm_error("warning - empty %s colormap", ID2string(iffid));
-            skip_chunk(ifp, iffid, chunksize);
+            skip_chunk(ifP, iffid, chunksize);
         } else {
             unsigned int i;
             cmap->color = ppm_allocrow(colors);
@@ -361,10 +361,10 @@ read_cmyk(FILE *     const ifp,
             
             for( i = 0; i < colors; ++i ) {
                 int c, m, y, k;
-                c = get_byte(ifp, iffid, &chunksize);
-                m = get_byte(ifp, iffid, &chunksize);
-                y = get_byte(ifp, iffid, &chunksize);
-                k = get_byte(ifp, iffid, &chunksize);
+                c = get_byte(ifP, iffid, &chunksize);
+                m = get_byte(ifP, iffid, &chunksize);
+                y = get_byte(ifP, iffid, &chunksize);
+                k = get_byte(ifP, iffid, &chunksize);
 
                 {
                     pixval const red = 
@@ -380,7 +380,7 @@ read_cmyk(FILE *     const ifp,
                     PPM_ASSIGN(cmap->color[i], red, green, blue);
                 }
             }
-            chunk_end(ifp, iffid, chunksize);
+            chunk_end(ifP, iffid, chunksize);
         }
     }
 }
@@ -388,7 +388,7 @@ read_cmyk(FILE *     const ifp,
 
 
 static void
-read_clut(FILE *        const ifp,
+read_clut(FILE *        const ifP,
           IFF_ID        const iffid,
           unsigned long const chunksize,
           ColorMap *    const cmap) {
@@ -396,19 +396,19 @@ read_clut(FILE *        const ifp,
     if (chunksize != CLUTSize) {
         pm_message("invalid size for %s chunk - skipping it", 
                    ID2string(iffid));
-        skip_chunk(ifp, iffid, chunksize);
+        skip_chunk(ifP, iffid, chunksize);
     } else {
         long type;
         unsigned char * lut;
         unsigned long remainingChunksize;
         unsigned int i;
 
-        type = get_big_long(ifp, iffid, &remainingChunksize);
-        get_big_long(ifp, iffid, &remainingChunksize); /* skip reserved fld */
+        type = get_big_long(ifP, iffid, &remainingChunksize);
+        get_big_long(ifP, iffid, &remainingChunksize); /* skip reserved fld */
 
         MALLOCARRAY_NOFAIL(lut, 256);
         for( i = 0; i < 256; ++i )
-            lut[i] = get_byte(ifp, iffid, &remainingChunksize);
+            lut[i] = get_byte(ifP, iffid, &remainingChunksize);
 
         switch( type ) {
         case CLUT_MONO:
@@ -433,6 +433,27 @@ read_clut(FILE *        const ifp,
 
 
 
+static void
+warnNonsquarePixels(uint8_t const xAspect,
+                    uint8_t const yAspect) {
+
+    if (xAspect != yAspect) {
+        const char * const baseMsg = "warning - non-square pixels";
+
+        if (pm_have_float_format())
+            pm_message("%s; to fix do a 'pamscale -%cscale %g'",
+                       baseMsg,
+                       xAspect > yAspect ? 'x' : 'y',
+                       xAspect > yAspect ? 
+                       (float)xAspect/yAspect : 
+                       (float)yAspect/xAspect);
+        else
+            pm_message("%s", baseMsg);
+    }
+}
+
+
+
 static BitMapHeader *
 read_bmhd(FILE *        const ifP,
           IFF_ID        const iffid,
@@ -440,7 +461,7 @@ read_bmhd(FILE *        const ifP,
 
     BitMapHeader * bmhdP;
 
-    if( chunksize != BitMapHeaderSize ) {
+    if (chunksize != BitMapHeaderSize) {
         pm_message("invalid size for %s chunk - skipping it", 
                    ID2string(iffid));
         skip_chunk(ifP, iffid, chunksize);
@@ -467,24 +488,24 @@ read_bmhd(FILE *        const ifP,
         bmhdP->pageWidth  = get_big_short(ifP, iffid, &remainingChunksize);
         bmhdP->pageHeight = get_big_short(ifP, iffid, &remainingChunksize);
 
-        if( verbose ) {
-            if( typeid == ID_ILBM )
+        if (verbose) {
+            if (typeid == ID_ILBM)
                 pm_message("dimensions: %dx%d, %d planes", 
                            bmhdP->w, bmhdP->h, bmhdP->nPlanes);
             else
                 pm_message("dimensions: %dx%d", bmhdP->w, bmhdP->h);
 
-            if( typeid == ID_ILBM || typeid == ID_PBM ) {
+            if (typeid == ID_ILBM || typeid == ID_PBM) {
                 pm_message("compression: %s",
                            bmhdP->compression <= cmpMAXKNOWN ?
                            cmpNAME[bmhdP->compression] : "unknown");
 
-                switch( bmhdP->masking ) {
+                switch(bmhdP->masking) {
                 case mskNone:
                     break;
                 case mskHasMask:
                 case mskHasTransparentColor:
-                    if( !maskfile )
+                    if (!maskfile)
                         pm_message("use '-maskfile <filename>' "
                                    "to generate a PBM mask file from %s", 
                                    mskNAME[bmhdP->masking]);
@@ -498,27 +519,20 @@ read_bmhd(FILE *        const ifP,
                 }
             }
             else    /* RGBN/RGB8 */
-                if( !maskfile )
+                if (!maskfile)
                     pm_message("use '-maskfile <filename>' "
                                "to generate a PBM mask file "
                                "from genlock bits");
         }
 
         /* fix aspect ratio */
-        if( bmhdP->xAspect == 0 || bmhdP->yAspect == 0 ) {
+        if (bmhdP->xAspect == 0 || bmhdP->yAspect == 0) {
             pm_message("warning - illegal aspect ratio %d:%d, using 1:1", 
                        bmhdP->xAspect, bmhdP->yAspect);
             bmhdP->xAspect = bmhdP->yAspect = 1;
         }
 
-        if( bmhdP->xAspect != bmhdP->yAspect ) {
-            pm_message("warning - non-square pixels; "
-                       "to fix do a 'pnmscale -%cscale %g'",
-                       bmhdP->xAspect > bmhdP->yAspect ? 'x' : 'y',
-                       bmhdP->xAspect > bmhdP->yAspect ? 
-                       (float)(bmhdP->xAspect)/bmhdP->yAspect : 
-                       (float)(bmhdP->yAspect)/bmhdP->xAspect);
-        }
+        warnNonsquarePixels(bmhdP->xAspect, bmhdP->yAspect);
     }
     return bmhdP;
 }
@@ -627,42 +641,42 @@ decode_mask(FILE *          const ifP,
     unsigned char *ilp;
 
     cols = bmhdP->w;
-    switch( bmhdP->masking ) {
+    switch (bmhdP->masking) {
     case mskNone:
         break;
     case mskHasMask:        /* mask plane */
         read_ilbm_plane(ifP, remainingChunksizeP, RowBytes(cols), 
                         bmhdP->compression);
-        if( maskfile ) {
+        if (maskfile) {
             ilp = ilbmrow;
             cbit = 7;
-            for( col = 0; col < cols; col++, cbit-- ) {
-                if( cbit < 0 ) {
+            for (col = 0; col < cols; ++col, --cbit) {
+                if (cbit < 0) {
                     cbit = 7;
-                    ilp++;
+                    ++ilp;
                 }
-                if( *ilp & bit_mask[cbit] )
+                if (*ilp & bit_mask[cbit])
                     maskrow[col] = PBM_BLACK;
                 else
                     maskrow[col] = PBM_WHITE;
             }
             pbm_writepbmrow(maskfile, maskrow, cols, 0);
-            wrotemask = 1;
+            wrotemask = true;
         }
         break;
     case mskHasTransparentColor:
-        if( !chunkyrow )
+        if (!chunkyrow)
             pm_error("decode_mask(): chunkyrow == NULL - can't happen");
         
-        if( maskfile ) {
-            for( col = 0; col < cols; col++ ) {
-                if( chunkyrow[col] == bmhdP->transparentColor )
+        if (maskfile) {
+            for (col = 0; col < cols; ++col) {
+                if (chunkyrow[col] == bmhdP->transparentColor)
                     maskrow[col] = PBM_WHITE;
                 else
                     maskrow[col] = PBM_BLACK;
             }
             pbm_writepbmrow(maskfile, maskrow, cols, 0);
-                wrotemask = 1;
+                wrotemask = true;
         }
         break;
     case mskLasso:
@@ -1031,7 +1045,7 @@ get_color(cmap, idx, red, green, blue)
  ****************************************************************************/
 
 static void
-std_to_ppm(FILE *         const ifp, 
+std_to_ppm(FILE *         const ifP, 
            long           const chunksize, 
            BitMapHeader * const bmhdP, 
            ColorMap *     const cmap, 
@@ -1040,7 +1054,7 @@ std_to_ppm(FILE *         const ifp,
 
 
 static void
-ham_to_ppm(FILE *         const ifp, 
+ham_to_ppm(FILE *         const ifP, 
            long           const chunksize, 
            BitMapHeader * const bmhdP, 
            ColorMap *     const cmap, 
@@ -1063,7 +1077,7 @@ ham_to_ppm(FILE *         const ifp,
 
         pm_message("%d-plane HAM?? - interpreting image as a normal ILBM", 
                    bmhdP->nPlanes);
-        std_to_ppm(ifp, chunksize, bmhdP, cmap, assumed_viewportmodes);
+        std_to_ppm(ifP, chunksize, bmhdP, cmap, assumed_viewportmodes);
         return;
     } else {
         unsigned long remainingChunksize;
@@ -1107,8 +1121,8 @@ ham_to_ppm(FILE *         const ifp,
             if( HAS_MULTIPALETTE(cmap) )
                 multi_update(cmap, row);
 
-            decode_row(ifp, &remainingChunksize, rawrow, bmhdP->nPlanes, bmhdP);
-            decode_mask(ifp, &remainingChunksize, rawrow, bmhdP);
+            decode_row(ifP, &remainingChunksize, rawrow, bmhdP->nPlanes, bmhdP);
+            decode_mask(ifP, &remainingChunksize, rawrow, bmhdP);
 
             r = g = b = 0;
             for( col = 0; col < cols; col++ ) {
@@ -1142,7 +1156,7 @@ ham_to_ppm(FILE *         const ifp,
             }
             ppm_writeppmrow(stdout, pixelrow, cols, MAXCOLVAL, 0);
         }
-        chunk_end(ifp, ID_BODY, remainingChunksize);
+        chunk_end(ifP, ID_BODY, remainingChunksize);
     }
 }
 
@@ -1469,46 +1483,54 @@ rgbn_to_ppm(FILE *         const ifP,
     unsigned int const rows = bmhdP->h;
     unsigned int const cols = bmhdP->w;
 
-    int row, col, count, genlock, tries;
-    pixval r, g, b, maxval;
+    unsigned int row;
+    unsigned int count;
+    pixval maxval;
     unsigned long remainingChunksize;
     pixel * transpColorP;
 
     pm_message("input is a %d-bit RGB image", (typeid == ID_RGB8 ? 8 : 4));
 
-    if( bmhdP->compression != 4 )
+    if (bmhdP->compression != 4)
         pm_error("invalid compression mode for %s: %d (must be 4)", 
                  ID2string(typeid), bmhdP->compression);
-
-    switch( typeid ) {
-        case ID_RGBN:
-            if( bmhdP->nPlanes != 13 )
-                pm_error("invalid number of planes for %s: %d (must be 13)", 
-                         ID2string(typeid), bmhdP->nPlanes);
-            maxval = lut_maxval(cmap, 15);
-            break;
-        case ID_RGB8:
-            if( bmhdP->nPlanes != 25 )
-                pm_error("invalid number of planes for %s: %d (must be 25)", 
-                         ID2string(typeid), bmhdP->nPlanes);
-            maxval = 255;
-            break;
-        default:
-            pm_error("rgbn_to_ppm(): invalid IFF ID %s - can't happen", 
-                     ID2string(typeid));
+    
+    switch (typeid) {
+    case ID_RGBN:
+        if (bmhdP->nPlanes != 13)
+            pm_error("invalid number of planes for %s: %d (must be 13)", 
+                     ID2string(typeid), bmhdP->nPlanes);
+        maxval = lut_maxval(cmap, 15);
+        break;
+    case ID_RGB8:
+        if (bmhdP->nPlanes != 25)
+            pm_error("invalid number of planes for %s: %d (must be 25)", 
+                     ID2string(typeid), bmhdP->nPlanes);
+        maxval = 255;
+        break;
+    default:
+        pm_error("rgbn_to_ppm(): invalid IFF ID %s - can't happen", 
+                 ID2string(typeid));
     }
 
     transpColorP = transpColor(bmhdP, cmap, transpName, maxval);
 
     ppm_writeppminit(stdout, cols, rows, maxval, 0);
 
-    remainingChunksize = chunksize;  /* initial value */
-    count = 0;
-    for( row = 0; row < rows; row++ ) {
-        for( col = 0; col < cols; col++ ) {
+    for (row = 0, count = 0, remainingChunksize = chunksize;
+         row < rows;
+         ++row) {
+
+        unsigned int col;
+
+        for (col = 0; col < cols; ++col) {
+            unsigned int tries;
+            unsigned int genlock;
+            pixval r, g, b;
+
             tries = 0;
-            while( !count ) {
-                if( typeid == ID_RGB8 ) {
+            while (count == 0) {
+                if (typeid == ID_RGB8) {
                     r = lookup_red(cmap,   get_byte(ifP, ID_BODY, 
                                                     &remainingChunksize));
                     g = lookup_green(cmap, get_byte(ifP, ID_BODY,
@@ -1518,47 +1540,46 @@ rgbn_to_ppm(FILE *         const ifP,
                     count = get_byte(ifP, ID_BODY, &remainingChunksize);
                     genlock = count & 0x80;
                     count &= 0x7f;
-                }
-                else {
-                    int word;
-                    word = get_big_short(ifP, ID_BODY, &remainingChunksize);
+                } else {
+                    unsigned int const word =
+                        get_big_short(ifP, ID_BODY, &remainingChunksize);
                     r = lookup_red(cmap, (word & 0xf000) >> 12);
                     g = lookup_green(cmap, (word & 0x0f00) >> 8);
                     b = lookup_blue(cmap, (word & 0x00f0) >> 4);
                     genlock = word & 0x0008;
                     count = word & 0x0007;
                 }
-                if( !count ) {
+                if (!count) {
                     count = get_byte(ifP, ID_BODY, &remainingChunksize);
-                    if( !count )
+                    if (count == 0)
                         count =
                             get_big_short(ifP, ID_BODY, &remainingChunksize);
-                        if( !count )
-                            ++tries;
+                    if (count == 0)
+                        ++tries;
                 }
             }
-            if( tries ) {
-                pm_message("warning - repeat count 0 at col %d row %d: "
-                           "skipped %d RGB entr%s",
+            if (tries > 0) {
+                pm_message("warning - repeat count 0 at col %u row %u: "
+                           "skipped %u RGB entr%s",
                             col, row, tries, (tries == 1 ? "y" : "ies"));
             }
-            if( maskfile ) {
+            if (maskfile) {
                 /* genlock bit set -> transparent */
-                if( genlock )
+                if (genlock)
                     maskrow[col] = PBM_WHITE;
                 else
                     maskrow[col] = PBM_BLACK;
             }
-            if( transpColorP && maskrow && maskrow[col] == PBM_WHITE )
+            if (transpColorP && maskrow && maskrow[col] == PBM_WHITE)
                 pixelrow[col] = *transpColorP;
             else
                 PPM_ASSIGN(pixelrow[col], r, g, b);
             --count;
         }
         ppm_writeppmrow(stdout, pixelrow, cols, maxval, 0);
-        if( maskfile ) {
+        if (maskfile) {
             pbm_writepbmrow(maskfile, maskrow, cols, 0);
-            wrotemask = 1;
+            wrotemask = true;
         }
     }
     chunk_end(ifP, ID_BODY, remainingChunksize);
@@ -1947,13 +1968,13 @@ PCHG_ConvertBig(PCHGHeader *    const PCHG,
 
 
 static void
-read_pchg(FILE *     const ifp,
+read_pchg(FILE *     const ifP,
           IFF_ID     const iffid,
           long       const chunksize,
           ColorMap * const cmap) {
 
     if( cmap->mp_type >= MP_TYPE_PCHG ) {
-        skip_chunk(ifp, iffid, chunksize);
+        skip_chunk(ifP, iffid, chunksize);
     } else {
         PCHGHeader      PCHG;
         unsigned char   *data;
@@ -1967,15 +1988,15 @@ read_pchg(FILE *     const ifp,
 
         remainingChunksize = chunksize;  /* initial value */
 
-        PCHG.Compression = get_big_short(ifp, iffid, &remainingChunksize);
-        PCHG.Flags       = get_big_short(ifp, iffid, &remainingChunksize);
-        PCHG.StartLine   = get_big_short(ifp, iffid, &remainingChunksize);
-        PCHG.LineCount   = get_big_short(ifp, iffid, &remainingChunksize);
-        PCHG.ChangedLines= get_big_short(ifp, iffid, &remainingChunksize);
-        PCHG.MinReg      = get_big_short(ifp, iffid, &remainingChunksize);
-        PCHG.MaxReg      = get_big_short(ifp, iffid, &remainingChunksize);
-        PCHG.MaxChanges  = get_big_short(ifp, iffid, &remainingChunksize);
-        PCHG.TotalChanges= get_big_long(ifp, iffid, &remainingChunksize);
+        PCHG.Compression = get_big_short(ifP, iffid, &remainingChunksize);
+        PCHG.Flags       = get_big_short(ifP, iffid, &remainingChunksize);
+        PCHG.StartLine   = get_big_short(ifP, iffid, &remainingChunksize);
+        PCHG.LineCount   = get_big_short(ifP, iffid, &remainingChunksize);
+        PCHG.ChangedLines= get_big_short(ifP, iffid, &remainingChunksize);
+        PCHG.MinReg      = get_big_short(ifP, iffid, &remainingChunksize);
+        PCHG.MaxReg      = get_big_short(ifP, iffid, &remainingChunksize);
+        PCHG.MaxChanges  = get_big_short(ifP, iffid, &remainingChunksize);
+        PCHG.TotalChanges= get_big_long(ifP, iffid, &remainingChunksize);
 
 #ifdef DEBUG
         pm_message("PCHG StartLine   : %d", PCHG.StartLine);
@@ -1990,17 +2011,17 @@ read_pchg(FILE *     const ifp,
             long treesize, compsize;
 
             CompHdr.CompInfoSize     =
-                get_big_long(ifp, iffid, &remainingChunksize);
+                get_big_long(ifP, iffid, &remainingChunksize);
             CompHdr.OriginalDataSize =
-                get_big_long(ifp, iffid, &remainingChunksize);
+                get_big_long(ifP, iffid, &remainingChunksize);
 
             treesize = CompHdr.CompInfoSize;
             MALLOCARRAY_NOFAIL(comptree, treesize);
-            read_bytes(ifp, treesize, comptree, iffid, &remainingChunksize);
+            read_bytes(ifP, treesize, comptree, iffid, &remainingChunksize);
 
             compsize = remainingChunksize;
             MALLOCARRAY_NOFAIL(compdata, compsize);
-            read_bytes(ifp, compsize, compdata, iffid, &remainingChunksize);
+            read_bytes(ifP, compsize, compdata, iffid, &remainingChunksize);
 
             datasize = CompHdr.OriginalDataSize;
             MALLOCARRAY_NOFAIL(data, datasize);
@@ -2015,7 +2036,7 @@ read_pchg(FILE *     const ifp,
 #endif
             datasize = remainingChunksize;
             MALLOCARRAY_NOFAIL(data, datasize);
-            read_bytes(ifp, datasize, data, iffid, &remainingChunksize);
+            read_bytes(ifP, datasize, data, iffid, &remainingChunksize);
         }
 
         if( PCHG.Flags & PCHGF_USE_ALPHA )
@@ -2054,7 +2075,7 @@ read_pchg(FILE *     const ifp,
                          ID2string(iffid));
         }
         free(data);
-        chunk_end(ifp, iffid, remainingChunksize);
+        chunk_end(ifP, iffid, remainingChunksize);
     }
 }
 
@@ -2079,7 +2100,7 @@ ignored_iffid(IFF_ID       const iffid,
 
 
 static void 
-process_body( FILE *          const ifp,
+process_body( FILE *          const ifP,
               long            const chunksize,
               BitMapHeader *  const bmhdP,
               ColorMap *      const cmap,
@@ -2089,19 +2110,19 @@ process_body( FILE *          const ifp,
               DirectColor *   const dcol,
               int *           const viewportmodesP) {
     
-    if( bmhdP == NULL )
+    if (bmhdP == NULL)
         pm_error("%s chunk without %s chunk", 
                  ID2string(ID_BODY), ID2string(ID_BMHD));
 
     prepareCmap(bmhdP, cmap);
 
     pixelrow = ppm_allocrow(bmhdP->w);
-    if( maskfile ) {
+    if (maskfile) {
         maskrow = pbm_allocrow(bmhdP->w);
         pbm_writepbminit(maskfile, bmhdP->w, bmhdP->h, 0);
     }
 
-    if( typeid == ID_ILBM ) {
+    if (typeid == ID_ILBM) {
         int isdeep;
 
         MALLOCARRAY_NOFAIL(ilbmrow, RowBytes(bmhdP->w));
@@ -2115,27 +2136,27 @@ process_body( FILE *          const ifp,
         } else
             isdeep = isdeepopt;
         
-        if( isdeep > 0 )
-            deep_to_ppm(ifp, chunksize, bmhdP, cmap);
-        else if( dcol )
-            dcol_to_ppm(ifp, chunksize, bmhdP, cmap, dcol);
-        else if( bmhdP->nPlanes > 8 ) {
-            if( bmhdP->nPlanes <= 16 && HAS_COLORMAP(cmap) )
-                std_to_ppm(ifp, chunksize, bmhdP, cmap, *viewportmodesP);
-            else if( isdeep >= 0 && (bmhdP->nPlanes % 3 == 0) )
-                deep_to_ppm(ifp, chunksize, bmhdP, cmap);
-            else if( bmhdP->nPlanes <= 16 )   
+        if (isdeep > 0)
+            deep_to_ppm(ifP, chunksize, bmhdP, cmap);
+        else if (dcol)
+            dcol_to_ppm(ifP, chunksize, bmhdP, cmap, dcol);
+        else if (bmhdP->nPlanes > 8) {
+            if (bmhdP->nPlanes <= 16 && HAS_COLORMAP(cmap))
+                std_to_ppm(ifP, chunksize, bmhdP, cmap, *viewportmodesP);
+            else if (isdeep >= 0 && (bmhdP->nPlanes % 3 == 0))
+                deep_to_ppm(ifP, chunksize, bmhdP, cmap);
+            else if (bmhdP->nPlanes <= 16)
                 /* will be interpreted as grayscale */
-                std_to_ppm(ifp, chunksize, bmhdP, cmap, *viewportmodesP);
+                std_to_ppm(ifP, chunksize, bmhdP, cmap, *viewportmodesP);
             else
                 pm_error("don't know how to interpret %d-plane image", 
                          bmhdP->nPlanes);
         } else
-            std_to_ppm(ifp, chunksize, bmhdP, cmap, *viewportmodesP);
+            std_to_ppm(ifP, chunksize, bmhdP, cmap, *viewportmodesP);
     } else if( typeid == ID_PBM )
-        ipbm_to_ppm(ifp, chunksize, bmhdP, cmap, *viewportmodesP);
+        ipbm_to_ppm(ifP, chunksize, bmhdP, cmap, *viewportmodesP);
     else   /* RGBN or RGB8 */
-        rgbn_to_ppm(ifp, chunksize, bmhdP, cmap);
+        rgbn_to_ppm(ifP, chunksize, bmhdP, cmap);
 }
 
 
@@ -2407,6 +2428,8 @@ main(int argc, char *argv[]) {
     if( argn != argc )
         pm_usage(usage);
 
+    wrotemask = false;  /* initial value */
+
     /* Read in the ILBM file. */
 
     firstIffid = get_big_long(ifP, ID_FORM, NULL);
diff --git a/converter/ppm/picttoppm.c b/converter/ppm/picttoppm.c
index 43b8d1ef..828d5270 100644
--- a/converter/ppm/picttoppm.c
+++ b/converter/ppm/picttoppm.c
@@ -1200,7 +1200,7 @@ doDiffSize(struct Rect       const clipsrc,
 
     unsigned int const dstadd = dstwid - xsize;
 
-    FILE * pnmscalePipeP;
+    FILE * pamscalePipeP;
     const char * command;
     FILE * scaled;
     int cols, rows, format;
@@ -1221,19 +1221,19 @@ doDiffSize(struct Rect       const clipsrc,
 
     pm_close(tempFileP);
 
-    pm_asprintf(&command, "pnmscale -xsize %d -ysize %d > %s",
+    pm_asprintf(&command, "pamscale -xsize %d -ysize %d > %s",
                 rectwidth(&clipdst), rectheight(&clipdst), tempFilename);
 
     pm_message("running command '%s'", command);
 
-    pnmscalePipeP = popen(command, "w");
-    if (pnmscalePipeP == NULL)
+    pamscalePipeP = popen(command, "w");
+    if (pamscalePipeP == NULL)
         pm_error("cannot execute command '%s'  popen() errno = %s (%d)",
                  command, strerror(errno), errno);
 
     pm_strfree(command);
 
-    fprintf(pnmscalePipeP, "P6\n%d %d\n%d\n",
+    fprintf(pamscalePipeP, "P6\n%d %d\n%d\n",
             rectwidth(&clipsrc), rectheight(&clipsrc), PPM_MAXMAXVAL);
 
     switch (pixSize) {
@@ -1245,9 +1245,9 @@ doDiffSize(struct Rect       const clipsrc,
             for (colNumber = 0; colNumber < xsize; ++colNumber) {
                 unsigned int const colorIndex = row[colNumber];
                 struct RGBColor * const ct = &color_map[colorIndex];
-                fputc(redepth(ct->red, 65535L), pnmscalePipeP);
-                fputc(redepth(ct->grn, 65535L), pnmscalePipeP);
-                fputc(redepth(ct->blu, 65535L), pnmscalePipeP);
+                fputc(redepth(ct->red, 65535L), pamscalePipeP);
+                fputc(redepth(ct->grn, 65535L), pamscalePipeP);
+                fputc(redepth(ct->blu, 65535L), pamscalePipeP);
             }
         }
     }
@@ -1259,9 +1259,9 @@ doDiffSize(struct Rect       const clipsrc,
             unsigned int colNumber;
             for (colNumber = 0; colNumber < xsize; ++colNumber) {
                 struct RGBColor const color = decode16(&row[colNumber * 2]);
-                fputc(redepth(color.red, 32), pnmscalePipeP);
-                fputc(redepth(color.grn, 32), pnmscalePipeP);
-                fputc(redepth(color.blu, 32), pnmscalePipeP);
+                fputc(redepth(color.red, 32), pamscalePipeP);
+                fputc(redepth(color.grn, 32), pamscalePipeP);
+                fputc(redepth(color.blu, 32), pamscalePipeP);
             }
         }
     }
@@ -1278,17 +1278,17 @@ doDiffSize(struct Rect       const clipsrc,
 
             unsigned int colNumber;
             for (colNumber = 0; colNumber < xsize; ++colNumber) {
-                fputc(redepth(redPlane[colNumber], 256), pnmscalePipeP);
-                fputc(redepth(grnPlane[colNumber], 256), pnmscalePipeP);
-                fputc(redepth(bluPlane[colNumber], 256), pnmscalePipeP);
+                fputc(redepth(redPlane[colNumber], 256), pamscalePipeP);
+                fputc(redepth(grnPlane[colNumber], 256), pamscalePipeP);
+                fputc(redepth(bluPlane[colNumber], 256), pamscalePipeP);
             }
         }
     }
     break;
     }
 
-    if (pclose(pnmscalePipeP))
-        pm_error("pnmscale failed.  pclose() returned Errno %s (%d)",
+    if (pclose(pamscalePipeP))
+        pm_error("pamscale failed.  pclose() returned Errno %s (%d)",
                  strerror(errno), errno);
 
     ppm_readppminit(scaled = pm_openr(tempFilename), &cols, &rows,
diff --git a/converter/ppm/ppmtoarbtxt.c b/converter/ppm/ppmtoarbtxt.c
index df859c84..569f5ea2 100644
--- a/converter/ppm/ppmtoarbtxt.c
+++ b/converter/ppm/ppmtoarbtxt.c
@@ -1,4 +1,4 @@
-/* ppmtoarbtxt.c - convert portable pixmap to cleartext
+/* ppmtoarbtxt.c - convert PPM to a custom text-based format
 **
 ** Renamed from ppmtotxt.c by Bryan Henderson in January 2003.
 **
@@ -12,51 +12,150 @@
 ** implied warranty.
 */
 
+#include <assert.h>
 #include <string.h>
+#ifdef __GLIBC__
+  #include <printf.h>  /* Necessary for parse_printf_format() */
+#endif
 
-#include "ppm.h"
 #include "mallocvar.h"
 #include "nstring.h"
+#include "shhopt.h"
+#include "ppm.h"
+
+/* HAVE_PARSE_PRINTF_FORMAT means the system library has
+   parse_printf_format(), declared in <printf.h>.  This essentially means
+   systems with GNU libc.
+*/
+
+#ifndef HAVE_PARSE_PRINTF_FORMAT
+  #ifdef PA_FLAG_MASK                   /* Defined in printf.h */
+    #define HAVE_PARSE_PRINTF_FORMAT 1
+  #else
+    #define HAVE_PARSE_PRINTF_FORMAT 0
+  #endif
+#endif
+
+
+
+struct CmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;
+    const char * bodySklFileName;
+    const char * hd;
+    const char * tl;
+    unsigned int debug;
+};
+
+
+
+static void
+parseCommandLine(int argc, const char ** argv,
+                 struct CmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that many of the strings that this function returns in the
+   *cmdline_p structure are actually in the supplied argv array.  And
+   sometimes, one of these strings is actually just a suffix of an entry
+   in argv!
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int hdSpec, tlSpec;
+
+    unsigned int option_def_index;
+    
+    MALLOCARRAY(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0,   "hd",   OPT_STRING, &cmdlineP->hd, 
+            &hdSpec,             0);
+    OPTENT3(0,   "tl",   OPT_STRING, &cmdlineP->tl,
+            &tlSpec,             0);
+    OPTENT3(0,   "debug", OPT_FLAG, NULL,
+            &cmdlineP->debug,      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);
+    free(option_def);
+
+    if (!hdSpec)
+        cmdlineP->hd = NULL;
+
+    if (!tlSpec)
+        cmdlineP->tl = NULL;
+
+    if (argc-1 < 1)
+        pm_error("You must specify the body skeleton file name as an "
+                 "argument");
+    else {
+        cmdlineP->bodySklFileName = strdup(argv[1]);
+
+        if (argc-1 < 2)
+            cmdlineP->inputFileName = strdup("-");  /* he wants stdin */
+        else {
+            cmdlineP->inputFileName = strdup(argv[2]);
+            if (argc-1 > 2)
+                pm_error("Too many arguments.  The only possible arguments "
+                         "are the body skeleton file name and input image "
+                         "file name");
+        }
+    }
+}
+
+
+
 
 typedef enum {
 /* The types of object we handle */
     BDATA, IRED, IGREEN, IBLUE, ILUM, FRED, FGREEN, FBLUE, FLUM,
     WIDTH, HEIGHT, POSX, POSY
-} SKL_OBJ_TYP;
+} SkeletonObjectType;
 
 typedef enum {
     OBJTYP_ICOLOR, OBJTYP_FCOLOR, OBJTYP_INT, OBJTYP_BDATA
-} SKL_OBJ_CLASS;
+} SkeletonObjectClass;
 
 /* Maximum size for a format string ("%d" etc.) */
+/* Add one to this size for the terminating '\0'. */
 #define MAXFORMAT 16
 
+typedef union {
 /* The data we keep for each object */
-typedef union
- {
-  struct BNDAT { char *bdat;   /* Binary data (text with newlines etc.) */
-                 int ndat;
-               } bin_data;
-
-  struct ICDAT { char icformat[MAXFORMAT];  /* Integer colors */
-                 int icolmin, icolmax;
-               } icol_data;
-
-  struct FCDAT { char fcformat[MAXFORMAT];  /* Float colors */
-                 double fcolmin, fcolmax;
-               } fcol_data;
-
-  struct IDAT  { char iformat[MAXFORMAT];   /* Integer data */
-               } i_data;
- } SKL_OBJ_DATA;
+    struct Bndat {
+        char * bdat;   /* Binary data (text with newlines etc.) */
+        unsigned int ndat;
+    } binData;
+    
+    struct Icdat {
+        char icformat[MAXFORMAT+1];  /* Integer colors */
+        unsigned int icolmin, icolmax;
+    } icolData;
+
+    struct Fcdat {
+        char fcformat[MAXFORMAT+1];  /* Float colors */
+        double fcolmin, fcolmax;
+    } fcolData;
+    
+    struct Idat {
+        char iformat[MAXFORMAT+1];   /* Integer data */
+    } iData;
+} SkeletonObjectData;
 
 
 /* Each object has a type and some data */
-typedef struct
- { 
-   SKL_OBJ_TYP otyp;
-   SKL_OBJ_DATA odata;
- } SKL_OBJ;
+typedef struct { 
+    SkeletonObjectType objType;
+    SkeletonObjectData odata;
+} SkeletonObject;
+
 
 
 #define MAX_SKL_HEAD_OBJ 64
@@ -66,199 +165,164 @@ typedef struct
 #define MAX_OBJ_BUF 80
 
 
-static void write_txt (fout, nobj, obj, width, height, x, y, red, green, blue)
-FILE *fout;
-int nobj;
-SKL_OBJ *obj[];
-int width, height, x, y;
-double red, green, blue;
-
-{register int count;
-
-#define WRITE_BNDAT(fd,theobj) \
- {struct BNDAT *bdata = &((theobj)->odata.bin_data); \
-       fwrite (bdata->bdat,bdata->ndat,1,fd); }
-
-#define WRITE_ICOL(fd,theobj,thecol) \
- {struct ICDAT *icdata = &((theobj)->odata.icol_data); \
-  fprintf (fd,icdata->icformat,(int)(icdata->icolmin \
-                + (icdata->icolmax - icdata->icolmin)*(thecol))); }
-
-#define WRITE_FCOL(fd,theobj,thecol) \
- {struct FCDAT *fcdata = &((theobj)->odata.fcol_data); \
-  fprintf (fd,fcdata->fcformat,(double)(fcdata->fcolmin \
-                + (fcdata->fcolmax - fcdata->fcolmin)*(thecol))); }
-
-#define WRITE_IDAT(fd,theobj,thedat) \
- {struct IDAT *idata = &((theobj)->odata.i_data); \
-  fprintf (fd,idata->iformat,thedat); }
-
- for (count = 0; count < nobj; count++)
- {
-   switch (obj[count]->otyp)
-   {
-     case BDATA:
-       WRITE_BNDAT (fout,obj[count]);
-       break;
-     case IRED:
-       WRITE_ICOL (fout,obj[count],red);
-       break;
-     case IGREEN:
-       WRITE_ICOL (fout,obj[count],green);
-       break;
-     case IBLUE:
-       WRITE_ICOL (fout,obj[count],blue);
-       break;
-     case ILUM:
-       WRITE_ICOL (fout,obj[count],0.299*red+0.587*green+0.114*blue);
-       break;
-     case FRED:
-       WRITE_FCOL (fout,obj[count],red);
-       break;
-     case FGREEN:
-       WRITE_FCOL (fout,obj[count],green);
-       break;
-     case FBLUE:
-       WRITE_FCOL (fout,obj[count],blue);
-       break;
-     case FLUM:
-       WRITE_FCOL (fout,obj[count],0.299*red+0.587*green+0.114*blue);
-       break;
-     case WIDTH:
-       WRITE_IDAT (fout,obj[count],width);
-       break;
-     case HEIGHT:
-       WRITE_IDAT (fout,obj[count],height);
-       break;
-     case POSX:
-       WRITE_IDAT (fout,obj[count],x);
-       break;
-     case POSY:
-       WRITE_IDAT (fout,obj[count],y);
-       break;
-   }
- }
-}
 
+static void
+dumpSkeleton(SkeletonObject ** const skeletonPList,
+             unsigned int      const nSkeleton) {
 
-static SKL_OBJ *
-save_bin_data(int    const ndat, 
-              char * const bdat) {
+    unsigned int i;
 
-    /* Save binary data in Object */
+    pm_message("%u objects", nSkeleton);
 
-    SKL_OBJ *obj;
+    for (i = 0; i < nSkeleton; ++i) {
+        SkeletonObject * const skeletonP = skeletonPList[i];
 
-    obj = (SKL_OBJ *)malloc (sizeof (SKL_OBJ) + ndat);
-    if (obj != NULL)
-    {
-        obj->otyp = BDATA;
-        obj->odata.bin_data.ndat = ndat;
-        obj->odata.bin_data.bdat = ((char *)(obj))+sizeof (SKL_OBJ);
-        memcpy (obj->odata.bin_data.bdat,bdat,ndat);
+        pm_message("  Object: Type %u", skeletonP->objType);
     }
-    return (obj);
 }
 
 
 
-static SKL_OBJ *
-save_icol_data(SKL_OBJ_TYP  const ctyp,
-               const char * const format,
-               int          const icolmin,
-               int          const icolmax) {
-/* Save integer color data in Object */
-
-    SKL_OBJ * objP;
-
-    MALLOCVAR(objP);
-
-    if (objP) {
-        objP->otyp = ctyp;
-        strcpy(objP->odata.icol_data.icformat, format);
-        objP->odata.icol_data.icolmin = icolmin;
-        objP->odata.icol_data.icolmax = icolmax;
-    }
-    return objP;
+static void
+dumpAllSkeleton(SkeletonObject ** const bodySkeletonPList,
+                unsigned int      const bodyNskl,
+                SkeletonObject ** const headSkeletonPList, 
+                unsigned int      const headNskl,
+                SkeletonObject ** const tailSkeletonPList,
+                unsigned int      const tailNskl) {
+    
+    pm_message("Body skeleton:");
+    dumpSkeleton(bodySkeletonPList, bodyNskl);
+
+    pm_message("Head skeleton:");
+    dumpSkeleton(headSkeletonPList, headNskl);
+
+    pm_message("Tail skeleton:");
+    dumpSkeleton(tailSkeletonPList, tailNskl);
 }
 
 
 
-static SKL_OBJ *
-save_fcol_data(SKL_OBJ_TYP  const ctyp,
-               const char * const format,
-               double       const fcolmin,
-               double       const fcolmax) {
-/* Save float color data in Object */
-
-    SKL_OBJ * objP;
+static void
+writeBndat(FILE *           const ofP,
+           SkeletonObject * const objectP) {
 
-    MALLOCVAR(objP);
+    struct Bndat * const bdataP = &objectP->odata.binData;
 
-    if (objP) {
-        objP->otyp = ctyp;
-        strcpy(objP->odata.fcol_data.fcformat, format);
-        objP->odata.fcol_data.fcolmin = fcolmin;
-        objP->odata.fcol_data.fcolmax = fcolmax;
-    }
-    return objP;
+    fwrite(bdataP->bdat, bdataP->ndat, 1, ofP);
 }
 
 
 
-static SKL_OBJ *
-save_i_data(SKL_OBJ_TYP  const ctyp,
-            const char * const format) {
+static void
+writeIcol(FILE *           const ofP,
+          SkeletonObject * const objectP,
+          double           const value) {
+
+    struct Icdat * const icdataP = &objectP->odata.icolData;
+    
+    fprintf(ofP, icdataP->icformat,
+            (unsigned int)
+            (icdataP->icolmin
+             + (icdataP->icolmax - icdataP->icolmin) * value));
+}
 
-/* Save universal data in Object */
 
-    SKL_OBJ * objP;
 
-    MALLOCVAR(objP);
-    if (objP) {
-        objP->otyp = ctyp;
-        strcpy(objP->odata.i_data.iformat, format);
-    }
-    return objP;
+static void
+writeFcol(FILE *           const ofP,
+          SkeletonObject * const objectP,
+          double           const value) {
+
+    struct Fcdat * const fcdataP = &objectP->odata.fcolData;
+    
+    fprintf(ofP, fcdataP->fcformat,
+            (double)
+            (fcdataP->fcolmin
+             + (fcdataP->fcolmax - fcdataP->fcolmin) * value));
 }
 
 
 
-static char const escape = '#';
+static void
+writeIdat(FILE *           const ofP,
+          SkeletonObject * const objectP,
+          unsigned int     const value) {
+
+    struct Idat * const idataP = &objectP->odata.iData;
+    
+    fprintf(ofP, idataP->iformat, value);
+}
 
 
 
-static SKL_OBJ_TYP
-interpretObjType(const char * const typstr) {
+static void
+writeText(FILE *            const ofP,
+          unsigned int      const nObj,
+          SkeletonObject ** const obj,
+          unsigned int      const width,
+          unsigned int      const height,
+          unsigned int      const x,
+          unsigned int      const y,
+          double            const red,
+          double            const green,
+          double            const blue) {
+    
+    unsigned int i;
 
-    SKL_OBJ_TYP otyp;
-
-    /* Check for integer colors */
-    if      (streq(typstr, "ired")  ) otyp = IRED;
-    else if (streq(typstr, "igreen")) otyp = IGREEN;
-    else if (streq(typstr, "iblue") ) otyp = IBLUE;
-    else if (streq(typstr, "ilum")  ) otyp = ILUM;
-    /* Check for real colors */
-    else if (streq(typstr, "fred")  ) otyp = FRED;
-    else if (streq(typstr, "fgreen")) otyp = FGREEN;
-    else if (streq(typstr, "fblue") ) otyp = FBLUE;
-    else if (streq(typstr, "flum")  ) otyp = FLUM;
-    /* Check for integer data */
-    else if (streq(typstr, "width") ) otyp = WIDTH;
-    else if (streq(typstr, "height")) otyp = HEIGHT;
-    else if (streq(typstr, "posx")  ) otyp = POSX;
-    else if (streq(typstr, "posy")  ) otyp = POSY;
-    else                              otyp = BDATA;
-
-    return otyp;
+    for (i = 0; i < nObj; ++i) {
+        switch (obj[i]->objType) {
+        case BDATA:
+            writeBndat(ofP, obj[i]);
+            break;
+        case IRED:
+            writeIcol(ofP, obj[i], red);
+            break;
+        case IGREEN:
+            writeIcol(ofP, obj[i], green);
+            break;
+        case IBLUE:
+            writeIcol(ofP, obj[i], blue);
+            break;
+        case ILUM:
+            writeIcol(ofP, obj[i],
+                      PPM_LUMINR*red + PPM_LUMING*green + PPM_LUMINB*blue);
+            break;
+        case FRED:
+            writeFcol(ofP, obj[i], red);
+            break;
+        case FGREEN:
+            writeFcol(ofP, obj[i], green);
+            break;
+        case FBLUE:
+            writeFcol(ofP, obj[i], blue);
+            break;
+        case FLUM:
+            writeFcol(ofP, obj[i],
+                      PPM_LUMINR*red + PPM_LUMING*green + PPM_LUMINB*blue);
+            break;
+        case WIDTH:
+            writeIdat(ofP, obj[i], width);
+            break;
+        case HEIGHT:
+            writeIdat(ofP, obj[i], height);
+            break;
+        case POSX:
+            writeIdat(ofP, obj[i], x);
+            break;
+        case POSY:
+            writeIdat(ofP, obj[i], y);
+            break;
+        }
+    }
 }
 
 
 
-static SKL_OBJ_CLASS
-objClass(SKL_OBJ_TYP const otyp) {
+static SkeletonObjectClass
+objClass(SkeletonObjectType const objType) {
 
-    switch (otyp) {
+    switch (objType) {
     case IRED:
     case IGREEN:
     case IBLUE:
@@ -283,298 +347,871 @@ objClass(SKL_OBJ_TYP const otyp) {
 }
 
 
+/*----------------------------------------------------------------------------
+  Format string validation
+
+  We validate format strings (such as "%f" "%03d") found in the skeleton files
+  for convenience, even though behavior is documented as undefined when the
+  user supplies a bogus format string.  Certain strings, most notably those
+  with "%n", are especially risky; they pose a security threat.
+
+  On systems with Glibc, we check with parse_printf_format().  On other
+  systems we conduct a cursory scan of the characters in the format string,
+  looking for characters that trigger non-numeric conversions, etc.
+
+  Documentation for parse_printf_format() is usually available in texinfo
+  format on GNU/Linux systems.  As of Dec. 2014 there is no official man page.
+  
+  Online documentation is available from:
+  https://
+  www.gnu.org/software/libc/manual/html_node/Parsing-a-Template-String.html
+-----------------------------------------------------------------------------*/
 
+#if HAVE_PARSE_PRINTF_FORMAT
 static void
-addImpostorReplacementSeq(char *         const line,
-                          unsigned int   const startCursor,
-                          const char *   const seqContents,
-                          unsigned int   const seqContentsLen,
-                          unsigned int * const newCursorP) {
+validateParsePrintfFLag(int                const printfConversion,
+                        SkeletonObjectType const ctyp,
+                        const char **      const errorP) {
 /*----------------------------------------------------------------------------
-   Add to line line[], at position 'startCursor', text that looks like a
-   replacement sequence but doesn't have the proper contents (the
-   stuff between the parentheses) to be one.  For example,
+  Assuming 'printfConversion' is the value reported by parse_printf_format()
+  as the type of argument a format string requires, 
+  return an explanation of how it is incompatible with 'ctyp' as
+  *errorP -- return null string if it is compatible.
+-----------------------------------------------------------------------------*/
+    /* We first check for "%n", then the type modifiers, and finally the
+       actual conversion type (char, int, float, double, string or pointer.)
+    */
+    switch (printfConversion & PA_FLAG_MASK) {
+    case PA_FLAG_PTR:  /* This means %n */
+        pm_asprintf(errorP, "Contains a %%n conversion specifier");
+        break;
+
+    case PA_FLAG_SHORT:
+    case PA_FLAG_LONG:
+    case PA_FLAG_LONG_LONG:
+        /* We omit PA_FLAG_LONG_DOUBLE because it is a synonym for
+           PA_FLAG_LONG_LONG: listing both causes compilation errors.
+        */
+        pm_asprintf(errorP, "Invalid type modifier");
+        break;
+
+    default:
+        switch (printfConversion & ~PA_FLAG_MASK) {
+        case PA_CHAR:
+            pm_message("Warning: char type conversion."); 
+        case PA_INT:
+            if(objClass(ctyp) == OBJTYP_ICOLOR ||
+               objClass(ctyp) == OBJTYP_INT )
+                *errorP = NULL;
+            else
+                pm_asprintf(errorP, "Conversion specifier requires a "
+                            "character or integer argument, but it is in "
+                            "a replacement sequence for a different type");
+            break;
+        case PA_DOUBLE:
+            if(objClass(ctyp) == OBJTYP_FCOLOR)
+                *errorP = NULL;
+            else
+                pm_asprintf(errorP, "Conversion specifier requires a "
+                            "double precision floating point argument, "
+                            "but it is in "
+                            "a replacement sequence for a different type");
+            break;
+        case PA_FLOAT:
+        case PA_STRING:    /* %s */
+        case PA_POINTER:   /* %p */
+        default:
+            pm_asprintf(errorP, "Conversion specifier requires an argument of "
+                        "a type that this program never provides for "
+                        "any replacement sequence");
+        }
+    }
+}
+#endif
 
-     "#(fread x)"
 
-   seqContents[] is the contents; 'seqContentsLen' its length.
 
-   Return as *newCursorP where the line[] cursor ends up after adding
-   the sequence.
+static void
+validateFormatWithPpf(const char *       const format,
+                      SkeletonObjectType const ctyp,
+                      const char **      const errorP) {
+/*----------------------------------------------------------------------------
+  Validate format string 'format' for use with a skeleton of type 'ctyp',
+  using the system parse_printf_format() function.
+
+  Return as *errorP an explanation of how it is invalid, or a null string
+  if it is valid.
 -----------------------------------------------------------------------------*/
-    unsigned int cursor;
-    unsigned int i;
+    /* We request parse_printf_format() to report the details of the first
+       8 conversions.  8 because the maximum length of format is 16 means it
+       can have up to 8 conversions: "%d%d%d%d%d%d%d%d".
+
+       Actually this is more than necessary: we are concerned with only the
+       first conversion and whether there it is the only one.
+    */
 
-    cursor = startCursor;
+    int printfConversion[MAXFORMAT/2] = {0, 0, 0, 0, 0, 0, 0, 0};
 
-    line[cursor++] = escape;
-    line[cursor++] = '(';
+    size_t const n =
+        parse_printf_format(format, MAXFORMAT/2, printfConversion);
+
+    switch (n) {
+    case 0:
+        pm_asprintf(errorP, "No transformation found");
+        break;
+
+    case 1:
+        validateParsePrintfFLag(printfConversion[0], ctyp, errorP);
+        break;
+
+    default:
+        pm_asprintf(errorP, "Has %lu extra transformation%s ",
+                    n-1, n-1 > 1 ? "s" : "");
+        break;
+    }
+}
 
-    for (i = 0; i < seqContentsLen; ++i)
-        line[cursor++] = seqContents[i];
 
-    line[cursor++] = ')';
 
-    *newCursorP = cursor;
+static void
+validateFormatOne(char               const typeSpecifier,
+                  bool               const isLastInString,
+                  SkeletonObjectType const ctyp,
+                  bool *             const validatedP,
+                  const char **      const errorP) {
+
+    switch (typeSpecifier) {
+        /* Valid character encountered.  Skip. */
+        /* ' ' (space) is listed here, but should never occur for
+           we use sscanf() to parse the fields.
+        */
+    case ' ': case '-': case '+': case '\'': case '#': case '.':
+    case '0': case '1': case '2': case '3': case '4': case '5':
+    case '6': case '7': case '8': case '9':
+        break;
+        
+    case 'c': case 'C':
+        pm_message("Warning: char type conversion: %%%c.", typeSpecifier);
+    case 'i': case 'd': case 'u': case 'o': case 'x': case 'X':
+        if (!isLastInString)
+            pm_asprintf(errorP, "Extra characters at end");
+        else if(objClass(ctyp) != OBJTYP_ICOLOR &&
+                objClass(ctyp) != OBJTYP_INT )
+            pm_asprintf(errorP, "Conversion type mismatch");
+        else
+            *validatedP = true;
+        break;
+
+    case 'f': case 'F': case 'g': case 'G': case 'a': case 'A':
+        if (!isLastInString)
+            pm_asprintf(errorP, "Extra characters at end");
+        else if(objClass(ctyp) != OBJTYP_FCOLOR)
+            pm_asprintf(errorP, "Conversion type mismatch");
+        else
+            *validatedP = true;
+        break;
+
+    case '\0':
+        pm_asprintf(errorP, "No conversion specified");
+        break;
+    case '%':
+        pm_asprintf(errorP, "No more than one %% is allowed");
+        break;
+    case '$':
+    case '*':
+        pm_asprintf(errorP, "%c is not allowed", typeSpecifier);
+        break;
+    case 'h': case 'l': case 'L': case 'q': case 'j': case 'Z': case 't':
+        pm_asprintf(errorP, "Modifier %c is not allowed in format",
+                    typeSpecifier);
+        break;
+    case 's': case 'S': case 'm': case 'p': case 'n':
+        pm_asprintf(errorP, "Invalid conversion type");
+        break;
+    default:
+        pm_asprintf(errorP, "Abnormal character");
+        break;
+    }
 }
 
 
 
-static int
-read_skeleton(const char *   const filename,
-              unsigned int   const maxskl,
-              unsigned int * const nsklP,
-              SKL_OBJ **     const skl) {
+static void
+validateFormatGen(const char *       const format,
+                  SkeletonObjectType const ctyp,
+                  const char **      const errorP)  {
 /*----------------------------------------------------------------------------
-  Read skeleton file
+  Validate format string 'format' for use with a skeleton of type 'ctyp',
+  without using the system parse_printf_format() function.
+
+  The string must begin with "%" and end with the translation type character
+  ("%d", "%x", "%f", etc.)
+
+  We check only for invalid characters.  Invalid constructs, such as
+  "%00.00.00d" will pass this test.
+
+  Return as *errorP an explanation of how it is invalid, or a null string
+  if it is valid.
 -----------------------------------------------------------------------------*/
-    FILE * sklfile;
-    unsigned int slen;
-    unsigned int objlen;
-    int chr;
-    SKL_OBJ_TYP otyp;
-    char line[MAX_LINE_BUF+MAX_OBJ_BUF+16];
-    char objstr[MAX_OBJ_BUF],typstr[MAX_OBJ_BUF];
-    unsigned int nskl;
-
-#define SAVE_BIN(slen,line) \
-    { if (slen > 0 && (skl[nskl] = save_bin_data(slen,line)) != NULL) ++nskl; \
-      slen = 0; }
-
-    sklfile = pm_openr(filename);
-
-    /* Parse skeleton file */
-    nskl = 0;  /* initial value */
-
-    slen = 0;
-    while ((chr = getc (sklfile)) != EOF) {  /* Up to end of skeleton file */
-        if (nskl >= maxskl)
-            return -1;
-
-        if (slen+1 >= MAX_LINE_BUF) {
-            /* Buffer finished.  Save as binary object */
-            SAVE_BIN(slen,line);
+    if (format[0] != '%')
+        pm_asprintf(errorP, "Does not start with %%");
+    else {
+        unsigned int i;
+        bool validated;
+
+        for (i = 1, validated = false, *errorP = NULL;
+             !validated && !*errorP;
+             ++i) {
+
+            validateFormatOne(format[i], format[i+1] == '\0', ctyp,
+                              &validated, errorP);
         }
+    }
+}
 
-        if (chr != escape) {
-            /* Not a replacement sequence; just a literal character */
-            line[slen++] = chr;
-            continue;
-        }
 
-        chr = getc(sklfile);
-        if (chr == EOF) {
-            /* Not a valid replacement sequence */
-            line[slen++] = escape;
-            break;
-        }
-        if (chr != '(') {
-            /* Not a valid replacement sequence */
-            line[slen++] = escape;
-            line[slen++] = chr;
-            continue;
+
+static void
+validateFormat(const char *       const format,
+               SkeletonObjectType const ctyp) {
+
+    const char * error;
+
+    if (strlen(format) > MAXFORMAT)
+        pm_asprintf(&error, "Too long");
+    else {
+#if HAVE_PARSE_PRINTF_FORMAT
+        if (true)
+            validateFormatWithPpf(format, ctyp, &error);
+        else  /* Silence compiler warning about unused function */
+            validateFormatGen(format, ctyp, &error);
+#else
+        validateFormatGen(format, ctyp, &error);
+#endif
+    }
+
+    if (error)
+        pm_error("Invalid format string '%s'.  %s", format, error);
+}              
+               
+
+
+static SkeletonObject *
+newBinDataObj(unsigned int const nDat, 
+              const char * const bdat) {
+/*----------------------------------------------------------------------------
+  Create a binary data object.
+-----------------------------------------------------------------------------*/
+    SkeletonObject * objectP;
+
+    objectP = malloc(sizeof(*objectP) + nDat);
+
+    if (!objectP)
+        pm_error("Failed to allocate memory for binary data object "
+                 "with %u bytes", nDat);
+
+    objectP->objType = BDATA;
+    objectP->odata.binData.ndat = nDat;
+    objectP->odata.binData.bdat = ((char *)objectP) + sizeof(SkeletonObject);
+    memcpy(objectP->odata.binData.bdat, bdat, nDat);
+
+    return objectP;
+}
+
+
+
+static SkeletonObject *
+newIcolDataObj(SkeletonObjectType const ctyp,
+               const char *       const format,
+               unsigned int       const icolmin,
+               unsigned int       const icolmax) {
+/*----------------------------------------------------------------------------
+  Create integer color data object.
+-----------------------------------------------------------------------------*/
+    SkeletonObject * objectP;
+
+    MALLOCVAR(objectP);
+
+    if (!objectP)
+        pm_error("Failed to allocate memory for an integer color data "
+                 "object");
+
+    objectP->objType = ctyp;
+    validateFormat(format, ctyp);
+    strcpy(objectP->odata.icolData.icformat, format);
+    objectP->odata.icolData.icolmin = icolmin;
+    objectP->odata.icolData.icolmax = icolmax;
+
+    return objectP;
+}
+
+
+
+static SkeletonObject *
+newFcolDataObj(SkeletonObjectType  const ctyp,
+               const char *        const format,
+               double              const fcolmin,
+               double              const fcolmax) {
+/*----------------------------------------------------------------------------
+  Create float color data object.
+-----------------------------------------------------------------------------*/
+    SkeletonObject * objectP;
+
+    MALLOCVAR(objectP);
+
+    if (!objectP)
+        pm_error("Failed to allocate memory for a float color data object");
+
+    objectP->objType = ctyp;
+    validateFormat(format, ctyp);
+    strcpy(objectP->odata.fcolData.fcformat, format);
+    objectP->odata.fcolData.fcolmin = fcolmin;
+    objectP->odata.fcolData.fcolmax = fcolmax;
+
+    return objectP;
+}
+
+
+
+static SkeletonObject *
+newIdataObj(SkeletonObjectType const ctyp,
+            const char *       const format) {
+/*----------------------------------------------------------------------------
+  Create universal data object.
+-----------------------------------------------------------------------------*/
+    SkeletonObject * objectP;
+
+    MALLOCVAR(objectP);
+
+    if (!objectP)
+        pm_error("Failed to allocate memory for a universal data object");
+
+    objectP->objType = ctyp;
+    validateFormat(format, ctyp);
+    strcpy(objectP->odata.iData.iformat, format);
+
+    return objectP;
+}
+
+
+
+static char const escape = '#';
+
+
+
+static SkeletonObjectType
+interpretObjType(const char * const typstr) {
+
+    SkeletonObjectType objType;
+
+    /* handle integer colors */
+    if      (streq(typstr, "ired")  ) objType = IRED;
+    else if (streq(typstr, "igreen")) objType = IGREEN;
+    else if (streq(typstr, "iblue") ) objType = IBLUE;
+    else if (streq(typstr, "ilum")  ) objType = ILUM;
+    /* handle real colors */
+    else if (streq(typstr, "fred")  ) objType = FRED;
+    else if (streq(typstr, "fgreen")) objType = FGREEN;
+    else if (streq(typstr, "fblue") ) objType = FBLUE;
+    else if (streq(typstr, "flum")  ) objType = FLUM;
+    /* handle integer data */
+    else if (streq(typstr, "width") ) objType = WIDTH;
+    else if (streq(typstr, "height")) objType = HEIGHT;
+    else if (streq(typstr, "posx")  ) objType = POSX;
+    else if (streq(typstr, "posy")  ) objType = POSY;
+    else                              objType = BDATA;
+
+    return objType;
+}
+
+
+
+static SkeletonObject *
+newIcSkelFromReplString(const char *       const objstr,
+                        SkeletonObjectType const objType) {
+
+    SkeletonObject * retval;
+    unsigned int icolmin, icolmax;
+    char formstr[MAX_OBJ_BUF];
+    unsigned int nOdata;
+
+    nOdata = sscanf(objstr, "%*s%s%u%u", formstr, &icolmin, &icolmax);
+
+    if (nOdata == 3)
+        retval = newIcolDataObj(objType, formstr, icolmin, icolmax);
+    else if (nOdata == EOF) {
+        /* No arguments specified.  Use defaults */
+        retval = newIcolDataObj(objType, "%u", 0, 255);
+    } else
+        retval = NULL;
+
+    return retval;
+}
+
+
+
+static SkeletonObject *
+newFcSkelFromReplString(const char *       const objstr,
+                        SkeletonObjectType const objType) {
+
+    SkeletonObject * retval;
+    double fcolmin, fcolmax;
+    char formstr[MAX_OBJ_BUF];
+    unsigned int nOdata;
+
+    nOdata = sscanf(objstr, "%*s%s%lf%lf", formstr,
+                    &fcolmin, &fcolmax);
+
+    if (nOdata == 3)
+        retval = newFcolDataObj(objType, formstr, fcolmin, fcolmax);
+    else if (nOdata == EOF) {
+        /* No arguments specified.  Use defaults */
+        retval = newFcolDataObj(objType, "%f", 0.0, 1.0);
+    } else
+        retval = NULL;
+
+    return retval;
+} 
+
+
+
+static SkeletonObject *
+newISkelFromReplString(const char *       const objstr,
+                       SkeletonObjectType const objType) {
+
+    SkeletonObject * retval;
+    char formstr[MAX_OBJ_BUF];
+    unsigned int const nOdata = sscanf(objstr, "%*s%s", formstr);
+    
+    if (nOdata == 1)
+        retval = newIdataObj(objType, formstr);
+    else if (nOdata == EOF) {
+        /* No arguments specified.  Use defaults */
+        retval = newIdataObj(objType, "%u");
+    } else
+        retval = NULL;
+
+    return retval;
+} 
+
+
+
+static SkeletonObject *
+newSkeletonFromReplString(const char * const objstr) {
+/*----------------------------------------------------------------------------
+  Create a skeleton from the replacement string 'objstr' (the stuff
+  between the parentheses in #(...) ).
+
+  Return NULL if it isn't a valid replacement string.
+-----------------------------------------------------------------------------*/
+    /* We use sscanf() to parse the contents of objstr, giving it a format
+       template with the largest number of fields possible plus one extra to
+       pick up and check for the existence of invalid trailing characters.  We
+       read and discard fields beyond the first, if any.  The appropriate
+       new**SkelFromReplString() function determines their contents with a
+       separate call to sscanf().
+    */
+
+    SkeletonObject * retval;
+    char typstr[MAX_OBJ_BUF];
+    SkeletonObjectType objType;
+    int conversionCt;
+    char s1[MAX_OBJ_BUF];    /* Dry read. */
+    char s2[MAX_OBJ_BUF];    /* Extra tailing characters. */
+    float f1, f2;            /* Dry read. */ 
+
+    typstr[0] = '\0';  /* initial value */
+
+    conversionCt = sscanf(objstr, "%s%s%f%f%s", typstr, s1, &f1, &f2, s2);
+    switch (conversionCt) {
+    case 1: case 2: case 4:
+        objType = interpretObjType(typstr);
+      break;
+    default:
+        objType = BDATA;
+    }
+
+    switch (objClass(objType)) {
+    case OBJTYP_ICOLOR:
+        retval = newIcSkelFromReplString(objstr, objType);
+        break;
+    case OBJTYP_FCOLOR:
+        retval = newFcSkelFromReplString(objstr, objType);
+        break;
+    case OBJTYP_INT:
+        retval = newISkelFromReplString(objstr, objType);
+        break;
+    case OBJTYP_BDATA:
+        retval = NULL;
+    }
+    return retval;
+}
+
+
+
+static void
+readThroughCloseParen(FILE * const ifP,
+                      char * const objstr,
+                      size_t const objstrSize,
+                      bool * const unclosedP) {
+/*----------------------------------------------------------------------------
+   Read *ifP up through close parenthesis ( ')' ) into 'objstr', which
+   is of size 'objstrSize'.  Make it a NUL-terminated string.
+
+   Return *unclosedP true iff we run out of file or run out of objstr
+   before we see a close parenthesis.  In this case, return the rest of
+   the file, or as much as fits, in 'objstr', not NUL-terminated.
+-----------------------------------------------------------------------------*/
+    unsigned int i;
+    bool eof;
+    bool gotEscSeq;
+
+    for (i= 0, eof = false, gotEscSeq = false;
+         i < objstrSize - 1 && !gotEscSeq && !eof;
+         ++i) {
+
+        int rc;
+
+        rc = getc(ifP);
+        if (rc == EOF)
+            eof = true;
+        else {
+            char const chr = rc;
+            if (chr == ')') {
+                gotEscSeq = true;
+                objstr[i] = '\0';
+	        } else
+                objstr[i] = chr;
         }
-        /* Read replacement string up through ')'.  Put contents of
-           parentheses in objstr[].
+    }
+    *unclosedP = !gotEscSeq;
+}
+
+
+
+typedef struct {
+    unsigned int      capacity;
+    SkeletonObject ** skeletonPList;
+    unsigned int      nSkeleton;
+} SkeletonBuffer;
+
+
+
+static void
+SkeletonBuffer_init(SkeletonBuffer *  const bufferP,
+                    unsigned int      const capacity,
+                    SkeletonObject ** const skeletonPList) {
+
+    bufferP->capacity      = capacity;
+    bufferP->skeletonPList = skeletonPList;
+    bufferP->nSkeleton     = 0;
+}
+
+
+
+static void
+SkeletonBuffer_add(SkeletonBuffer * const bufferP,
+                   SkeletonObject * const skeletonP) {
+
+    if (bufferP->nSkeleton >= bufferP->capacity)
+        pm_error("Too many skeletons.  Max = %u", bufferP->capacity);
+
+    bufferP->skeletonPList[bufferP->nSkeleton++] = skeletonP;
+}                   
+
+
+
+typedef struct {
+
+    char data[MAX_LINE_BUF + MAX_OBJ_BUF + 16];
+
+    unsigned int length;
+
+    SkeletonBuffer * skeletonBufferP;
+        /* The buffer to which we flush.  Flushing means turning all the
+           characters currently in our buffer into a binary skeleton object
+           here.
         */
-        for (objlen = 0; objlen < sizeof(objstr)-1; ++objlen) {
-            chr = getc(sklfile);
-            if (chr == EOF) break;
-            if (chr == ')') break;
-            objstr[objlen] = chr;
-        }
-        objstr[objlen] = '\0';
-
-        if (chr != ')') {
-            /* Not valid replacement sequence */
-            unsigned int i;
-            line[slen++] = escape;
-            line[slen++] = chr;
-            for (i = 0; i < objlen; ++i)
-                line[slen++] = objstr[i];
-            if (chr == EOF)
-                break;
-            continue;
-        }
 
-        typstr[0] = '\0';           /* Get typ of data */
-        sscanf(objstr, "%s", typstr);
-
-        otyp = interpretObjType(typstr);
-
-        switch (objClass(otyp)) {
-        case OBJTYP_ICOLOR: {
-            int icolmin, icolmax;
-            char formstr[MAX_OBJ_BUF];
-            int n_odata;
-
-            n_odata = sscanf(objstr, "%*s%s%d%d", formstr, &icolmin, &icolmax);
-
-            if (n_odata == 3) {
-                SAVE_BIN(slen, line);
-                skl[nskl] = save_icol_data(otyp, formstr, icolmin, icolmax);
-                if (skl[nskl] != NULL)
-                    ++nskl;
-            } else if (n_odata == EOF) {
-                /* No arguments specified.  Use defaults */
-                SAVE_BIN(slen, line);
-                skl[nskl] = save_icol_data(otyp, "%d", 0, 255);
-                if (skl[nskl] != NULL)
-                    ++nskl;
-            } else
-                addImpostorReplacementSeq(line, slen, objstr, objlen, &slen);
-        } break;
-        case OBJTYP_FCOLOR: {
-            double fcolmin, fcolmax;
-            char formstr[MAX_OBJ_BUF];
-            int n_odata;
-
-            n_odata = sscanf(objstr, "%*s%s%lf%lf", formstr,
-                             &fcolmin, &fcolmax);
-
-            if (n_odata == 3) {
-                SAVE_BIN(slen, line);
-                skl[nskl] = save_fcol_data(otyp, formstr, fcolmin, fcolmax);
-                if (skl[nskl] != NULL)
-                    ++nskl;
-            } else if (n_odata == EOF) {
-                /* No arguments specified.  Use defaults */
-                SAVE_BIN(slen, line);
-                skl[nskl] = save_fcol_data(otyp, "%f", 0.0, 1.0);
-                if (skl[nskl] != NULL)
-                    ++nskl;
-            } else
-                addImpostorReplacementSeq(line, slen, objstr, objlen, &slen);
-        } break;
-
-        case OBJTYP_INT: {
-            char formstr[MAX_OBJ_BUF];
-            int const n_odata = sscanf(objstr, "%*s%s", formstr);
-
-            if (n_odata == 1) {
-                SAVE_BIN(slen, line);
-                skl[nskl] = save_i_data(otyp, formstr);
-                if (skl[nskl] != NULL)
-                    ++nskl;
-            } else if (n_odata == EOF) {
-                /* No arguments specified.  Use defaults */
-                SAVE_BIN(slen, line);
-                skl[nskl] = save_i_data(otyp, "%d");
-                if (skl[nskl] != NULL)
-                    ++nskl;
-            } else
-                addImpostorReplacementSeq(line, slen, objstr, objlen, &slen);
-        } break;
-        case OBJTYP_BDATA:
-            addImpostorReplacementSeq(line, slen, objstr, objlen, &slen);
+} Buffer;
+
+
+
+static void
+Buffer_init(Buffer *         const bufferP,
+            SkeletonBuffer * const skeletonBufferP) {
+
+    bufferP->skeletonBufferP = skeletonBufferP;
+    bufferP->length = 0;
+}
+
+
+
+static void
+Buffer_flush(Buffer * const bufferP) {
+/*----------------------------------------------------------------------------
+   Flush the buffer out to a binary skeleton object.
+-----------------------------------------------------------------------------*/
+    SkeletonBuffer_add(bufferP->skeletonBufferP,
+                       newBinDataObj(bufferP->length, bufferP->data));
+
+    bufferP->length = 0;
+}
+
+
+
+static void
+Buffer_add(Buffer * const bufferP,
+           char     const newChar) {
+
+    if (bufferP->length >= MAX_LINE_BUF)
+        Buffer_flush(bufferP);
+
+    assert(bufferP->length < MAX_LINE_BUF);
+
+    bufferP->data[bufferP->length++] = newChar;
+}
+
+
+
+static void
+Buffer_dropFinalNewline(Buffer * const bufferP) {
+/*----------------------------------------------------------------------------
+   If the last thing in the buffer is a newline, remove it.
+-----------------------------------------------------------------------------*/
+    if (bufferP->length >= 1 && bufferP->data[bufferP->length-1] == '\n') {
+            /* Drop finishing newline character */
+            --bufferP->length;
+    }
+}
+
+
+
+static void
+addImpostorReplacementSeq(Buffer *     const bufferP,
+                          const char * const seqContents) {
+/*----------------------------------------------------------------------------
+  Add to buffer *bufferP something that looks like a replacement sequence but
+  doesn't have the proper contents (the stuff between the parentheses) to be
+  one.  For example,
+
+  "#(fread x)"
+
+  seqContents[] is the contents, NUL-terminated.
+-----------------------------------------------------------------------------*/
+    const char * p;
+
+    Buffer_add(bufferP, escape);
+    Buffer_add(bufferP, '(');
+
+    for (p = &seqContents[0]; *p; ++p)
+        Buffer_add(bufferP, *p);
+
+    Buffer_add(bufferP, ')');
+}
+
+
+
+static void
+readSkeletonFile(const char *      const filename,
+                 unsigned int      const maxskl,
+                 const char **     const errorP,
+                 unsigned int *    const nSkeletonP,
+                 SkeletonObject ** const skeletonPList) {
+/*----------------------------------------------------------------------------
+-----------------------------------------------------------------------------*/
+    FILE * sklfileP;
+    SkeletonBuffer skeletonBuffer;
+        /* A buffer for accumulating skeleton objects */
+    Buffer buffer;
+        /* A buffer for accumulating binary (literal; unsubstituted) data, on
+           its way to becoming a binary skeleton object. 
+        */
+    bool eof;
+    const char * error;
+
+    SkeletonBuffer_init(&skeletonBuffer, maxskl, skeletonPList);
+
+    Buffer_init(&buffer, &skeletonBuffer);
+
+    sklfileP = pm_openr(filename);
+
+    for (eof = false, error = NULL; !eof && !error; ) {
+
+        int rc;
+
+        rc = getc(sklfileP);
+
+        if (rc == EOF)
+            eof = true;
+        else {
+            char const chr = rc;
+
+            if (chr != escape) {
+                /* Not a replacement sequence; just a literal character */
+                Buffer_add(&buffer, chr);
+            } else {
+                int rc;
+                rc = getc(sklfileP);
+                if (rc == EOF) {
+                    /* Not a replacement sequence, just an escape caharacter
+                       at the end of the file.
+                    */
+                    Buffer_add(&buffer, escape);
+                    eof = true;
+                } else {
+                    char const chr = rc;
+
+                    if (chr != '(') {
+                        /* Not a replacement sequence, just a lone escape
+                           character
+                        */
+                        Buffer_add(&buffer, escape);
+                        Buffer_add(&buffer, chr);
+                    } else {
+                        char objstr[MAX_OBJ_BUF];
+                        bool unclosed;
+                        readThroughCloseParen(sklfileP,
+                                              objstr, sizeof(objstr),
+                                              &unclosed);
+                        if (unclosed)
+                            pm_asprintf(&error, "Unclosed parentheses "
+                                        "in #() escape sequence");
+                        else {
+                            SkeletonObject * const skeletonP =
+                                newSkeletonFromReplString(objstr);
+
+                            if (skeletonP) {
+                                Buffer_flush(&buffer);
+                                SkeletonBuffer_add(&skeletonBuffer, skeletonP);
+                            } else
+                                addImpostorReplacementSeq(&buffer, objstr);
+                        }
+                    }
+                }
+            }
         }
-    } /* EOF of skeleton file */
+    }
 
-    if (slen >= 1 && line[slen-1] == '\n')
-        /* Drop finishing newline character */
-        --slen;
+    if (!error) {
+        Buffer_dropFinalNewline(&buffer);
+        Buffer_flush(&buffer);
+    }
+    *errorP = error;
+    *nSkeletonP = skeletonBuffer.nSkeleton;
 
-    SAVE_BIN(slen, line);  /* Save whatever is left */
+    fclose(sklfileP);
+}
 
-    *nsklP = nskl;
 
-    fclose(sklfile);
-    return 0;
+
+static void
+convertIt(FILE *            const ifP,
+          FILE *            const ofP,
+          SkeletonObject ** const bodySkeletonPList,
+          unsigned int      const bodyNskl,
+          SkeletonObject ** const headSkeletonPList, 
+          unsigned int      const headNskl,
+          SkeletonObject ** const tailSkeletonPList,
+          unsigned int      const tailNskl) {
+
+    pixel * pixelrow;
+    pixval maxval;
+    double dmaxval;
+    int rows, cols;
+    int format;
+    unsigned int row;
+
+    ppm_readppminit(ifP, &cols, &rows, &maxval, &format);
+
+    pixelrow = ppm_allocrow(cols);
+
+    dmaxval = (double)maxval;
+
+    /* Write header */
+    writeText(ofP, headNskl, headSkeletonPList, 
+              cols, rows , 0, 0, 0.0, 0.0, 0.0);
+
+    /* Write raster */
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+
+        ppm_readppmrow(ifP, pixelrow, cols, maxval, format);
+
+        for (col = 0; col < cols; ++col) {
+            pixel const thisPixel = pixelrow[col];
+
+            writeText(ofP, bodyNskl, bodySkeletonPList,
+                      cols, rows, col, row,
+                      PPM_GETR(thisPixel)/dmaxval,
+                      PPM_GETG(thisPixel)/dmaxval,
+                      PPM_GETB(thisPixel)/dmaxval);
+        }
+    }
+
+    /* Write trailer */
+    writeText(ofP, tailNskl, tailSkeletonPList, 
+              cols, rows, 0, 0, 0.0, 0.0, 0.0);
 }
 
 
 
-int main( argc, argv )
-int argc;
-char* argv[];
-
-{register int col;
- register pixel* xP;
- pixel* pixelrow;
- pixval maxval,red,green,blue;
- double dmaxval;
- int argn, rows, cols, format, row;
- unsigned int head_nskl,body_nskl,tail_nskl;
- SKL_OBJ *head_skl[MAX_SKL_HEAD_OBJ];
- SKL_OBJ *body_skl[MAX_SKL_BODY_OBJ];
- SKL_OBJ *tail_skl[MAX_SKL_TAIL_OBJ];
- FILE *ifp;
- const char *usage = "bodyskl [ -hd headskl ] [ -tl tailskl ] [pnmfile]";
-
- ppm_init( &argc, argv );
-
- argn = 1;
- if (argn == argc)
-   pm_usage( usage );
-                          /* Read body skeleton file */
- if (read_skeleton (argv[argn],sizeof (body_skl)/sizeof (SKL_OBJ *),
-                    &body_nskl,body_skl) < 0)
-   pm_usage ( usage );
- ++argn;
-
- head_nskl = tail_nskl = 0;
-
- while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0')
- {
-   if ( pm_keymatch ( argv[argn], "-hd", 1) && argn+1 < argc )
-   {
-     argn++;           /* Read header skeleton */
-     if (read_skeleton (argv[argn],sizeof (head_skl)/sizeof (SKL_OBJ *),
-                        &head_nskl,head_skl) < 0)
-       pm_usage ( usage );
-   }
-   else if ( pm_keymatch ( argv[argn], "-tl", 1) && argn+1 < argc )
-   {
-     argn++;           /* Read tail skeleton */
-     if (read_skeleton (argv[argn],sizeof (tail_skl)/sizeof (SKL_OBJ *),
-                        &tail_nskl,tail_skl) < 0)
-       pm_usage ( usage );
-   }
-   else
-   {
-     pm_usage ( usage );
-   }
-   argn++;
- }
-
- if ( argn != argc )
- {
-   ifp = pm_openr( argv[argn] );
-   ++argn;
- }
- else 
- {
-   ifp = stdin;
- }
-
- if ( argn != argc )
-   pm_usage( usage );
-
- ppm_readppminit( ifp, &cols, &rows, &maxval, &format );
- pixelrow = ppm_allocrow( cols );
- dmaxval = (double)maxval;
-
- if (head_nskl > 0)    /* Write header */
-   write_txt (stdout,head_nskl,head_skl,cols,rows,0,0,0.0,0.0,0.0);
-
- for ( row = 0; row < rows; ++row )
- {
-   ppm_readppmrow( ifp, pixelrow, cols, maxval, format );
-
-   for ( col = 0, xP = pixelrow; col < cols; ++col, ++xP )
-   {
-     red = PPM_GETR( *xP );
-     green = PPM_GETG( *xP );
-     blue = PPM_GETB( *xP );
-     write_txt (stdout,body_nskl,body_skl,cols,rows,col,row,
-                red/dmaxval,green/dmaxval,blue/dmaxval);
-   }
- }
-
- if (tail_nskl > 0)    /* Write trailer */
-   write_txt (stdout,tail_nskl,tail_skl,cols,rows,0,0,0.0,0.0,0.0);
-
- pm_close( ifp );
-
- exit( 0 );
+int
+main(int           argc,
+     const char ** argv) {
+    
+    struct CmdlineInfo cmdline;
+
+    unsigned int headNskl, bodyNskl, tailNskl;
+    SkeletonObject * headSkeletonPList[MAX_SKL_HEAD_OBJ];
+    SkeletonObject * bodySkeletonPList[MAX_SKL_BODY_OBJ];
+    SkeletonObject * tailSkeletonPList[MAX_SKL_TAIL_OBJ];
+    FILE * ifP;
+    const char * error;
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    readSkeletonFile(cmdline.bodySklFileName, ARRAY_SIZE(bodySkeletonPList),
+                     &error, &bodyNskl, bodySkeletonPList);
+    if (error)
+        pm_error("Invalid body skeleton file '%s'.  %s",
+                 cmdline.bodySklFileName, error);
+
+    if (cmdline.hd) {
+        readSkeletonFile(cmdline.hd, ARRAY_SIZE(headSkeletonPList),
+                         &error, &headNskl, headSkeletonPList);
+        if (error)
+            pm_error("Invalid head skeleton file '%s'.  %s",
+                     cmdline.hd, error);
+    } else
+        headNskl = 0;
+
+    if (cmdline.tl) {
+        readSkeletonFile(cmdline.tl, ARRAY_SIZE(tailSkeletonPList),
+                         &error, &tailNskl, tailSkeletonPList);
+        if (error)
+            pm_error("Invalid tail skeleton file '%s'.  %s",
+                     cmdline.tl, error);
+    } else
+        tailNskl = 0;
+
+    if (cmdline.debug)
+        dumpAllSkeleton(bodySkeletonPList, bodyNskl,
+                        headSkeletonPList, headNskl,
+                        tailSkeletonPList, tailNskl);
+
+    convertIt(ifP, stdout,
+              bodySkeletonPList, bodyNskl,
+              headSkeletonPList, headNskl,
+              tailSkeletonPList, tailNskl);
+
+    pm_close(ifP);
+
+    return 0;
 }
+
+
diff --git a/converter/ppm/ppmtobmp.c b/converter/ppm/ppmtobmp.c
index 1fc82ecb..24b1b3e5 100644
--- a/converter/ppm/ppmtobmp.c
+++ b/converter/ppm/ppmtobmp.c
@@ -63,11 +63,11 @@ freeColorMap(const colorMap * const colorMapP) {
 
 
 
-struct cmdlineInfo {
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
-    char *input_filename;
+    const char * inputFilename;
     int class;  /* C_WIN or C_OS2 */
     unsigned int bppSpec;
     unsigned int bpp;
@@ -77,7 +77,7 @@ struct cmdlineInfo {
 
 static void
 parseCommandLine(int argc, const char ** argv,
-                 struct cmdlineInfo * const cmdlineP) {
+                 struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that many of the strings that this function returns in the
    *cmdline_p structure are actually in the supplied argv array.  And
@@ -131,9 +131,9 @@ parseCommandLine(int argc, const char ** argv,
         cmdlineP->mapfile = NULL;
 
     if (argc - 1 == 0)
-        cmdlineP->input_filename = strdup("-");  /* he wants stdin */
+        cmdlineP->inputFilename = strdup("-");  /* he wants stdin */
     else if (argc - 1 == 1)
-        cmdlineP->input_filename = strdup(argv[1]);
+        cmdlineP->inputFilename = strdup(argv[1]);
     else 
         pm_error("Too many arguments.  The only argument accepted "
                  "is the input file specificaton");
@@ -915,7 +915,7 @@ int
 main(int           argc,
      const char ** argv) {
 
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
     FILE * ifP;
     int rows;
     int cols;
@@ -926,7 +926,7 @@ main(int           argc,
 
     parseCommandLine(argc, argv, &cmdline);
 
-    ifP = pm_openr(cmdline.input_filename);
+    ifP = pm_openr(cmdline.inputFilename);
     
     ppm_readppminit(ifP, &cols, &rows, &maxval, &ppmFormat);
     
diff --git a/doc/HISTORY b/doc/HISTORY
index 037b074a..cefdfa35 100644
--- a/doc/HISTORY
+++ b/doc/HISTORY
@@ -4,21 +4,45 @@ Netpbm.
 CHANGE HISTORY 
 --------------
 
-14.12.25 BJH  Release 10.68.03
+14.12.26 BJH  Release 10.69.00
+
+              pnmnorm: add -bsingle, -wsingle.
+
+              ppmtoarbtxt: Do some validation of format strings.  Thanks
+              Prophet of the Way <afu@wta.att.ne.jp>.
+
+              pamcrater: Add -verbose.
+
+              ppmtoarbtxt: Fail if a #() escape sequence runs off end of
+              file or is too long to process; before, the program would
+              treat the text from # to EOF or where the buffer filled up
+              as literal text, even ignoring any #() within.
+
+              NetBSD: show actual numbers in messages instead of "f" or
+              no information, by using NetBSD's vasprintf.
+
+              Make %g in messages display the actual number instead of "g" in
+              messages where platform doesn't have vasprintf.  (But scores of
+              %f are still left).
+
+              anytopnm: convert all images in a multi-image GIF instead of
+              just the first.
+              
+              Improve "bad magic number" message from pbmXXX, and pgmXXX, and
+              pnmXXX programs.
 
               Fix bogus message from ppmXXX programs when the input is not
               (per the magic number) a Netpbm image.  Introduced after
               Netpbm 10.26 (January 2005) but before Netpbm 10.35 (August
               2006).
 
-14.11.23 BJH  Release 10.68.02
+              ppmtoarbtxt: Fix some undefined behavior when program limits
+              are exceeded (i.e. buffer overruns).
 
               pambackground: fix bug: segfault or incorrect results in most
               cases.  Thanks Ludolf Holzheid (ludolf.holzheid@gmx.de).
               Introduced in Netpbm 10.37 (December 2006).
 
-14.09.28 BJH  Release 10.68.01
-
               Windows build: fix universal build failure with "No rule to make
               ...icon.netpbm.oLINKERISCOMPILER...".  Broken in Netpbm 10.67
               (June 2014).
@@ -48,7 +72,7 @@ CHANGE HISTORY
 
               pcdovtoppm: Fix crash due to invalid operator == on some
               systems.  Always broken (pcdovtoppm was new sometime between
-              Netpbm 9.25 (March 2002) and Netpbm 10.11 (Februrary 2010)).
+              Netpbm 9.25 (March 2002) and Netpbm 10.11 (October 2002)).
 
               Build: change _XOPEN_SOURCE from 600 back to 500 in 7 files.  It
               was changed from 500 to 600 in Subversion revision 1731 in
diff --git a/editor/pnmnorm.c b/editor/pnmnorm.c
index 70d5641a..131b39d0 100644
--- a/editor/pnmnorm.c
+++ b/editor/pnmnorm.c
@@ -51,6 +51,8 @@ struct cmdlineInfo {
     xelval wvalue;
     unsigned int wpercentSpec;
     float wpercent;
+    unsigned int bsingle;
+    unsigned int wsingle;
     float middle;
     unsigned int midvalueSpec;
     xelval midvalue;
@@ -101,6 +103,10 @@ parseCommandLine(int argc, const char ** argv,
             &cmdlineP->bvalue,     &cmdlineP->bvalueSpec, 0);
     OPTENT3(0,   "wvalue",        OPT_UINT,   
             &cmdlineP->wvalue,     &cmdlineP->wvalueSpec, 0);
+    OPTENT3(0,   "bsingle",       OPT_FLAG,   
+            NULL,                 &cmdlineP->bsingle, 0);
+    OPTENT3(0,   "wsingle",       OPT_FLAG,   
+            NULL,                 &cmdlineP->wsingle, 0);
     OPTENT3(0,   "middle",        OPT_FLOAT,   
             &cmdlineP->middle,     &middleSpec, 0);
     OPTENT3(0,   "midvalue",      OPT_UINT,   
@@ -150,6 +156,12 @@ parseCommandLine(int argc, const char ** argv,
         pm_error("You specified a per centage > 100 for bpercent: %f",
                  cmdlineP->bpercent);
 
+    if (cmdlineP->bsingle && (cmdlineP->bpercentSpec || cmdlineP->bvalueSpec))
+        pm_error("You cannot specify both -bsingle and -bpercent or -bvalue");
+
+    if (cmdlineP->wsingle && (cmdlineP->wpercentSpec || cmdlineP->wvalueSpec))
+        pm_error("You cannot specify both -wsingle and -wpercent or -wvalue");
+
     if (middleSpec) {
         if (cmdlineP->middle < 0.0 || cmdlineP->middle > 1.0)
             pm_error("-middle is a normalized brightness value; it "
@@ -250,6 +262,50 @@ buildHistogram(FILE *   const ifp,
 
 
 
+static xelval
+minimumValue(const unsigned int * const hist,
+             unsigned int         const highest) {
+
+    xelval i;
+    bool foundOne;
+
+    for (i = 0, foundOne = false; !foundOne; ) {
+        if (hist[i] > 0)
+            foundOne = true;
+        else {
+            if (i == highest)
+                pm_error("INTERNAL ERROR in '%s'.  No pixels", __FUNCTION__);
+            else
+                ++i;
+        }
+    }
+    return i;
+}
+
+
+
+static xelval
+maximumValue(const unsigned int * const hist,
+             unsigned int         const highest) {
+
+    xelval i;
+    bool foundOne;
+
+    for (i = highest, foundOne = false; !foundOne; ) {
+        if (hist[i] > 0)
+            foundOne = true;
+        else {
+            if (i == 0)
+                pm_error("INTERNAL ERROR in '%s'.  No pixels", __FUNCTION__);
+            else
+                --i;
+        }
+    }
+    return i;
+}
+
+
+
 static void
 computeBottomPercentile(unsigned int         hist[], 
                         unsigned int   const highest,
@@ -259,7 +315,7 @@ computeBottomPercentile(unsigned int         hist[],
 /*----------------------------------------------------------------------------
    Compute the lowest index of hist[] such that the sum of the hist[]
    values with that index and lower represent at least 'percent' per cent of
-   'n' (which is assumed to be the sum of all the values in hist[],
+   'total' (which is assumed to be the sum of all the values in hist[],
    given to us to save us the time of computing it).
 -----------------------------------------------------------------------------*/
     unsigned int cutoff = total * percent / 100.0;
@@ -291,7 +347,7 @@ computeTopPercentile(unsigned int         hist[],
 /*----------------------------------------------------------------------------
    Compute the highest index of hist[] such that the sum of the hist[]
    values with that index and higher represent 'percent' per cent of
-   'n' (which is assumed to be the sum of all the values in hist[],
+   'total' (which is assumed to be the sum of all the values in hist[],
    given to us to save us the time of computing it).
 -----------------------------------------------------------------------------*/
     unsigned int cutoff = total * percent / 100.0;
@@ -450,7 +506,7 @@ resolvePercentParams(FILE *             const ifP,
 /*----------------------------------------------------------------------------
    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.
+   by the -{b,w}{value,percent,single} options.
 
    These values may be invalid because of overlapping, and they may exceed
    the maximum allowed stretch; Caller must deal with that.
@@ -465,7 +521,9 @@ resolvePercentParams(FILE *             const ifP,
         buildHistogram(ifP, cols, rows, maxval, format, hist,
                        cmdline.brightMethod);
 
-        if (cmdline.bvalueSpec && !cmdline.bpercentSpec) {
+        if (cmdline.bsingle)
+            *bvalueP = minimumValue(hist, maxval);
+        else if (cmdline.bvalueSpec && !cmdline.bpercentSpec) {
             *bvalueP = cmdline.bvalue;
         } else {
             xelval percentBvalue;
@@ -477,7 +535,9 @@ resolvePercentParams(FILE *             const ifP,
                 *bvalueP = percentBvalue;
         }
 
-        if (cmdline.wvalueSpec && !cmdline.wpercentSpec) {
+        if (cmdline.wsingle)
+            *wvalueP = maximumValue(hist, maxval);
+        else if (cmdline.wvalueSpec && !cmdline.wpercentSpec) {
             *wvalueP = cmdline.wvalue;
         } else {
             xelval percentWvalue;
diff --git a/generator/pamcrater.c b/generator/pamcrater.c
index d61ce548..50745501 100644
--- a/generator/pamcrater.c
+++ b/generator/pamcrater.c
@@ -62,8 +62,10 @@ struct CmdlineInfo {
     unsigned int width;
     unsigned int randomseedSpec;
     unsigned int randomseed;
+    unsigned int verbose;
     unsigned int test;
     unsigned int radius;
+    int          offset;
 };
 
 
@@ -81,7 +83,7 @@ parseCommandLine(int argc, const char ** const argv,
     optStruct3 opt;
     unsigned int option_def_index;
 
-    unsigned int numberSpec, heightSpec, widthSpec, radiusSpec;
+    unsigned int numberSpec, heightSpec, widthSpec, radiusSpec, offsetSpec;
 
     MALLOCARRAY_NOFAIL(option_def, 100);
 
@@ -94,10 +96,14 @@ parseCommandLine(int argc, const char ** const argv,
             &widthSpec,                  0);
     OPTENT3(0,   "randomseed", OPT_UINT,    &cmdlineP->randomseed,
             &cmdlineP->randomseedSpec,   0);
+    OPTENT3(0,   "verbose",    OPT_FLAG,    NULL,
+            &cmdlineP->verbose,          0);
     OPTENT3(0,   "test",       OPT_FLAG,    NULL,
             &cmdlineP->test,       0);
     OPTENT3(0,   "radius",     OPT_UINT,    &cmdlineP->radius,
             &radiusSpec,           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 */
@@ -122,6 +128,9 @@ parseCommandLine(int argc, const char ** const argv,
     if (cmdlineP->width == 0)
         pm_error("-width must be positive");
 
+    if (!offsetSpec)
+        cmdlineP->offset=0;
+
     if (cmdlineP->test) {
         if (!radiusSpec)
             pm_error("With -test, you must specify -radius");
@@ -139,6 +148,9 @@ parseCommandLine(int argc, const char ** const argv,
         if (radiusSpec)
             pm_error("-radius is meaningful only with -test");
 
+        if (offsetSpec)
+            pm_error("-offset is meaningful only with -test");
+
         if (!numberSpec)
             cmdlineP->number = 50000;
 
@@ -149,9 +161,6 @@ parseCommandLine(int argc, const char ** const argv,
 }
 
 
-/* Definitions for obtaining random numbers. */
-
-/*  Display parameters  */
 
 static double const arand       = 32767.0;  /* Random number parameters */
 static double const CdepthPower = 1.5;      /* Crater depth power factor */
@@ -161,7 +170,9 @@ static double const DepthBias2  = 0.5;      /* Square of depth bias */
 
 static double const
 cast(double const high) {
-
+/*----------------------------------------------------------------------------
+   A random number in the range [0, 'high'].
+-----------------------------------------------------------------------------*/
     return high * ((rand() & 0x7FFF) / arand);
 }
 
@@ -196,8 +207,15 @@ terrainModP(struct pam * const pamP,
             tuple **     const terrain,
             int          const x,
             int          const y) {
+/*----------------------------------------------------------------------------
+   A pointer to the sample in 'terrain' of an image described by *pamP that is
+   at Column 'x' of Row 'y', but modulus the image size.
 
-    return &terrain[mod(y, pamP->height)][mod(x, pamP->width)][0];
+   So e.g. if the image is 10 x 10 and 'x' and 'y' are both 12, our value
+   would be a pointer to the sample at Column 2 or Row 2.  If they are both
+   -1, we would point to Column 9, Row 9.
+-----------------------------------------------------------------------------*/
+    return &terrain[mod(x, pamP->height)][mod(y, pamP->width)][0];
 }
 
 
@@ -208,25 +226,49 @@ terrainMod(struct pam * const pamP,
            tuple **     const terrain,
            int          const x,
            int          const y) {
+/*----------------------------------------------------------------------------
+   The value of the sample in 'terrain' of an image described by *pamP that is
+   at Column 'x' of Row 'y', but modulus the image size.
 
+   So e.g. if the image is 10 x 10 and 'x' and 'y' are both 12, our value
+   would be the value of the sample at Column 2 or Row 2.  If they are both
+   -1, we would return Column 9, Row 9.
+-----------------------------------------------------------------------------*/
     return *terrainModP(pamP, terrain, x, y);
 }
 
 
 
 static void
+setElev(struct pam * const pamP,
+        tuple **     const terrain,
+        int          const cx,
+        int          const cy,
+        unsigned int const elevation) {
+
+    *terrainModP(pamP, terrain, cx, cy) = MIN(pamP->maxval, elevation);
+}
+
+
+
+static void
 smallCrater(struct pam * const pamP,
             tuple **     const terrain,
             int          const cx,
             int          const cy,
-            double       const g) {
+            double       const radius) {
 /*----------------------------------------------------------------------------
    Generate a crater with a special method for tiny craters.
+
+   Center the crater at Column 'cx', Row 'cy'; wrap as necessary to get them
+   on the canvas.  These might even be negative.
 -----------------------------------------------------------------------------*/
     int y;
     unsigned int amptot;
     unsigned int npatch;
 
+    assert(radius < 3);
+
     /* Set pixel to the average of its Moore neighborhood. */
 
     for (y = cy - 1, amptot = 0, npatch = 0; y <= cy + 1; ++y) {
@@ -238,12 +280,48 @@ smallCrater(struct pam * const pamP,
     }
     {
         unsigned int const axelev = amptot / npatch;
+            /* The mean elevation of the Moore neighborhood (9 pixels
+               centered on the crater location).
+            */
         
         /* Perturb the mean elevation by a small random factor. */
 
-        int const x = g >= 1 ? ((rand() >> 8) & 3) - 1 : 0;
-        *terrainModP(pamP, terrain, cx, cy) = axelev + x;
+        int const x = radius >= 1 ? ((rand() >> 8) & 0x3) - 1 : 0;
+
+        assert(axelev > 0);
+
+        setElev(pamP, terrain, cx, cy, axelev + x);
+    }
+}
+
+
+
+static unsigned int
+meanElev(struct pam * const pamP,
+         tuple **     const terrain,
+         int          const cx,
+         int          const cy,
+         double       const radius) {
+/*----------------------------------------------------------------------------
+   The mean elevation in 'terrain', which is described by *pamP, within
+   'radius' pixels vertically and horizontally of (cx, cy).
+
+   We assume the area is a fraction the whole 'terrain'.
+-----------------------------------------------------------------------------*/
+    unsigned int amptot;
+    unsigned int npatch;
+    int y;
+
+    for (y = cy - radius, amptot = 0, npatch = 0; y <= cy + radius; ++y) {
+        int x;
+        for (x = cx - radius; x <= cx + radius; ++x) {
+            amptot += terrainMod(pamP, terrain, x, y);
+            ++npatch;
+        }
     }
+    assert(npatch > 0);
+
+    return amptot / npatch;
 }
 
 
@@ -258,39 +336,24 @@ normalCrater(struct pam * const pamP,
    Generate a regular (not tiny) crater.
 
    Generate an impact feature of the correct size and shape.
------------------------------------------------------------------------------*/
+----------------------------------------------------------------------------*/
     int    const impactRadius = (int) MAX(2, (radius / 3));
     int    const craterRadius = (int) radius;
     double const rollmin      = 0.9;
 
     int y;
-    unsigned int amptot, axelev;
-    unsigned int npatch;
 
-    /* Determine mean elevation around the impact area.
-       We assume the impact area is a fraction of the total crater size. */
-
-    for (y = cy - impactRadius, amptot = 0, npatch = 0;
-         y <= cy + impactRadius;
-         ++y) {
-        int x;
-        for (x = cx - impactRadius; x <= cx + impactRadius; ++x) {
-            amptot += terrainMod(pamP, terrain, x, y);
-            ++npatch;
-        }
-    }
-    assert(npatch > 0);
-    axelev = amptot / npatch;
+    unsigned int const axelev = meanElev(pamP, terrain, cx, cy, impactRadius);
+        /* The mean elevation of the impact area, before impact */
 
     for (y = cy - craterRadius; y <= cy + craterRadius; ++y) {
-        int const dysq = (cy - y) * (cy - y);
+        int const dysq = SQR(cy - y);
 
         int x;
 
         for (x = cx - craterRadius; x <= cx + craterRadius; ++x) {
-            int  const dxsq = (cx - x) * (cx - x);
-            double const cd = (dxsq + dysq) /
-                              (double) (craterRadius * craterRadius);
+            int  const dxsq = SQR(cx - x);
+            double const cd = (dxsq + dysq) / (double) SQR(craterRadius);
             double const cd2 = cd * 2.25;
             double const tcz = sqrt(DepthBias2) - sqrt(fabs(1 - cd2));
             double cz;
@@ -312,7 +375,7 @@ normalCrater(struct pam * const pamP,
                     (terrainMod(pamP, terrain, x, y) + cz) * roll;
                 av = MAX(1000, MIN(64000, av));
                 
-                *terrainModP(pamP, terrain, x, y) = av;
+                setElev(pamP, terrain, x, y, av);
             }
         }
     }
@@ -329,7 +392,12 @@ plopCrater(struct pam * const pamP,
            tuple **     const terrain,
            int          const cx,
            int          const cy,
-           double       const radius) {
+           double       const radius,
+           bool         const verbose) {
+
+    if (verbose && pm_have_float_format())
+        pm_message("Plopping crater at (%4d, %4d) with radius %g",
+                   cx, cy, radius);
 
     if (radius < 3)
         smallCrater (pamP, terrain, cx, cy, radius);
@@ -340,40 +408,58 @@ plopCrater(struct pam * const pamP,
 
 
 static void
+initCanvas(unsigned int const width,
+           unsigned int const height,
+           struct pam * const pamP,
+           tuple ***    const terrainP) {
+/*----------------------------------------------------------------------------
+   Initialize the output image to a flat area of middle elevation.
+-----------------------------------------------------------------------------*/
+    tuple ** terrain;    /* elevation array */
+    unsigned int row;
+
+    pamP->size   = sizeof(*pamP);
+    pamP->len    = PAM_STRUCT_SIZE(tuple_type);
+    pamP->file   = stdout;
+    pamP->format = PAM_FORMAT;
+    pamP->height = height;
+    pamP->width  = width;
+    pamP->depth  = 1;
+    pamP->maxval = 65535;
+    pamP->bytes_per_sample = 2;
+    STRSCPY(pamP->tuple_type, "elevation");
+
+    terrain = pnm_allocpamarray(pamP);
+
+    for (row = 0; row < pamP->height; ++row) {
+        unsigned int col;
+        for (col = 0; col < pamP->width; ++col)
+            terrain[row][col][0] = pamP->maxval / 2;
+    }
+    *terrainP = terrain;
+}
+
+
+
+static void
 genCraters(struct CmdlineInfo const cmdline) {
 /*----------------------------------------------------------------------------
    Generate cratered terrain
 -----------------------------------------------------------------------------*/
     tuple ** terrain;    /* elevation array */
-    unsigned int row;
     struct pam pam;
 
     /* Allocate the elevation array and initialize it to mean surface
        elevation.
     */
 
-    pam.size   = sizeof(pam);
-    pam.len    = PAM_STRUCT_SIZE(tuple_type);
-    pam.file   = stdout;
-    pam.format = PAM_FORMAT;
-    pam.height = cmdline.height;
-    pam.width  = cmdline.width;
-    pam.depth  = 1;
-    pam.maxval = 65535;
-    pam.bytes_per_sample = 2;
-    STRSCPY(pam.tuple_type, "elevation");
-
-    terrain = pnm_allocpamarray(&pam);
+    initCanvas(cmdline.width, cmdline.height, &pam, &terrain);
 
-    for (row = 0; row < pam.height; ++row) {
-        unsigned int col;
-        for (col = 0; col < pam.width; ++col)
-            terrain[row][col][0] = pam.maxval / 2;
-    }
-    
     if (cmdline.test)
         plopCrater(&pam, terrain,
-                   pam.width/2, pam.height/2, (double) cmdline.radius);
+                   pam.width/2 + cmdline.offset,
+                   pam.height/2 + cmdline.offset,
+                   (double) cmdline.radius, cmdline.verbose);
     else {
         unsigned int const ncraters = cmdline.number; /* num of craters */
         unsigned int l;
@@ -391,7 +477,7 @@ genCraters(struct CmdlineInfo const cmdline) {
             */
             double const radius = sqrt(1 / (M_PI * (1 - cast(0.9999))));
 
-            plopCrater(&pam, terrain, cx, cy, radius);
+            plopCrater(&pam, terrain, cx, cy, radius, cmdline.verbose);
 
             if (((l + 1) % 100000) == 0)
                 pm_message("%u craters generated of %u (%u%% done)",
diff --git a/generator/ppmrough.c b/generator/ppmrough.c
index 5d5bdeda..e749c9c2 100644
--- a/generator/ppmrough.c
+++ b/generator/ppmrough.c
@@ -15,6 +15,7 @@
 #include <sys/time.h>
 
 #include "pm_c_util.h"
+#include "mallocvar.h"
 #include "shhopt.h"
 #include "ppm.h"
 
@@ -38,14 +39,16 @@ struct CmdlineInfo {
 
 static void
 parseCommandLine(int argc, const char ** argv,
-                 struct CmdlineInfo * const cmdlineP)
-{
-    optEntry *option_def = malloc(100*sizeof(optEntry));
-    /* Instructions to OptParseOptions2 on how to parse our options.    */
+                 struct CmdlineInfo * const cmdlineP) {
+
+    optEntry * option_def;
+        /* Instructions to OptParseOptions2 on how to parse our options.    */
     optStruct3 opt;
 
     unsigned int option_def_index;
 
+    MALLOCARRAY(option_def, 100);
+
     option_def_index = 0;   /* incremented by OPTENTRY */
     OPTENT3(0, "width",       OPT_UINT,   &cmdlineP->width,   NULL, 0);
     OPTENT3(0, "height",      OPT_UINT,   &cmdlineP->height,  NULL, 0);
diff --git a/lib/libpbm2.c b/lib/libpbm2.c
index a8e4b0f6..a77fa0ae 100644
--- a/lib/libpbm2.c
+++ b/lib/libpbm2.c
@@ -87,10 +87,13 @@ pbm_readpbminit(FILE * const ifP,
                 int *  const rowsP,
                 int *  const formatP) {
 
-    *formatP = pm_readmagicnumber(ifP);
+    int realFormat;
 
-    switch (PAM_FORMAT_TYPE(*formatP)) {
+    realFormat = pm_readmagicnumber(ifP);
+
+    switch (PAM_FORMAT_TYPE(realFormat)) {
     case PBM_TYPE:
+        *formatP = realFormat;
         pbm_readpbminitrest(ifP, colsP, rowsP);
         break;
 
@@ -110,7 +113,8 @@ pbm_readpbminit(FILE * const ifP,
                  "to PBM with 'pamtopnm'");
         break;
     default:
-        pm_error("bad magic number - not a Netpbm file");
+        pm_error("bad magic number 0x%x - not a PPM, PGM, PBM, or PAM file",
+                 realFormat);
     }
     validateComputableSize(*colsP, *rowsP);
 }
diff --git a/lib/libpgm1.c b/lib/libpgm1.c
index 57344c16..de5aedde 100644
--- a/lib/libpgm1.c
+++ b/lib/libpgm1.c
@@ -167,7 +167,8 @@ pgm_readpgminit(FILE * const fileP,
         break;
 
     default:
-        pm_error("bad magic number - not a Netpbm file");
+        pm_error("bad magic number 0x%x - not a PPM, PGM, PBM, or PAM file",
+                 realFormat);
     }
     validateComputableSize(*colsP, *rowsP);
 }
diff --git a/lib/libpm.c b/lib/libpm.c
index 228f42c6..fa8ae4db 100644
--- a/lib/libpm.c
+++ b/lib/libpm.c
@@ -278,6 +278,19 @@ pm_error(const char format[], ...) {
 
 
 
+bool
+pm_have_float_format(void) {
+/*----------------------------------------------------------------------------
+  Return true iff %f, %e, and %g work in format strings for pm_message, etc.
+
+  Where they don't "work," that means the specifier just appears itself in
+  the formatted strings, e.g. "the number is g".
+-----------------------------------------------------------------------------*/
+    return pm_vasprintf_knows_float();
+}
+
+
+
 static void *
 mallocz(size_t const size) {
 
diff --git a/lib/libpnm1.c b/lib/libpnm1.c
index 29fee13a..045b7afc 100644
--- a/lib/libpnm1.c
+++ b/lib/libpnm1.c
@@ -129,7 +129,8 @@ pnm_readpnminit(FILE *   const fileP,
     break;
 
     default:
-        pm_error("bad magic number - not a ppm, pgm, or pbm file");
+        pm_error("bad magic number 0x%x - not a PPM, PGM, PBM, or PAM file",
+                 realFormat);
     }
     validateComputableSize(*colsP, *rowsP);
 }
diff --git a/lib/pm.h b/lib/pm.h
index 72ecc919..a24ea7bb 100644
--- a/lib/pm.h
+++ b/lib/pm.h
@@ -23,6 +23,8 @@
 #include <sys/stat.h>
 #include <fcntl.h>
 
+#include "pm_c_util.h"
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -219,6 +221,9 @@ pm_errormsg(const char format[], ...);
 void PM_GNU_PRINTF_ATTR(1,2)
 pm_error (const char reason[], ...);       
 
+bool
+pm_have_float_format(void);
+
 /* Obsolete - use shhopt and user's manual instead */
 void 
 pm_usage (const char usage[]);             
diff --git a/lib/util/matrix.c b/lib/util/matrix.c
index 5101f2c3..e9456e93 100644
--- a/lib/util/matrix.c
+++ b/lib/util/matrix.c
@@ -106,10 +106,14 @@ findLargestIthCoeff(unsigned int   const n,
             maxSoFar = thisA;
         }
     }
-    if (maxSoFar < epsilon)
-        pm_asprintf(errorP, "Matrix equation has no unique solution.  "
-                    "(debug: coeff %u %e < %e)", i, maxSoFar, epsilon);
-    else {
+    if (maxSoFar < epsilon) {
+        const char * const baseMsg = "Matrix equation has no unique solution";
+        if (pm_have_float_format())
+            pm_asprintf(errorP, "%s.  (debug: coeff %u %e < %e)",
+                        baseMsg, i, maxSoFar, epsilon);
+        else
+            pm_asprintf(errorP, "%s", baseMsg);
+    } else {
         *istarP = maxIdx;
         *errorP = NULL;
     }
diff --git a/lib/util/nstring.c b/lib/util/nstring.c
index 1fe66a27..74618422 100644
--- a/lib/util/nstring.c
+++ b/lib/util/nstring.c
@@ -221,8 +221,8 @@ pm_vsnprintf(char *       const str,
             const char *q = strchr(p + 1,'%');
             size_t n = !q ? strlen(p) : (q - p);
             if (str_l < str_m) {
-                size_t avail = str_m - str_l;
-                fast_memcpy(str + str_l, p, (n > avail ? avail : n));
+                size_t const avail = str_m - str_l;
+                fast_memcpy(str + str_l, p, (MIN(n, avail)));
             }
             p += n; str_l += n;
         } else {
@@ -231,7 +231,6 @@ pm_vsnprintf(char *       const str,
             size_t precision = 0;
             bool precision_specified;
             bool justify_left;
-            bool zero_padding;
             bool alternate_form;
             bool force_sign;
             bool space_for_positive;
@@ -252,16 +251,18 @@ pm_vsnprintf(char *       const str,
                    argument for the c conversion is unsigned.
                 */
 
-            size_t number_of_zeros_to_pad = 0;
+            bool zero_padding;
+
+            size_t number_of_zeros_to_pad;
                 /* number of zeros to be inserted for numeric
                    conversions as required by the precision or minimal
                    field width
                 */
 
-            size_t zero_padding_insertion_ind = 0;
+            size_t zero_padding_insertion_ind;
                 /* index into tmp where zero padding is to be inserted */
 
-            char fmt_spec = '\0';
+            char fmt_spec;
                 /* current conversion specifier character */
 
             str_arg = credits;
@@ -272,10 +273,14 @@ pm_vsnprintf(char *       const str,
 
             /* parse flags */
             justify_left = false;  /* initial value */
-            zero_padding = false;  /* initial value */
             alternate_form = false;  /* initial value */
             force_sign = false;  /* initial value */
             space_for_positive = false;  /* initial value */
+            zero_padding = false;  /* initial value */
+            number_of_zeros_to_pad = 0;  /* initial value */
+            zero_padding_insertion_ind = 0;  /* initial value */
+            fmt_spec = '\0';  /* initial value */
+
             while (*p == '0' || *p == '-' || *p == '+' ||
                    *p == ' ' || *p == '#' || *p == '\'') {
                 switch (*p) {
@@ -408,9 +413,7 @@ pm_vsnprintf(char *       const str,
                     else {
                         /* memchr on HP does not like n > 2^31  !!! */
                         const char * q =
-                            memchr(str_arg, '\0',
-                                   precision <= 0x7fffffff ?
-                                   precision : 0x7fffffff);
+                            memchr(str_arg, '\0', MIN(precision, 0x7fffffff));
                         str_arg_l = !q ? precision : (q-str_arg);
                     }
                     break;
@@ -570,9 +573,9 @@ pm_vsnprintf(char *       const str,
                     */
                     if (zero_padding_insertion_ind < str_arg_l &&
                         tmp[zero_padding_insertion_ind] == '-') {
-                        zero_padding_insertion_ind++;
+                        zero_padding_insertion_ind += 1;
                     }
-                    if (zero_padding_insertion_ind+1 < str_arg_l &&
+                    if (zero_padding_insertion_ind + 1 < str_arg_l &&
                         tmp[zero_padding_insertion_ind]   == '0' &&
                         (tmp[zero_padding_insertion_ind+1] == 'x' ||
                          tmp[zero_padding_insertion_ind+1] == 'X') ) {
@@ -580,7 +583,7 @@ pm_vsnprintf(char *       const str,
                     }
                 }
                 {
-                    size_t num_of_digits =
+                    size_t const num_of_digits =
                         str_arg_l - zero_padding_insertion_ind;
                     if (alternate_form && fmt_spec == 'o'
                         /* unless zero is already the first character */
@@ -606,9 +609,10 @@ pm_vsnprintf(char *       const str,
                 }
                 /* zero padding to specified minimal field width? */
                 if (!justify_left && zero_padding) {
-                    int n =
+                    int const n =
                         min_field_width - (str_arg_l+number_of_zeros_to_pad);
-                    if (n > 0) number_of_zeros_to_pad += n;
+                    if (n > 0)
+                        number_of_zeros_to_pad += n;
                 }
             } break;
             case 'f': {
@@ -654,12 +658,12 @@ pm_vsnprintf(char *       const str,
 
             if (!justify_left) {
                 /* left padding with blank or zero */
-                int n = min_field_width - (str_arg_l+number_of_zeros_to_pad);
+                int n = min_field_width - (str_arg_l + number_of_zeros_to_pad);
                 if (n > 0) {
                     if (str_l < str_m) {
-                        size_t avail = str_m-str_l;
-                        fast_memset(str+str_l, (zero_padding ? '0' : ' '),
-                                    (n > avail ? avail : n));
+                        size_t const avail = str_m - str_l;
+                        fast_memset(str + str_l, (zero_padding ? '0' : ' '),
+                                    (MIN(n, avail)));
                     }
                     str_l += n;
                 }
@@ -673,27 +677,31 @@ pm_vsnprintf(char *       const str,
                 */
                 zero_padding_insertion_ind = 0;
             } else {
-                /* insert first part of numerics (sign or '0x') before
-                   zero padding
-                */
-                int n = zero_padding_insertion_ind;
-                if (n > 0) {
-                    if (str_l < str_m) {
-                        size_t avail = str_m-str_l;
-                        fast_memcpy(str+str_l, str_arg, (n>avail?avail:n));
+                {
+                    /* insert first part of numerics (sign or '0x') before
+                       zero padding
+                    */
+                    int const n = zero_padding_insertion_ind;
+                    if (n > 0) {
+                        if (str_l < str_m) {
+                            size_t const avail = str_m - str_l;
+                            fast_memcpy(str + str_l, str_arg, (MIN(n, avail)));
+                        }
+                        str_l += n;
                     }
-                    str_l += n;
                 }
-                /* insert zero padding as requested by the precision
-                   or min field width
-                */
-                n = number_of_zeros_to_pad;
-                if (n > 0) {
-                    if (str_l < str_m) {
-                        size_t avail = str_m - str_l;
-                        fast_memset(str + str_l, '0', (n > avail ? avail : n));
+                {
+                    /* insert zero padding as requested by the precision
+                       or min field width
+                    */
+                    int const n = number_of_zeros_to_pad;
+                    if (n > 0) {
+                        if (str_l < str_m) {
+                            size_t const avail = str_m - str_l;
+                            fast_memset(str + str_l, '0', (MIN(n, avail)));
+                        }
+                        str_l += n;
                     }
-                    str_l += n;
                 }
             }
             /* insert formatted string (or as-is conversion specifier
@@ -706,7 +714,7 @@ pm_vsnprintf(char *       const str,
                         size_t const avail = str_m-str_l;
                         fast_memcpy(str + str_l,
                                     str_arg + zero_padding_insertion_ind,
-                                    (n > avail ? avail : n));
+                                    MIN(n, avail));
                     }
                     str_l += n;
                 }
@@ -714,11 +722,12 @@ pm_vsnprintf(char *       const str,
             /* insert right padding */
             if (justify_left) {
                 /* right blank padding to the field width */
-                int n = min_field_width - (str_arg_l+number_of_zeros_to_pad);
+                int const n =
+                    min_field_width - (str_arg_l + number_of_zeros_to_pad);
                 if (n > 0) {
                     if (str_l < str_m) {
-                        size_t avail = str_m-str_l;
-                        fast_memset(str+str_l, ' ', (n>avail?avail:n));
+                        size_t const avail = str_m - str_l;
+                        fast_memset(str+str_l, ' ', (MIN(n, avail)));
                     }
                     str_l += n;
                 }
@@ -730,7 +739,7 @@ pm_vsnprintf(char *       const str,
            of overwriting the last character (shouldn't happen, but
            just in case)
         */
-        str[str_l <= str_m-1 ? str_l : str_m-1] = '\0';
+        str[MIN(str_l, str_m - 1)] = '\0';
     }
     *sizeP = str_l;
 }
diff --git a/lib/util/nstring.h b/lib/util/nstring.h
index 28adda94..7238a76e 100644
--- a/lib/util/nstring.h
+++ b/lib/util/nstring.h
@@ -175,6 +175,9 @@ pm_vasprintf(const char ** const resultP,
              const char *  const format,
              va_list             args);
 
+bool
+pm_vasprintf_knows_float(void);
+
 void 
 pm_strfree(const char * const string);
 
diff --git a/lib/util/vasprintf.c b/lib/util/vasprintf.c
index e38252fa..a947f763 100644
--- a/lib/util/vasprintf.c
+++ b/lib/util/vasprintf.c
@@ -6,6 +6,8 @@
 #include <string.h>
 
 #include "pm_config.h"
+#include "pm_c_util.h"
+
 #include "nstring.h"
 
 
@@ -38,6 +40,12 @@ pm_vasprintf(const char ** const resultP,
 
        So instead, we just allocate 4K and truncate or waste as
        necessary.
+
+       Note that we don't recognize the floating point specifiers (%f, %e, %g)
+       - we render them as 'f', 'e', and 'g'.  It would be too much work to
+       make this code handle those, just for the few systems on which it runs.
+       Instead, we have pm_vasprintf_knows_float(), and any caller that cares
+       enough can avoid using these specifiers where they don't work.
     */
     size_t const allocSize = 4096;
     result = malloc(allocSize);
@@ -56,3 +64,14 @@ pm_vasprintf(const char ** const resultP,
     }
 #endif
 }
+
+
+
+bool
+pm_vasprintf_knows_float(void) {
+#if HAVE_VASPRINTF
+    return true;
+#else
+    return false;
+#endif
+}
diff --git a/pm_config.in.h b/pm_config.in.h
index ed1ebb89..1e54f06e 100644
--- a/pm_config.in.h
+++ b/pm_config.in.h
@@ -139,7 +139,7 @@
 
 /* #define HAVE_SETMODE */
 
-#if (defined(__GLIBC__) || defined(__GNU_LIBRARY__) || defined(__APPLE__))
+#if (defined(__GLIBC__) || defined(__GNU_LIBRARY__) || defined(__APPLE__)) || defined(__NetBSD__)
   #define HAVE_VASPRINTF 1
 #else
   #define HAVE_VASPRINTF 0
diff --git a/test/Test-Order b/test/Test-Order
index cc79570e..7467ba0b 100644
--- a/test/Test-Order
+++ b/test/Test-Order
@@ -55,6 +55,8 @@ pamflip2.test
 pamenlarge.test
 pnminvert.test
 pamchannel.test
+ppmchange.test
+pambackground.test
 
 pbmpscale.test
 pnmremap1.test
diff --git a/test/gif-roundtrip.test b/test/gif-roundtrip.test
index cb62bfb1..8d1a9bbe 100755
--- a/test/gif-roundtrip.test
+++ b/test/gif-roundtrip.test
@@ -27,7 +27,7 @@ test_grn=${tmpdir}/testimg.grn
 test_blu=${tmpdir}/testimg.blu
 out_red=${tmpdir}/out.red
 out_grn=${tmpdir}/out.grn
-out_blu=${tmpdir}/out.blu
+#out_blu=${tmpdir}/out.blu
 
 pamtogif ${test_red} | giftopnm > ${out_red} &&
 pamtogif ${test_grn} | giftopnm > ${out_grn} &&
@@ -35,8 +35,7 @@ pamtogif ${test_blu} | giftopnm | \
   rgb3toppm ${out_red} ${out_grn} - | \
   cksum
 
-rm ${test_ppm} ${test_grn} ${test_blu} \
-   ${out_red} ${out_grn} ${out_blu}
+rm ${test_ppm} ${test_grn} ${test_blu} ${out_red} ${out_grn}
 
 
 # Test 2. Should produce 1571496937 33838
diff --git a/test/pambackground.ok b/test/pambackground.ok
new file mode 100644
index 00000000..acb69bfa
--- /dev/null
+++ b/test/pambackground.ok
@@ -0,0 +1,3 @@
+2155020792 451
+2514682516 33871
+2667595257 17328
diff --git a/test/pambackground.test b/test/pambackground.test
new file mode 100755
index 00000000..58526fdd
--- /dev/null
+++ b/test/pambackground.test
@@ -0,0 +1,42 @@
+#! /bin/bash
+# This script tests: pambackground 
+# Also requires: pamgradient pamseq pbmmake pnmmargin pnmremap pnmtile
+
+  alias pambackground="${PBM_TESTPREFIX}pambackground"
+  alias pamgradient="${PBM_BINPREFIX}pamgradient"
+  alias pamseq="${PBM_BINPREFIX}pamseq"
+  alias pbmmake="${PBM_BINPREFIX}pbmmake"
+  alias pnmmargin="${PBM_BINPREFIX}pnmmargin"
+  alias pnmremap="${PBM_BINPREFIX}pnmremap"
+  alias pnmtile="${PBM_BINPREFIX}pnmtile"
+  shopt -s expand_aliases
+
+tmpdir=${tmpdir:-/tmp}
+
+# Test 1.
+# Should produce: 2155020792 451
+pbmmake -g 23 11 | pnmmargin -black 2 | pambackground | cksum
+
+
+# Test 2.
+# Should produce: 2514682516 33871
+ibmttl_pam=${tmpdir}/ibmttl.pam
+
+pamseq 3 1 > ${ibmttl_pam} && \
+pnmremap -quiet -mapfile ${ibmttl_pam} testimg.ppm | pambackground | cksum
+rm ${ibmttl_pam}
+
+
+# Test 3.
+# Should produce: 2667595257 17328
+pamgradient rgb:01/01/01 rgb:ff/7f/00 rgb:00/ff/7f rgb:fe/fe/fe 10 10 | \
+pnmmargin -white 2 | pnmtile 144 120 | pambackground | cksum
+
+
+# pamgradient rgb:01/01/01 rgb:ff/7f/00  rgb:00/ff/7f rgb:fe/fe/fe 10 10 | \
+# pnmmargin -white 2 | pnmtile 144 120 | cksum
+# should produce: 3147800256 51855
+# Above input image is a "wall" with 12x10 "windows".
+
+# pnmremap -mapfile ${ibmttl_pam} testimg.ppm | cksum
+# should produce : 452672747 101517
diff --git a/test/pamcrater.test b/test/pamcrater.test
index 1d182208..cc937731 100755
--- a/test/pamcrater.test
+++ b/test/pamcrater.test
@@ -1,13 +1,21 @@
 #! /bin/bash
 # This script tests: pamcrater pamshadedrelief
-# Also requires: pamslice pamvalidate
+# Also requires: pamstack pamvalidate pamcut pamflip
 
   alias pamcrater="${PBM_TESTPREFIX}pamcrater"
-  alias pamslice="${PBM_TESTPREFIX}pamshadedrelief"
-  alias pamslice="${PBM_BINPREFIX}pamslice"
-  alias pamvalidate="${PBM_TESTPREFIX}pamvalidate"
+  alias pamshadedrelief="${PBM_TESTPREFIX}pamshadedrelief"
+  alias pamstack="${PBM_BINPREFIX}pamstack"
+  alias pamvalidate="${PBM_BINPREFIX}pamvalidate"
+  alias pamcut="${PBM_BINPREFIX}pamcut"
+  alias pamflip="${PBM_BINPREFIX}pamflip"
+
   shopt -s expand_aliases
 
+# We use the undocumented --test and --radius options of pamcrater.
+# pamcrater --test --radius=N
+# The above draws a single crater of radius N.
+# The resulting image should be symmetric.
+
 tmpdir=${tmpdir:-/tmp}
 
 test_pam=${tmpdir}/test.pam
@@ -29,11 +37,11 @@ pamstack ${test10_pam} ${test50_pam} ${test100_pam} ${test150_pam} |
 
 for i in 1 10 70
   do
-    ( pamslice -row=$((128 + $i))  ${test_pam} | cksum &&
-      pamslice -row=$((128 - $i))  ${test_pam} | cksum &&
-      pamslice -col=$((128 + $i))  ${test_pam} | cksum &&
-      pamslice -col=$((128 - $i))  ${test_pam} | cksum
-    ) | uniq -c | awk '{print $1}'
+  ( pamcut -top=$((128 + $i)) -height=1 ${test_pam} | cksum &&
+    pamcut -top=$((128 - $i)) -height=1 ${test_pam} | cksum &&
+    pamcut -left=$((128 + $i)) -width=1 ${test_pam} | pamflip -xy | cksum &&
+    pamcut -left=$((128 - $i)) -width=1 ${test_pam} | pamflip -xy | cksum
+  ) | uniq -c | awk '{print $1}'
   done
 
 rm ${test_pam} ${test10_pam} ${test50_pam}
@@ -42,12 +50,12 @@ rm ${test_pam} ${test10_pam} ${test50_pam}
 
 pamshadedrelief ${test100_pam} > ${testshaded_pam}
 
-( pamslice -row=$((128 + 12))  ${testshaded_pam} | cksum &&
-  pamslice -row=$((128 - 12))  ${testshaded_pam} | cksum &&
-  pamslice -row=$((128 + 31))  ${testshaded_pam} | cksum &&
-  pamslice -row=$((128 - 31))  ${testshaded_pam} | cksum &&
-  pamslice -row=$((128 + 99))  ${testshaded_pam} | cksum &&
-  pamslice -row=$((128 - 99))  ${testshaded_pam} | cksum
+( pamcut -top=$((128 + 12)) -height=1  ${testshaded_pam} | cksum &&
+  pamcut -top=$((128 - 12)) -height=1  ${testshaded_pam} | cksum &&
+  pamcut -top=$((128 + 31)) -height=1  ${testshaded_pam} | cksum &&
+  pamcut -top=$((128 - 31)) -height=1  ${testshaded_pam} | cksum &&
+  pamcut -top=$((128 + 99)) -height=1  ${testshaded_pam} | cksum &&
+  pamcut -top=$((128 - 99)) -height=1  ${testshaded_pam} | cksum
 ) | uniq -c | awk '{print $1}'
 
 rm ${testshaded_pam} ${test100_pam}
diff --git a/test/pamenlarge.ok b/test/pamenlarge.ok
index 055262c9..a2408871 100644
--- a/test/pamenlarge.ok
+++ b/test/pamenlarge.ok
@@ -1,4 +1,4 @@
 3424505894 913236
 3763267672 304422
 3342398172 297
-237488670 3133413
+398497872 6806
diff --git a/test/pamenlarge.test b/test/pamenlarge.test
index 8f9e3c70..ced156dd 100755
--- a/test/pamenlarge.test
+++ b/test/pamenlarge.test
@@ -1,8 +1,11 @@
 #! /bin/bash
 # This script tests: pamenlarge
-# Also requires: pamchannel
+# Also requires: pamchannel pamseq pamtopnm
 
   alias pamenlarge="${PBM_TESTPREFIX}pamenlarge"
+  alias pamchannel="${PBM_TESTPREFIX}pamchannel"
+  alias pamseq="${PBM_TESTPREFIX}pamseq"
+  alias pamchannel="${PBM_TESTPREFIX}pamchannel"
   shopt -s expand_aliases
 
 # Test 1.  Should print 3424505894 913236
@@ -12,5 +15,5 @@ pamchannel -infile=testimg.ppm -tupletype="GRAYSCALE" 0 | pamtopnm | \
   pamenlarge 3 | cksum
 # Test 3.  Should print 3342398172 297
 pamenlarge 3 testgrid.pbm | cksum
-# Test 4.  Should print 237488670 3133413
-pamenlarge 3 -plain testimg.ppm | cksum
+# Test 4.  Should print 398497872 6806
+pamseq 3 4 | pamtopnm -assume | pamenlarge 3 -plain | cksum
diff --git a/test/png-roundtrip.test b/test/png-roundtrip.test
index 223103c8..dc05420a 100755
--- a/test/png-roundtrip.test
+++ b/test/png-roundtrip.test
@@ -6,8 +6,6 @@
   alias pnmtopng="${PBM_TESTPREFIX}pnmtopng"
   shopt -s expand_aliases
 
-## Fails because .ok not set yet.
-
 # Test 1.  Should print 1926073387 101484 18 times
 for flags in "" -interlace \
   -gamma=.45 \
diff --git a/test/ppmchange-roundtrip.test b/test/ppmchange-roundtrip.test
index a357870e..071c0b93 100755
--- a/test/ppmchange-roundtrip.test
+++ b/test/ppmchange-roundtrip.test
@@ -13,5 +13,5 @@ pnminvert | ppmtopgm | \
 pgmtopbm -th -val=0.5 | cksum
 
 ppmchange  black white white black testgrid.pbm | \
-ppmchange  black white white black -plain  | \
+ppmchange  black white white black | \
 ppmtopgm | pgmtopbm -th -val=0.5 | cksum
diff --git a/test/ppmchange.ok b/test/ppmchange.ok
new file mode 100644
index 00000000..130c3c45
--- /dev/null
+++ b/test/ppmchange.ok
@@ -0,0 +1,4 @@
+22488533 203
+1008787190 613
+3885709071 613
+2101746192 613
diff --git a/test/ppmchange.test b/test/ppmchange.test
new file mode 100755
index 00000000..9805be0a
--- /dev/null
+++ b/test/ppmchange.test
@@ -0,0 +1,63 @@
+#! /bin/bash
+# This script tests: ppmchange
+# Also requires: ppmrainbow pgmramp
+
+  alias ppmchange="${PBM_TESTPREFIX}ppmchange"
+  alias ppmrainbow="${PBM_BINPREFIX}ppmrainbow"
+  alias pgmramp="${PBM_BINPREFIX}pgmramp"
+  shopt -s expand_aliases
+
+#  Failure message
+## If this test fails and ppmchange-roundtrip.test succeeds,
+## the probably cause is a problem with one of the options of
+## ppmchange: -closeness or -remainder.
+
+tmpdir=${tmpdir:-/tmp}
+rainbow_ppm=${tmpdir}/rainbow.ppm
+
+# Explicit values for intermediate colors: rgb.txt may be defining them
+# in unusual ways.
+
+brown=rgb:67/43/00
+cyan=rgb:00/ff/ff
+yellow=rgb:ff/ff/00
+gray=rgb:7f/7f/7f
+
+
+# Test 1. Should print 811868957 60
+pgmramp -lr 8 8 | ppmchange black black  white white  $gray $gray \
+  -close=10 -remainder=blue | cksum
+
+
+# Test 2. Should print 1008787190 613
+
+ppmrainbow -tmpdir=$tmpdir -width=200 -height=1 red green blue | \
+  tee ${rainbow_ppm} | \
+  ppmchange red $brown   green $brown   blue $brown | cksum
+
+
+# Test 3. Should print 3885709071 613
+
+ppmchange red $brown   green $cyan   blue $yellow \
+  -closeness=25 ${rainbow_ppm} | cksum
+
+
+# Test 4. Should print 2101746192 613
+
+ppmchange red rgb:64/00/01 rgb:00/ff/00 rgb:00/32/02 blue blue \
+  -remainder=black -closeness=25 ${rainbow_ppm} | cksum
+
+rm ${rainbow_ppm}
+
+
+# cksum ${rainbow_ppm}
+# 1983174784 613 rainbow.ppm
+
+# ppmchange red rgb:64/00/01 rgb:00/ff/00 rgb:00/32/02 blue blue \
+#   -remainder=black -closeness=25  ${rainbow_ppm} | \
+#   pphist -sort=rgb -noheader
+#
+#     0     0     0	    0	     75 
+#     0     0   255	   29	     42 
+#     0    50     2	   30	     42 
+#   100     0     1	   30	     41 
diff --git a/test/ppmmix.test b/test/ppmmix.test
index 25e35f0e..9a54143f 100755
--- a/test/ppmmix.test
+++ b/test/ppmmix.test
@@ -19,12 +19,9 @@ tmpdir=${tmpdir:-/tmp}
 a1_pgm=${tmpdir}/a1.pgm
 a2_pgm=${tmpdir}/a2.pgm
 
-pbmmake -g 8 8 | \
- pgmtopgm > ${a1_pgm} &&
-pbmmake -g 2 2 | pamenlarge 4 | \
- pgmtopgm > ${a2_pgm} &&
-ppmmix 0.75 ${a1_pgm} ${a2_pgm} -plain | \
- ppmtopgm | pamdepth 3 -plain &&
+pbmmake -g 8 8 | pgmtopgm > ${a1_pgm} &&
+pbmmake -g 2 2 | pamenlarge 4 | pgmtopgm > ${a2_pgm} &&
+ppmmix 0.75 ${a1_pgm} ${a2_pgm} | ppmtopgm | pamdepth 3 -plain &&
 rm ${a1_pgm} ${a2_pgm}
 
 # Mix image with itself.
diff --git a/test/symmetry.test b/test/symmetry.test
index 5f09e91e..596b67b2 100755
--- a/test/symmetry.test
+++ b/test/symmetry.test
@@ -1,10 +1,11 @@
 #! /bin/bash
-# This script tests: pgmramp pamgauss pgmkernel pbmpscale
+# This script tests: pgmramp pamgauss pgmkernel pbmmake pbmpscale
 # Also requires: pamflip
 
   alias pgmramp="${PBM_TESTPREFIX}pgmramp"
   alias pamgauss="${PBM_TESTPREFIX}pamgauss"
   alias pgmkernel="${PBM_TESTPREFIX}pgmkernel"
+  alias pbmmake="${PBM_TESTPREFIX}pbmmake"
   alias pbmpscale="${PBM_TESTPREFIX}pbmpscale"
   alias pamflip="${PBM_BINPREFIX}pamflip"
   shopt -s expand_aliases
diff --git a/version.mk b/version.mk
index d4fb3e35..08a9e283 100644
--- a/version.mk
+++ b/version.mk
@@ -1,3 +1,3 @@
 NETPBM_MAJOR_RELEASE = 10
-NETPBM_MINOR_RELEASE = 68
-NETPBM_POINT_RELEASE = 3
+NETPBM_MINOR_RELEASE = 69
+NETPBM_POINT_RELEASE = 0