about summary refs log tree commit diff
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2017-03-28 15:34:36 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2017-03-28 15:34:36 +0000
commitca561223546a7e8acd055d5b2114869dd88f5114 (patch)
tree79a54d551eac6df794c539d8b8924aab217a1941
parent8b32628b4c65cdd2b09e877150bbc4bcdd9b6ba7 (diff)
downloadnetpbm-mirror-ca561223546a7e8acd055d5b2114869dd88f5114.tar.gz
netpbm-mirror-ca561223546a7e8acd055d5b2114869dd88f5114.tar.xz
netpbm-mirror-ca561223546a7e8acd055d5b2114869dd88f5114.zip
Copy Development as new Advanced
git-svn-id: http://svn.code.sf.net/p/netpbm/code/advanced@2932 9d0c8265-081b-0410-96cb-a4ca84ce46f8
-rw-r--r--GNUmakefile20
-rw-r--r--buildtools/debian/README11
-rwxr-xr-xbuildtools/debian/mkdeb11
-rwxr-xr-xbuildtools/installnetpbm.pl230
-rwxr-xr-xbuildtools/makeman1
-rwxr-xr-xbuildtools/stamp-date19
-rw-r--r--common.mk36
-rw-r--r--converter/other/bmptopnm.c28
-rw-r--r--converter/other/fiasco/lib/error.c8
-rw-r--r--converter/other/giftopnm.c330
-rw-r--r--converter/other/jbig/libjbig/jbig.c2
-rw-r--r--converter/other/pamtopng.c1
-rw-r--r--converter/other/pamtotiff.c92
-rw-r--r--converter/other/pnmtops.c6
-rw-r--r--converter/other/tifftopnm.c540
-rw-r--r--doc/HISTORY115
-rw-r--r--doc/INSTALL29
-rw-r--r--doc/TESTS2
-rw-r--r--editor/pnmcrop.c77
-rw-r--r--editor/ppmchange.c94
-rw-r--r--generator/ppmpat.c509
-rw-r--r--lib/libpm.c46
-rw-r--r--lib/libppmd.c5
-rw-r--r--lib/path.c157
-rw-r--r--lib/ppmdraw.h65
-rw-r--r--lib/util/nstring.h1
-rwxr-xr-xtest/Available-Testprog27
-rw-r--r--test/Test-Order4
-rw-r--r--test/fiasco-roundtrip.ok2
-rwxr-xr-xtest/fiasco-roundtrip.test20
-rw-r--r--test/jpeg-roundtrip.ok3
-rwxr-xr-xtest/jpeg-roundtrip.test21
-rw-r--r--test/jpeg2k-roundtrip.ok1
-rwxr-xr-xtest/jpeg2k-roundtrip.test7
-rw-r--r--test/ppmchange.ok9
-rwxr-xr-xtest/ppmchange.test39
-rw-r--r--test/ps-flate-roundtrip.ok3
-rwxr-xr-xtest/ps-flate-roundtrip.test48
-rw-r--r--test/ps-roundtrip.ok1
-rwxr-xr-xtest/ps-roundtrip.test14
-rw-r--r--test/tiff-flate-lzw-roundtrip.ok7
-rwxr-xr-xtest/tiff-flate-lzw-roundtrip.test33
-rw-r--r--test/tiff-roundtrip.ok4
-rwxr-xr-xtest/tiff-roundtrip.test26
-rw-r--r--version.mk4
45 files changed, 1739 insertions, 969 deletions
diff --git a/GNUmakefile b/GNUmakefile
index 4e10e12c..6f9eb41c 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -276,11 +276,9 @@ install-run: install-nonmerge
 endif
 
 .PHONY: install-merge install-nonmerge
-install-merge: install.merge install.lib install.data \
-	install.manwebmain install.manweb install.man
+install-merge: install.merge install.lib install.data
 
-install-nonmerge: install.bin install.lib install.data \
-	install.manwebmain install.manweb install.man
+install-nonmerge: install.bin install.lib install.data
 
 .PHONY: merge
 merge: lib/all netpbm
@@ -393,17 +391,6 @@ else
 install.lib:
 endif
 
-.PHONY: install.manwebmain
-install.manwebmain: $(PKGDIR)/$(PKGMANDIR)/web/netpbm.url $(PKGDIR)/bin/doc.url
-
-$(PKGDIR)/$(PKGMANDIR)/web/netpbm.url: $(PKGDIR)/$(PKGMANDIR)/web
-	echo "$(NETPBM_DOCURL)" > $@
-	chmod $(INSTALL_PERM_MAN) $@
-
-$(PKGDIR)/bin/doc.url: $(PKGDIR)/bin
-	echo "$(NETPBM_DOCURL)" > $@
-	chmod $(INSTALL_PERM_MAN) $@
-
 .PHONY: install-dev
 # Note that you might install the development package and NOT the runtime
 # package.  If you have a special system for building stuff, maybe for 
@@ -468,7 +455,8 @@ CHECK_VARS = \
 	URTLIB="$(URTLIB)" \
 	X11LIB="$(X11LIB)" \
 	XML2_LIBS="$(XML2_LIBS)" \
-	ZLIB="$(ZLIB)"
+	LEX="$(LEX)" \
+	ZLIB="$(ZLIB)" \
 
 # Test files in source tree.
 
diff --git a/buildtools/debian/README b/buildtools/debian/README
index 02fae4b5..2cf921ce 100644
--- a/buildtools/debian/README
+++ b/buildtools/debian/README
@@ -37,11 +37,11 @@ To install Netpbm as a Debian package:
 PREREQUSISITES
 --------------
 
-The following information was taken from the Wheezy version (Version 7) of
-Debian, in January 2014.
+The following information was taken from the Jessie version (Version 8) of
+Debian, in March 2017.
 
 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.
+while we list package libjpeg62-dev, the package libjpeg8-dev works fine.
 
 
 Building
@@ -54,12 +54,11 @@ indicate you don't have them, and the build will simply omit some parts.
 For example, if you don't install libx11-dev, the Netpbm build process
 will not build the 'pamx' program.
 
-  libjpeg8-dev
+  libjpeg62-dev
   libpng12-0-dev
-  libsvga1-dev
   libtiff5-dev
   libx11-dev
-  libxml2a-dev
+  libxml2-dev
   zlib1g-dev
 
 
diff --git a/buildtools/debian/mkdeb b/buildtools/debian/mkdeb
index 42a986eb..d1a88dc8 100755
--- a/buildtools/debian/mkdeb
+++ b/buildtools/debian/mkdeb
@@ -10,6 +10,10 @@
 #  package (what Debian contains is derived from Sourceforge Netpbm ca.
 #  2002).
 #
+#  The dependencies this package declares are those that can be satisfied by
+#  Debian 8 (Jessie).  Netpbm works fine on other versions of Debian, but you
+#  may have to change the dependencies in this program or ignore dependencies
+#  at install time.
 ###############################################################################
 
 use strict;
@@ -111,8 +115,8 @@ sub control($$) {
 
 # The Debian packaging system doesn't provide a way to express Netpbm's actual
 # prerequisites.  For example, Netpbm needs Version 6.2 or better of Libjpeg,
-# but there is no way to state that here.  Instead, we state Libjpeg 8.
-# This makes the Netpbm package less useful.
+# but there is no way to state that here.  Instead, we state Libjpeg 6.2
+# exactly.  This makes the Netpbm package less useful.
 
     my %control;
 
@@ -135,8 +139,7 @@ sub control($$) {
         'libc6, ' .
         'libjpeg62, ' .
         'libpng12-0, ' .
-        'libsvga1, ' .
-        'libtiff4, ' .
+        'libtiff5, ' .
         'libx11-6, ' .
         'libxml2, ' .
         'zlib1g, ' .
diff --git a/buildtools/installnetpbm.pl b/buildtools/installnetpbm.pl
index 61900335..c4a30d17 100755
--- a/buildtools/installnetpbm.pl
+++ b/buildtools/installnetpbm.pl
@@ -752,228 +752,6 @@ sub installHeader($$$) {
 
 
 
-sub getManDir($) {
-#-----------------------------------------------------------------------------
-#  Find out from the user where he wants the pointer man pages
-#  installed and return that.
-#-----------------------------------------------------------------------------
-    my ($prefix) = @_;
-
-    print("Where do you want the man pages installed?\n");
-
-    print("\n");
-
-    my $manDir;
-
-    while (!$manDir) {
-        my $default = "$prefix/man";
-
-        my $response = prompt("man page directory", $default);
-
-        if (-d($response)) {
-            $manDir = $response;
-        } else {
-            my $succeeded = mkdir($response, 0777);
-            
-            if (!$succeeded) {
-                print("Unable to create directory '$response'.  " .
-                      "Error=$ERRNO\n");
-            } else {
-                $manDir = $response;
-            }
-        }
-    }
-    print("\n");
-
-    return $manDir;
-}
-
-
-
-sub removeObsoleteManPage($) {
-
-    my ($mandir) = @_;
-
-    unlink("$mandir/man1/pgmoil");
-    unlink("$mandir/man1/pgmnorm");
-    unlink("$mandir/man1/ppmtojpeg");
-    unlink("$mandir/man1/bmptoppm");
-    unlink("$mandir/man1/ppmtonorm");
-    unlink("$mandir/man1/ppmtouil");
-    unlink("$mandir/man1/pnmnoraw");
-    unlink("$mandir/man1/gemtopbm");
-    unlink("$mandir/man1/pnminterp");
-}
-
-
-
-sub tryToCreateManwebConf($) {
-
-    my ($manweb_conf_filename) = $@;
-
-    print("You don't have a /etc/manweb.conf, which is the " .
-          "configuration\n");
-    print("file for the 'manweb' program, which is a quick way to " .
-          "get to Netpbm\n");
-    print("documentation.  Would you like to create one now?\n");
-        
-    my $done;
-    
-    while (!$done) {
-        my $response = prompt("create /etc/manweb.conf", "Y");
-        
-        if (uc($response) eq "Y") {
-            my $successful = open(MANWEB_CONF, ">/etc/manweb.conf");
-            if (!$successful) {
-                print("Unable to create file /etc/manweb.conf.  " .
-                          "error = $ERRNO\n");
-            } else {
-                print(MANWEB_CONF "#Configuration file for Manweb\n");
-                print(MANWEB_CONF "webdir=/usr/man/web\n");
-                close(MANWEB_CONF);
-                $done = $TRUE;
-            }
-        } else {
-            $done = $TRUE;
-        }
-    }
-}
-
-
-
-sub getWebdir($) {
-    my ($manweb_conf_filename) = @_;
-#-----------------------------------------------------------------------------
-#  Return the value of the Manweb "web directory," as indicated by the
-#  Manweb configuration file $manweb_conf_filename.
-#
-#  If that file doesn't exist, or doesn't have a 'webdir' value, or
-#  the 'webdir' value is a chain of directories instead of just one,
-#  we return an undefined value.
-#-----------------------------------------------------------------------------
-    my $webdir;
-
-    my $success = open(MANWEB_CONF, "<$manweb_conf_filename");
-    if (!$success) {
-        print("Unable to open file '$manweb_conf_filename' for reading.  " .
-              "error is $ERRNO\n");
-    } else {
-        while (<MANWEB_CONF>) {
-            chomp();
-            if (/^\s*#/) {
-                #It's comment - ignore
-            } elsif (/^\s*$/) {
-                #It's a blank line - ignore
-            } elsif (/\s*(\S+)\s*=\s*(\S+)/) {
-                #It looks like "keyword=value"
-                my ($keyword, $value) = ($1, $2);
-                if ($keyword eq "webdir") {
-                    # We can't handle a multi-directory path; we're looking
-                    # only for a webdir statement naming a sole directory.
-                    if ($value !~ m{:}) {
-                        $webdir = $value;
-                    }
-                }
-            }
-        }
-        close(MANWEB_CONF);
-    }              
-
-    return $webdir
-}
-
-
-
-sub userWantsManwebSymlink($$) {
-
-    my ($webdir, $netpbmWebdir) = @_;
-
-    print("Your manweb.conf file says top level documentation " .
-          "is in $webdir, \n");
-    print("but you installed netpbm.url in $netpbmWebdir.\n");
-    print("Do you want to create a symlink in $webdir now?\n");
-
-    my $wants;
-    my $done;
-    
-    while (!$done) {
-        my $response = prompt("create symlink (Y/N)", "Y");
-        
-        if (uc($response) eq "Y") {
-            $wants = $TRUE;
-            $done = $TRUE;
-        } elsif (uc($response) eq "N") {
-            $wants = $FALSE;
-            $done = $TRUE;
-        }
-    }
-    return $wants;
-}
-
-
-
-sub makeInManwebPath($) {
-
-    my ($netpbmWebdir) = @_;
-
-    # Now we need /etc/manweb.conf to point to the directory in which we
-    # just installed netpbm.url.
-
-    if (!-f("/etc/manweb.conf")) {
-        tryToCreateManwebConf("/etc/manweb.conf");
-    }
-    if (-f("/etc/manweb.conf")) {
-        my $webdir = getWebdir("/etc/manweb.conf");
-        if (defined($webdir)) {
-            if ($webdir ne $netpbmWebdir) {
-                if (userWantsManwebSymlink($webdir, $netpbmWebdir)) {
-                    my $old = "$netpbmWebdir/netpbm.url";
-                    my $new = "$webdir/netpbm.url";
-                    mkdir($webdir, 0777);
-                    my $success = symlink($old, $new);
-                    if (!$success) {
-                        print("Failed to create symbolic link from $new to " .
-                              "$old.  Error is $ERRNO\n");
-                    }
-                }
-            }
-        }
-    }
-}
-
-
-
-sub installManPage($$$) {
-
-
-# Note: This installs the pointer man pages and the netpbm.url file for Manweb.
-
-    my ($pkgdir, $prefix, $mandirR) = @_;
-
-    my $manDir = getManDir($prefix);
-
-    print("Installing man pages...\n");
-
-    my $rc = system("$cpCommand $pkgdir/man/* $manDir/");
-
-    if ($rc != 0) {
-        print("copy of man pages from $pkgdir/man to $manDir failed.\n");
-        print("cp exit code is $rc\n");
-    } else {
-        print("done.\n");
-    }
-
-    print("\n");
-
-    removeObsoleteManPage($manDir);
-
-    makeInManwebPath("$manDir/web");
-    
-    $$mandirR = $manDir;
-}
-
-
-
 sub netpbmVersion($) {
     my ($pkgdir) = @_;
 
@@ -1030,9 +808,6 @@ processTemplate($$$) {
             if (defined($infoR->{INCLUDEDIR})) {
                 s/\@INCLUDEDIR@/$infoR->{INCLUDEDIR}/;
             }
-            if (defined($infoR->{MANDIR})) {
-                s/\@MANDIR@/$infoR->{MANDIR}/;
-            }
             push(@output, $_);
         }
     }
@@ -1203,9 +978,6 @@ print("\n");
 installHeader($pkgdir, $prefix, \my $includedir);
 print("\n");
 
-installManPage($pkgdir, $prefix, \my $mandir);
-print("\n");
-
 my $templateSubsR =
     {VERSION    => netpbmVersion($pkgdir),
      BINDIR     => $bindir,
@@ -1213,7 +985,7 @@ my $templateSubsR =
      LINKDIR    => $linkdir,
      DATADIR    => $datadir,
      INCLUDEDIR => $includedir,
-     MANDIR     => $mandir};
+    };
 
 installConfig($bindir, $templateSubsR);
 
diff --git a/buildtools/makeman b/buildtools/makeman
index 13c54a70..b1d30afd 100755
--- a/buildtools/makeman
+++ b/buildtools/makeman
@@ -231,6 +231,7 @@ def makeman(name, file, indoc):
     indoc = indoc.replace("&mu;", "mu")
     indoc = indoc.replace("&sigma;", "sigma")
     # Tables
+    # This will not handle rowspan
     indoc = re.sub('(?i) *<table[^>]*>.*', ".TS", indoc)
     indoc = re.sub("(?i) *</table>.*", ".TE", indoc)
     # First the single-line case
diff --git a/buildtools/stamp-date b/buildtools/stamp-date
index 32839e94..902c82e4 100755
--- a/buildtools/stamp-date
+++ b/buildtools/stamp-date
@@ -8,16 +8,25 @@
 # copyright notice and this permission notice appear in supporting
 # documentation.  This software is provided "as is" without express or
 # implied warranty.
-#
-DATE=$(date)
+
+# SOURCE_DATE_EPOCH is an environment variable as described here:
+# https://reproducible-builds.org/specs/source-date-epoch/ on 2017.03.16.
+
+SOURCE_DATE_OR_NONE=${SOURCE_DATE_EPOCH:-NONE}
+
+BUILD_DATETIME=$(date +%s)
+
 LOGNAME_OR_UNKNOWN=${LOGNAME:-UNKNOWN}
 USER=${USER:-$LOGNAME_OR_UNKNOWN}
 if [ "$USER" = "UNKNOWN" ]; then
-    USER=`whoami`
+    USER=$(whoami)
 fi
 
-echo "/* This file tells the package when it was compiled */"
+echo "/* This file tells some facts about the building of the package */"
 echo "/* DO NOT EDIT - THIS FILE IS MAINTAINED AUTOMATICALLY */"
 echo "/* Created by the program 'stamp-date'  */"
-echo "#define COMPILE_TIME \"$DATE\""
+if [ "$SOURCE_DATE_OR_NONE" != "NONE" ]; then
+  echo "#define SOURCE_DATETIME $SOURCE_DATE_OR_NONE"
+fi
+echo "#define BUILD_DATETIME $BUILD_DATETIME"
 echo "#define COMPILED_BY \"$USER\""
diff --git a/common.mk b/common.mk
index dd7f4ed3..82f2adc6 100644
--- a/common.mk
+++ b/common.mk
@@ -509,36 +509,6 @@ $(DATAFILES:%=%_installdata): $(PKGDIR)/misc
 	  $(SRCDIR)/$(SUBDIR)/$(@:%_installdata=%) $<
 
 
-.PHONY: install.man install.man1 install.man3 install.man5
-install.man: install.man1 install.man3 install.man5 \
-	$(SUBDIRS:%=%/install.man)
-
-MANUALS1 = $(BINARIES) $(SCRIPTS)
-
-install.man1: $(MANUALS1:%=%_installman1)
-
-install.man3: $(MANUALS3:%=%_installman3)
-
-install.man5: $(MANUALS5:%=%_installman5)
-
-install.manweb: $(MANUALS1:%=%_installmanweb) $(SUBDIRS:%=%/install.manweb)
-
-%_installman1: $(PKGDIR)/$(PKGMANDIR)/man1
-	perl -w $(SRCDIR)/buildtools/makepointerman $(@:%_installman1=%) \
-          $(NETPBM_DOCURL) $< 1 $(MANPAGE_FORMAT) $(INSTALL_PERM_MAN)
-
-%_installman3: $(PKGDIR)/$(PKGMANDIR)/man3
-	perl -w $(SRCDIR)/buildtools/makepointerman $(@:%_installman3=%) \
-          $(NETPBM_DOCURL) $< 3 $(MANPAGE_FORMAT) $(INSTALL_PERM_MAN)
-
-%_installman5: $(PKGDIR)/$(PKGMANDIR)/man5
-	perl -w $(SRCDIR)/buildtools/makepointerman $(@:%_installman5=%) \
-          $(NETPBM_DOCURL) $< 5 $(MANPAGE_FORMAT) $(INSTALL_PERM_MAN)
-
-%_installmanweb: $(PKGDIR)/$(PKGMANDIR)/web
-	echo $(NETPBM_DOCURL)$(@:%_installmanweb=%).html \
-	  >$</$(@:%_installmanweb=%).url
-
 .PHONY: clean
 
 ifneq ($(EXE)x,x)
@@ -581,12 +551,6 @@ endif
 %/install.lib:
 	$(MAKE) -C $(dir $@) -f $(SRCDIR)/$(SUBDIR)/$(dir $@)Makefile \
 	    SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) $(notdir $@) 
-%/install.man:
-	$(MAKE) -C $(dir $@) -f $(SRCDIR)/$(SUBDIR)/$(dir $@)Makefile \
-	    SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) $(notdir $@) 
-%/install.manweb:
-	$(MAKE) -C $(dir $@) -f $(SRCDIR)/$(SUBDIR)/$(dir $@)Makefile \
-	    SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) $(notdir $@) 
 %/install.data:
 	$(MAKE) -C $(dir $@) -f $(SRCDIR)/$(SUBDIR)/$(dir $@)Makefile \
 	    SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) $(notdir $@) 
diff --git a/converter/other/bmptopnm.c b/converter/other/bmptopnm.c
index c39b4fd7..54868364 100644
--- a/converter/other/bmptopnm.c
+++ b/converter/other/bmptopnm.c
@@ -695,6 +695,13 @@ bmpReadinfoheader(FILE *                 const ifP,
         *errorP = NULL;
         *bytesReadP = cInfoHeaderSize;
     }
+    /* Part of our anti-arithmetic overflow strategy is to make sure height
+       and width always fit in 16 bits, so they can be multiplied together.
+       This shouldn't be a problem, since they come from 16 bit fields in
+       the BMP info header.
+    */
+    assert(headerP->cols < (1<<16));
+    assert(headerP->rows < (1<<16));
 }
 
 
@@ -1204,6 +1211,9 @@ bmpReadraster(FILE *            const ifP,
         */
     unsigned char ** bmpRaster;
 
+    assert(cols < (1<<16));
+    assert(bytesPerRow < (1<<16));
+
     bmpRaster = allocBmpRaster(rows, bytesPerRow);
 
     *bytesReadP = 0;
@@ -1456,8 +1466,8 @@ readBmp(FILE *               const ifP,
 
 static void
 writeRasterGen(unsigned char **   const bmpRaster,
-               int                const cols, 
-               int                const rows, 
+               unsigned int       const cols, 
+               unsigned int       const rows, 
                int                const format,
                unsigned int       const cBitCount, 
                struct pixelformat const pixelformat,
@@ -1492,8 +1502,8 @@ writeRasterGen(unsigned char **   const bmpRaster,
 
 static void
 writeRasterPbm(unsigned char ** const bmpRaster,
-               int              const cols, 
-               int              const rows, 
+               unsigned int     const cols, 
+               unsigned int     const rows, 
                xel              const colormap[]) {
 /*----------------------------------------------------------------------------
   Write the PBM raster to Standard Output corresponding to the raw BMP
@@ -1510,9 +1520,9 @@ writeRasterPbm(unsigned char ** const bmpRaster,
   
   We destroy *bmpRaster as a side effect.
 -----------------------------------------------------------------------------*/
-    unsigned int const colChars = pbm_packed_bytes(cols);
+    unsigned int const colCharCt = pbm_packed_bytes(cols);
     
-    int row;
+    unsigned int row;
     enum colorFormat {BlackWhite, WhiteBlack};
     enum colorFormat colorformat;
                   
@@ -1521,12 +1531,12 @@ writeRasterPbm(unsigned char ** const bmpRaster,
     else                  
         colorformat = BlackWhite;
         
-    for (row=0; row < rows; ++row){
+    for (row = 0; row < rows; ++row){
         unsigned char * const bitrow = bmpRaster[row]; 
 
         if (colorformat == BlackWhite) {
             unsigned int i;
-            for (i = 0; i < colChars; ++i) 
+            for (i = 0; i < colCharCt; ++i) 
                 bitrow[i] = ~bitrow[i]; /* flip all pixels */ 
         }   
 
@@ -1550,7 +1560,7 @@ main(int argc, const char ** argv) {
            and gray.
         */
     int cols, rows;
-    unsigned char **bmpRaster;
+    unsigned char ** bmpRaster;
         /* The raster part of the BMP image, as a row x column array, with
            each element being a raw byte from the BMP raster.  Note that
            bmpRaster[0] is really Row 0 -- the top row of the image, even
diff --git a/converter/other/fiasco/lib/error.c b/converter/other/fiasco/lib/error.c
index aeb6eaf9..08291ce0 100644
--- a/converter/other/fiasco/lib/error.c
+++ b/converter/other/fiasco/lib/error.c
@@ -84,10 +84,10 @@ set_error(const char *format, ...) {
             char * const vstring = va_arg (args, char *);
             len += strlen(vstring);
         } else if (*str == 'd') {
-            va_arg (args, int);
+            (void)va_arg(args, int);
             len += 10;
         } else if (*str == 'c') {
-            va_arg (args, int);
+            (void)va_arg(args, int);
             len += 1;
         } else
             return;
@@ -129,10 +129,10 @@ error(const char *format, ...) {
             char * const vstring = va_arg (args, char *);
             len += strlen(vstring);
         } else if (*str == 'd') {
-            va_arg (args, int);
+            (void)va_arg(args, int);
             len += 10;
         } else if (*str == 'c') {
-            va_arg (args, int);
+            (void)va_arg(args, int);
             len += 1;
         } else {
 #if HAVE_SETJMP_H
diff --git a/converter/other/giftopnm.c b/converter/other/giftopnm.c
index b0d479d5..f0946c9e 100644
--- a/converter/other/giftopnm.c
+++ b/converter/other/giftopnm.c
@@ -24,6 +24,7 @@
 #define _BSD_SOURCE   /* for strcaseeq */
 #include <string.h>
 #include <assert.h>
+#include <stdbool.h>
 
 #include "pm_config.h"
 #include "pm_c_util.h"
@@ -42,13 +43,13 @@
 #define MAX_LZW_BITS  12
 
 #ifndef   FASTPBMRENDER
-  #define FASTPBMRENDER TRUE
+  #define FASTPBMRENDER true
 #endif
 
 static const bool useFastPbmRender = FASTPBMRENDER;
 
 #ifndef   REPORTLZWCODES
-  #define REPORTLZWCODES FALSE
+  #define REPORTLZWCODES false
 #endif
 
 static const bool wantLzwCodes = REPORTLZWCODES;
@@ -61,7 +62,11 @@ readFile(FILE *          const ifP,
          unsigned char * const buffer,
          size_t          const len,
          const char **   const errorP) {
+/*----------------------------------------------------------------------------
+   Read the next 'len' bytes from *ifP into 'buffer'.
 
+   Fail if there aren't that many bytes to read.
+-----------------------------------------------------------------------------*/
     size_t bytesRead;
 
     bytesRead = fread(buffer, 1, len, ifP);
@@ -92,7 +97,7 @@ struct CmdlineInfo {
     bool allImages;  /* He wants all the images */
     unsigned int imageNum;
         /* image number he wants from input, starting at 0.  Undefined
-           if allImages is TRUE
+           if allImages is true
         */
     const char * alphaFileName;
     unsigned int quitearly;
@@ -102,7 +107,7 @@ struct CmdlineInfo {
 
 
 static void
-parseCommandLine(int argc, char ** argv,
+parseCommandLine(int argc, const char ** argv,
                  struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that the file spec array we return is stored in the storage that
@@ -136,20 +141,20 @@ parseCommandLine(int argc, char ** argv,
             &alphaSpec,                 0);
 
     opt.opt_table = option_def;
-    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
-    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+    opt.short_allowed = false;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = false;  /* We have no parms that are negative numbers */
 
-    pm_optParseOptions3( &argc, argv, opt, sizeof(opt), 0);
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
     free(option_def);
 
     if (!imageSpec) {
         cmdlineP->imageNum = 0;
-        cmdlineP->allImages = FALSE;
+        cmdlineP->allImages = false;
     } else {
         if (strcaseeq(image, "all")) { 
-            cmdlineP->allImages = TRUE;
+            cmdlineP->allImages = true;
         } else {
             char * tailptr;
 
@@ -165,7 +170,7 @@ parseCommandLine(int argc, char ** argv,
                 pm_error("Invalid value for 'image' option.  You specified "
                          "zero.  The first image is 1.");
 
-            cmdlineP->allImages = FALSE;
+            cmdlineP->allImages = false;
             cmdlineP->imageNum = (unsigned int) imageNum - 1;
         }
     }
@@ -312,8 +317,8 @@ readColorMap(FILE *        const ifP,
 
     assert(cmapSize <= MAXCOLORMAPSIZE);
 
-    *hasGrayP = FALSE;  /* initial assumption */
-    *hasColorP = FALSE;  /* initial assumption */
+    *hasGrayP = false;  /* initial assumption */
+    *hasColorP = false;  /* initial assumption */
 
     for (i = 0; i < cmapSize; ++i) {
         const char * error;
@@ -327,16 +332,16 @@ readColorMap(FILE *        const ifP,
 
         if (rgb[0] == rgb[1] && rgb[1] == rgb[2]) {
             if (rgb[0] != 0 && rgb[0] != GIFMAXVAL)
-                *hasGrayP = TRUE;
+                *hasGrayP = true;
         } else
-            *hasColorP = TRUE;
+            *hasColorP = true;
     }
     cmapP->size = cmapSize;
 }
 
 
 
-static bool zeroDataBlock = FALSE;
+static bool zeroDataBlock = false;
     /* the most recently read DataBlock was an EOD marker, i.e. had
        zero length
     */
@@ -355,10 +360,10 @@ getDataBlock(FILE *          const ifP,
    of the datablock at 'buf', and its length as *lengthP.
 
    Except that if we hit EOF or have an I/O error reading the first
-   byte (size field) of the DataBlock, we return *eofP == TRUE and
+   byte (size field) of the DataBlock, we return *eofP == true and
    *lengthP == 0.
 
-   We return *eofP == FALSE if we don't hit EOF or have an I/O error.
+   We return *eofP == false if we don't hit EOF or have an I/O error.
 
    If we hit EOF or have an I/O error reading the data portion of the
    DataBlock, we exit the program with pm_error().
@@ -375,21 +380,21 @@ getDataBlock(FILE *          const ifP,
                    error);
         pm_strfree(error);
         *errorP = NULL;
-        *eofP = TRUE;
+        *eofP = true;
         *lengthP = 0;
     } else {
         if (verbose)
             pm_message("%d byte block at Position %ld", count, pos);
-        *eofP = FALSE;
+        *eofP = false;
         *lengthP = count;
 
         if (count == 0) {
             *errorP = NULL;
-            zeroDataBlock = TRUE;
+            zeroDataBlock = true;
         } else {
             const char * error;
 
-            zeroDataBlock = FALSE;
+            zeroDataBlock = false;
             readFile(ifP, buf, count, &error); 
 
             if (error) {
@@ -417,7 +422,7 @@ readThroughEod(FILE * const ifP) {
     unsigned char buf[256];
     bool eod;
 
-    eod = FALSE;  /* initial value */
+    eod = false;  /* initial value */
     while (!eod) {
         bool eof;
         unsigned int count;
@@ -430,7 +435,7 @@ readThroughEod(FILE * const ifP) {
                        "anyway as if an EOD marker were at the end "
                        "of the file.");
         if (error || eof || count == 0)
-            eod = TRUE;
+            eod = true;
     }
 }
 
@@ -471,7 +476,7 @@ doCommentExtension(FILE * const ifP) {
     unsigned int blocklen;  
     bool done;
 
-    done = FALSE;
+    done = false;
     while (!done) {
         bool eof;
         const char * error;
@@ -480,7 +485,7 @@ doCommentExtension(FILE * const ifP) {
             pm_error("Error reading a data block in a comment extension.  %s",
                      error);
         if (blocklen == 0 || eof)
-            done = TRUE;
+            done = true;
         else {
             buf[blocklen] = '\0';
             if (showComment) {
@@ -622,7 +627,7 @@ getAnotherBlock(FILE *                const ifP,
     /* Add the next block to the buffer */
     getDataBlock(ifP, &gsP->buf[gsP->bufCount], &eof, &count, errorP);
     if (*errorP)
-        gsP->streamExhausted = TRUE;
+        gsP->streamExhausted = true;
     else {
         if (eof) {
             pm_message("EOF encountered in image "
@@ -652,7 +657,7 @@ getCode_init(struct GetCodeState * const getCodeStateP) {
     getCodeStateP->buf[1] = 0;
     getCodeStateP->bufCount = 2;
     getCodeStateP->curbit = getCodeStateP->bufCount * 8;
-    getCodeStateP->streamExhausted = FALSE;
+    getCodeStateP->streamExhausted = false;
 }
 
 
@@ -708,14 +713,14 @@ getCode_get(struct GetCodeState * const gsP,
 
   'codeSize' is the number of bits in the code we are to get.
 
-  Return *eofP == TRUE iff we hit the end of the stream.  That means a legal
+  Return *eofP == true iff we hit the end of the stream.  That means a legal
   end of stream, marked by an EOD marker, not just end of file.  An end of
   file in the middle of the GIF stream is an error.
 
   If there are bits left in the stream, but not 'codeSize' of them, we
-  call that a success with *eofP == TRUE.
+  call that a success with *eofP == true.
 
-  Return the code read (assuming *eofP == FALSE and *errorP == NULL)
+  Return the code read (assuming *eofP == false and *errorP == NULL)
   as *codeP.
 -----------------------------------------------------------------------------*/
 
@@ -737,7 +742,7 @@ getCode_get(struct GetCodeState * const gsP,
             /* The buffer still doesn't have enough bits in it; that means
                there were no data blocks left to read.
             */
-            *eofP = TRUE;
+            *eofP = true;
 
             {
                 int const bitsUnused = gsP->bufCount * 8 - gsP->curbit;
@@ -754,7 +759,7 @@ getCode_get(struct GetCodeState * const gsP,
                 pm_message("LZW code=0x%03x [%d]", *codeP, codeSize);
 
             gsP->curbit += codeSize;
-            *eofP = FALSE;
+            *eofP = false;
         }
     }
 }
@@ -824,7 +829,7 @@ termStack(struct Stack * const stackP) {
 
    LZW is an extension of Limpel-Ziv.  The two extensions are:
 
-     1) in Limpel-Ziv, codes are all the same number of bits.  In
+     1) In Limpel-Ziv, codes are all the same number of bits.  In
         LZW, they start out small and increase as the stream progresses.
 
      2) LZW has a clear code that resets the string table and code
@@ -860,7 +865,22 @@ termStack(struct Stack * const stackP) {
 
 static int const maxLzwCodeCt = (1<<MAX_LZW_BITS);
 
-struct Decompressor {
+typedef struct {
+/*----------------------------------------------------------------------------
+   An entry in the decompressor LZW code table.
+-----------------------------------------------------------------------------*/
+    unsigned int next;
+        /* The next code in the expansion after the one this entry specifies;
+           this is either another index into the LZW code table or a direct
+           code, which means it's the last data element in the expansion.
+        */
+    unsigned int dataElement;
+        /* The data element (color map index or gray level) to add to the
+           expansion for this entry.
+        */
+} CodeTableEntry;
+
+typedef struct {
     struct Stack stack;
     bool fresh;
         /* The stream is right after a clear code or at the very beginning */
@@ -894,18 +914,24 @@ struct Decompressor {
         /* We are to tolerate bad input data as best we can, rather than
            just declaring an error and bailing out.
         */
-    unsigned int table[(1 << MAX_LZW_BITS)][2];   /* LZW code table */  
-};
+    CodeTableEntry table[(1 << MAX_LZW_BITS)];   /* LZW code table */  
+        /* This contains the strings of expansions of LZW string codes, in
+           linked list form.  table[N] gives the first data element for the
+           string with string code N and the LZW code for the rest of the
+           string.  The latter is often a string code itself, which can also
+           be looked up in this table.
+        */
+} Decompressor;
 
 
 
 static void
-resetDecompressor(struct Decompressor * const decompP) {
+resetDecompressor(Decompressor * const decompP) {
 
     decompP->codeSize      = decompP->initCodeSize+1;
     decompP->maxCodeCt     = 1 << decompP->codeSize;
     decompP->nextTableSlot = decompP->maxDataVal + 3;
-    decompP->fresh         = TRUE;
+    decompP->fresh         = true;
 }
 
 
@@ -919,7 +945,7 @@ validateTransparentIndex(unsigned int const transparentIndex,
     if (transparentIndex >= cmapSize) {
         if (tolerateBadInput) {
             if (transparentIndex > maxDataVal)
-                pm_error("Invalid transparent index value: %d",
+                pm_error("Invalid transparent index value: %u",
                          transparentIndex);
         } else {
             pm_error("Invalid transparent index value %d in image with "
@@ -936,13 +962,13 @@ validateTransparentIndex(unsigned int const transparentIndex,
 
 
 static void
-lzwInit(struct Decompressor * const decompP, 
-        FILE *                const ifP,
-        int                   const initCodeSize,
-        unsigned int          const cmapSize,
-        bool                  const haveTransColor,
-        unsigned int          const transparentIndex,
-        bool                  const tolerateBadInput) {
+lzwInit(Decompressor * const decompP, 
+        FILE *         const ifP,
+        int            const initCodeSize,
+        unsigned int   const cmapSize,
+        bool           const haveTransColor,
+        unsigned int   const transparentIndex,
+        bool           const tolerateBadInput) {
 
     unsigned int const maxDataVal = (1 << initCodeSize) - 1;
     
@@ -972,8 +998,8 @@ lzwInit(struct Decompressor * const decompP,
     {
         unsigned int i;
         for (i = 0; i <= maxDataVal; ++i) {
-            decompP->table[i][0] = 0;
-            decompP->table[i][1] = i < cmapSize ? i : 0;
+            decompP->table[i].next = 0;
+            decompP->table[i].dataElement = i < cmapSize ? i : 0;
         }
     }
     decompP->haveTransColor   = haveTransColor;
@@ -987,7 +1013,7 @@ lzwInit(struct Decompressor * const decompP,
 
     getCode_init(&getCodeState);
     
-    decompP->fresh = TRUE;
+    decompP->fresh = true;
     
     initStack(&decompP->stack, maxLzwCodeCt);
 
@@ -997,8 +1023,8 @@ lzwInit(struct Decompressor * const decompP,
 
 
 static void
-lzwAdjustForPBM(struct Decompressor * const decompP,
-                GifColorMap           const cmap) {
+lzwAdjustForPBM(Decompressor * const decompP,
+                GifColorMap    const cmap) {
 /*----------------------------------------------------------------------------
   In the PBM case we use the table index value directly instead of looking up
   the color map for each pixel.
@@ -1009,13 +1035,14 @@ lzwAdjustForPBM(struct Decompressor * const decompP,
 ----------------------------------------------------------------------------*/
     unsigned int i;
     for (i = 0; i < cmap.size; ++i)
-        decompP->table[i][1] = cmap.map[i][0] == 0 ? PBM_BLACK : PBM_WHITE;
+        decompP->table[i].dataElement =
+            cmap.map[i][0] == 0 ? PBM_BLACK : PBM_WHITE;
 }
 
 
 
 static void
-lzwTerm(struct Decompressor * const decompP) {
+lzwTerm(Decompressor * const decompP) {
 
     termStack(&decompP->stack);
 }
@@ -1023,8 +1050,8 @@ lzwTerm(struct Decompressor * const decompP) {
 
 
 static void
-pushWholeStringOnStack(struct Decompressor * const decompP,
-                       unsigned int          const code0) {
+pushWholeStringOnStack(Decompressor * const decompP,
+                       unsigned int   const code0) {
 /*----------------------------------------------------------------------------
   Get the whole string that compression code 'code0' represents and push
   it onto the code stack so the leftmost code is on top.  Set
@@ -1035,21 +1062,47 @@ pushWholeStringOnStack(struct Decompressor * const decompP,
 
     for (stringCount = 0, code = code0;
          code > decompP->maxDataVal;
-         ++stringCount, code = decompP->table[code][0]
+         ++stringCount, code = decompP->table[code].next
         ) {
 
-        pushStack(&decompP->stack, decompP->table[code][1]);
+        pushStack(&decompP->stack, decompP->table[code].dataElement);
     }
-    decompP->firstcode = decompP->table[code][1];
+    decompP->firstcode = decompP->table[code].dataElement;
     pushStack(&decompP->stack, decompP->firstcode);
 }
 
 
 
 static void
-expandCodeOntoStack(struct Decompressor * const decompP,
-                    unsigned int          const incode,
-                    const char **         const errorP) {
+addLzwStringCode(Decompressor * const decompP) {
+
+    if (decompP->nextTableSlot < maxLzwCodeCt) {
+        decompP->table[decompP->nextTableSlot].next =
+            decompP->prevcode;
+        decompP->table[decompP->nextTableSlot].dataElement =
+            decompP->firstcode;
+        ++decompP->nextTableSlot;
+        if (decompP->nextTableSlot >= decompP->maxCodeCt) {
+            /* We've used up all the codes of the current code size.
+               Future codes in the stream will have codes one bit longer.
+               But there's an exception if we're already at the LZW
+               maximum, in which case the codes will simply continue
+               the same size.
+            */
+            if (decompP->codeSize < MAX_LZW_BITS) {
+                ++decompP->codeSize;
+                decompP->maxCodeCt = 1 << decompP->codeSize;
+            }
+        }
+    }
+}
+
+
+
+static void
+expandCodeOntoStack(Decompressor * const decompP,
+                    unsigned int   const incode,
+                    const char **  const errorP) {
 /*----------------------------------------------------------------------------
    'incode' is an LZW string code.  It represents a string of true data
    elements, as defined by the string translation table in *decompP.
@@ -1064,58 +1117,56 @@ expandCodeOntoStack(struct Decompressor * const decompP,
    as *errorP).
 -----------------------------------------------------------------------------*/
     unsigned int code;
+    const char * gifError;
 
-    *errorP = NULL; /* Initial value */
+    gifError = NULL; /* Initial value */
 
     if (incode <= decompP->maxDataVal) {
         if (incode < decompP->cmapSize)
             code = incode;      /* Direct index */
         else if (decompP->tolerateBadInput &&
                  decompP->haveTransColor &&
-                 decompP->table[incode][1] == decompP->transparentIndex)
+                 decompP->table[incode].dataElement ==
+                     decompP->transparentIndex)
             /* transparent code outside cmap   exceptional case */
             code = incode;
         else
-            pm_asprintf(errorP, "Error in GIF image: invalid color code %u. "
+            pm_asprintf(&gifError, "Invalid color code %u. "
                         "Valid color values are 0 - %u",
                         incode, decompP->cmapSize - 1);
-    }
-    else if (incode < decompP->nextTableSlot)  
+    } else if (incode < decompP->nextTableSlot)  
         /* LZW string, defined */
         code = incode;
-    else if  (incode == decompP->nextTableSlot && !decompP->fresh) {
+    else if (incode == decompP->nextTableSlot) {
         /* It's a code that isn't in our translation table yet.
-           This does not happen with the decoder in a fresh state.
         */
-        if (wantLzwCodes && verbose)
-            pm_message ("LZW code valid, but not in decoder table");
-
-        pushStack(&decompP->stack, decompP->firstcode);
-        code = decompP->prevcode;
+        if (decompP->fresh)
+            pm_asprintf(&gifError, "LZW string code encountered with "
+                        "decompressor in fresh state");
+        else {
+            if (wantLzwCodes && verbose)
+                pm_message ("LZW code valid, but not in decoder table");
+            
+            pushStack(&decompP->stack, decompP->firstcode);
+            code = decompP->prevcode;
+        }
     } else
-        pm_asprintf(errorP, "Error in GIF image: invalid LZW code");
-
-    if (!*errorP) {
+        pm_asprintf(&gifError, "LZW string code %u "
+                    "is neither a previously defined one nor the "
+                    "next in sequence to define (%u)",
+                    incode, decompP->nextTableSlot);
+
+    if (gifError) {
+        pm_asprintf(errorP, "INVALID GIF IMAGE: %s", gifError);
+        pm_strfree(gifError);
+    } else {
         pushWholeStringOnStack(decompP, code);
 
-        if (decompP->nextTableSlot < maxLzwCodeCt) {
-            decompP->table[decompP->nextTableSlot][0] = decompP->prevcode;
-            decompP->table[decompP->nextTableSlot][1] = decompP->firstcode;
-            ++decompP->nextTableSlot;
-            if (decompP->nextTableSlot >= decompP->maxCodeCt) {
-                /* We've used up all the codes of the current code size.
-                   Future codes in the stream will have codes one bit longer.
-                   But there's an exception if we're already at the LZW
-                   maximum, in which case the codes will simply continue
-                   the same size.
-                */
-                if (decompP->codeSize < MAX_LZW_BITS) {
-                    ++decompP->codeSize;
-                    decompP->maxCodeCt = 1 << decompP->codeSize;
-                }
-            }
-        }
+        addLzwStringCode(decompP);
+
         decompP->prevcode = incode;
+
+        *errorP = NULL;
     }
 }
 
@@ -1123,7 +1174,7 @@ expandCodeOntoStack(struct Decompressor * const decompP,
 
 static void
 lzwReadByteFresh(struct GetCodeState * const getCodeStateP,
-                 struct Decompressor * const decompP,
+                 Decompressor *        const decompP,
                  bool *                const endOfImageP,
                  unsigned char *       const dataReadP,
                  const char **         const errorP) {
@@ -1143,7 +1194,7 @@ lzwReadByteFresh(struct GetCodeState * const getCodeStateP,
 
     assert(decompP->fresh);  /* Entry requirement */
 
-    decompP->fresh = FALSE;
+    decompP->fresh = false;
 
     do {
         getCode_get(getCodeStateP, decompP->ifP, decompP->codeSize,
@@ -1152,11 +1203,11 @@ lzwReadByteFresh(struct GetCodeState * const getCodeStateP,
 
     if (!*errorP) {
         if (eof)
-            *endOfImageP = TRUE;
+            *endOfImageP = true;
         else if (code == decompP->endCode) {
             if (!zeroDataBlock)
                 readThroughEod(decompP->ifP);
-            *endOfImageP = TRUE;
+            *endOfImageP = true;
         } else if (code >= decompP->cmapSize) { 
             pm_asprintf(errorP, "Error in GIF image: invalid color code %u. "
                         "Valid color values are: 0 - %u",
@@ -1166,12 +1217,12 @@ lzwReadByteFresh(struct GetCodeState * const getCodeStateP,
             */
             decompP->prevcode = decompP->firstcode = 0;
 
-            *endOfImageP = FALSE;
+            *endOfImageP = false;
         } else {    /* valid code */
             decompP->prevcode  = code;
-            decompP->firstcode = decompP->table[code][1];
+            decompP->firstcode = decompP->table[code].dataElement;
             *dataReadP = decompP->firstcode;
-            *endOfImageP = FALSE;
+            *endOfImageP = false;
         }
     }
 }
@@ -1180,10 +1231,10 @@ lzwReadByteFresh(struct GetCodeState * const getCodeStateP,
 
 
 static void
-lzwReadByte(struct Decompressor * const decompP,
-            unsigned char *       const dataReadP,
-            bool *                const endOfImageP,
-            const char **         const errorP) {
+lzwReadByte(Decompressor *  const decompP,
+            unsigned char * const dataReadP,
+            bool *          const endOfImageP,
+            const char **   const errorP) {
 /*----------------------------------------------------------------------------
   Return the next data element of the decompressed image.  In the context
   of a GIF, a data element is the color table index of one pixel.
@@ -1202,7 +1253,7 @@ lzwReadByte(struct Decompressor * const decompP,
 -----------------------------------------------------------------------------*/
     if (!stackIsEmpty(&decompP->stack)) {
         *errorP = NULL;
-        *endOfImageP = FALSE;
+        *endOfImageP = false;
         *dataReadP = popStack(&decompP->stack);
     } else if (decompP->fresh) {
         lzwReadByteFresh(&getCodeState, decompP, endOfImageP, dataReadP,
@@ -1225,10 +1276,10 @@ lzwReadByte(struct Decompressor * const decompP,
                     if (code == decompP->endCode) {
                         if (!zeroDataBlock)
                             readThroughEod(decompP->ifP);
-                        *endOfImageP = TRUE;
+                        *endOfImageP = true;
                         *errorP = NULL;
                     } else {
-                        *endOfImageP = FALSE;
+                        *endOfImageP = false;
                         expandCodeOntoStack(decompP, code, errorP);
                         if (!*errorP)
                             *dataReadP = popStack(&decompP->stack);
@@ -1312,6 +1363,9 @@ renderRow(unsigned char *    const cmapIndexRow,
 /*----------------------------------------------------------------------------
   Convert one row of cmap indexes to PPM/PGM/PBM output.
 
+  The row is *xelrow, which is 'cols' columns wide and has pixels of format
+  'format'.
+
   Render the alpha row to *alphaFileP iff 'alphabits' is non-NULL.  If
   'haveTransColor' is false, render all white (i.e. the row is
   opaque).  'alphabits' is otherwise just a one-row buffer for us to use
@@ -1407,12 +1461,12 @@ pnmFormat(bool const hasGray,
 
 
 static void
-makePnmRow(struct Decompressor * const decompP,
-           unsigned int          const cols,
-           unsigned int          const rows,
-           bool                  const fillWithZero,
-           unsigned char *       const cmapIndexRow,
-           const char **         const errorP) {
+makePnmRow(Decompressor * const decompP,
+           unsigned int   const cols,
+           unsigned int   const rows,
+           bool           const fillWithZero,
+           unsigned char *const cmapIndexRow,
+           const char **  const errorP) {
 
     bool fillingWithZero;
     unsigned int col;
@@ -1456,15 +1510,15 @@ makePnmRow(struct Decompressor * const decompP,
 
 
 static void
-convertRaster(struct Decompressor * const decompP,
-              unsigned int          const cols,
-              unsigned int          const rows,
-              GifColorMap           const cmap, 
-              bool                  const interlace,
-              FILE *                const imageOutFileP,
-              FILE *                const alphaFileP,
-              bool                  const hasGray,
-              bool                  const hasColor) {
+convertRaster(Decompressor * const decompP,
+              unsigned int   const cols,
+              unsigned int   const rows,
+              GifColorMap    const cmap, 
+              bool           const interlace,
+              FILE *         const imageOutFileP,
+              FILE *         const alphaFileP,
+              bool           const hasGray,
+              bool           const hasColor) {
 /*----------------------------------------------------------------------------
    Read the raster from the GIF decompressor *decompP, and write it as a
    complete PNM stream (starting with the header) on *imageOutFileP and
@@ -1493,9 +1547,9 @@ convertRaster(struct Decompressor * const decompP,
     MALLOCARRAY2(cmapIndexArray, interlace ? rows : 1 , cols);
 
     if (imageOutFileP)
-        pnm_writepnminit(imageOutFileP, cols, rows, GIFMAXVAL, format, FALSE);
+        pnm_writepnminit(imageOutFileP, cols, rows, GIFMAXVAL, format, false);
     if (alphaFileP)
-        pbm_writepbminit(alphaFileP, cols, rows, FALSE);
+        pbm_writepbminit(alphaFileP, cols, rows, false);
 
     xelrow = pnm_allocrow(cols);  
     if (!xelrow)
@@ -1559,13 +1613,13 @@ convertRaster(struct Decompressor * const decompP,
 
 
 static void
-skipExtraneousData(struct Decompressor * const decompP) {
+skipExtraneousData(Decompressor * const decompP) {
 
     unsigned char byteRead;
     bool endOfImage;
     const char * error;
 
-    endOfImage = FALSE;  /* initial value */
+    endOfImage = false;  /* initial value */
 
     lzwReadByte(decompP, &byteRead, &endOfImage, &error);
 
@@ -1647,7 +1701,7 @@ readImageData(FILE *       const ifP,
               bool         const tolerateBadInput) {
 
     unsigned char lzwMinCodeSize;      
-    struct Decompressor decomp;
+    Decompressor decomp;
     const char * error;
 
     readFile(ifP, &lzwMinCodeSize, sizeof(lzwMinCodeSize), &error);
@@ -1788,7 +1842,7 @@ readExtensions(FILE*          const ifP,
    next image or GIF stream terminator.
 
    If we encounter EOD (end of GIF stream) before we find an image 
-   separator, we return *eodP == TRUE.  Else *eodP == FALSE.
+   separator, we return *eodP == true.  Else *eodP == false.
 
    If we hit end of file before an EOD marker, we fail.
 -----------------------------------------------------------------------------*/
@@ -1797,8 +1851,8 @@ readExtensions(FILE*          const ifP,
 
     *errorP = NULL;  /* initial value */
 
-    eod = FALSE;
-    imageStart = FALSE;
+    eod = false;
+    imageStart = false;
 
     /* Read the image descriptor */
     while (!imageStart && !eod && !*errorP) {
@@ -1814,7 +1868,7 @@ readExtensions(FILE*          const ifP,
             pm_strfree(error);
         } else {
             if (c == ';') {         /* GIF terminator */
-                eod = TRUE;
+                eod = true;
             } else if (c == '!') {         /* Extension */
                 unsigned char functionCode;
                 const char * error;
@@ -1830,7 +1884,7 @@ readExtensions(FILE*          const ifP,
                     doExtension(ifP, functionCode, gif89P);
                 }
             } else if (c == ',') 
-                imageStart = TRUE;
+                imageStart = true;
             else 
                 pm_message("Encountered invalid character 0x%02x while "
                            "seeking extension block, ignoring", (int)c);
@@ -1961,7 +2015,7 @@ convertImage(FILE *           const ifP,
 /*----------------------------------------------------------------------------
    Read a single GIF image from the current position of file 'ifP'.
 
-   If 'skipIt' is TRUE, don't do anything else.  Otherwise, write the
+   If 'skipIt' is true, don't do anything else.  Otherwise, write the
    image to the current position of files *imageoutFileP and *alphafileP.
    If *alphafileP is NULL, though, don't write any alpha information.
 -----------------------------------------------------------------------------*/
@@ -2016,7 +2070,7 @@ disposeOfReadExtensionsError(const char * const error,
             pm_error("Error accessing Image %u of stream.  %s",
                      imageSeq, error);
         pm_strfree(error);
-        *eodP = TRUE;
+        *eodP = true;
     }
 }
 
@@ -2037,7 +2091,7 @@ convertImages(FILE *       const ifP,
 
    'allImages' means Caller wants all the images in the stream.  
 
-   'requestedImageSeq' is meaningful only when 'allImages' is FALSE.  It 
+   'requestedImageSeq' is meaningful only when 'allImages' is false.  It 
    is the sequence number of the one image Caller wants from the stream,
    with the first image being 0.
 
@@ -2063,7 +2117,7 @@ convertImages(FILE *       const ifP,
 
     readGifHeader(ifP, &gifScreen);
 
-    for (imageSeq = 0, eod = FALSE;
+    for (imageSeq = 0, eod = false;
          !eod && (allImages || imageSeq <= requestedImageSeq || drainStream);
          ++imageSeq) {
 
@@ -2094,14 +2148,14 @@ convertImages(FILE *       const ifP,
 
 
 int
-main(int argc, char **argv) {
+main(int argc, const char **argv) {
 
     struct CmdlineInfo cmdline;
     FILE * ifP;
     FILE * alphaFileP;
     FILE * imageOutFileP;
 
-    pnm_init(&argc, argv);
+    pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
     verbose = cmdline.verbose;
diff --git a/converter/other/jbig/libjbig/jbig.c b/converter/other/jbig/libjbig/jbig.c
index 1ff867e8..0d29f770 100644
--- a/converter/other/jbig/libjbig/jbig.c
+++ b/converter/other/jbig/libjbig/jbig.c
@@ -29,8 +29,6 @@
 
 #ifdef DEBUG
 #include <stdio.h>
-#else
-#define NDEBUG
 #endif
 
 #include <stdlib.h>
diff --git a/converter/other/pamtopng.c b/converter/other/pamtopng.c
index 528184b2..b7f779ca 100644
--- a/converter/other/pamtopng.c
+++ b/converter/other/pamtopng.c
@@ -41,6 +41,7 @@
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <time.h>
 #include <png.h>
 /* setjmp.h needs to be included after png.h */
 #include <setjmp.h>
diff --git a/converter/other/pamtotiff.c b/converter/other/pamtotiff.c
index 9270fd4c..32057e55 100644
--- a/converter/other/pamtotiff.c
+++ b/converter/other/pamtotiff.c
@@ -518,7 +518,7 @@ writeScanLines(struct pam *   const pamP,
 
        The samples form pixel values according to the pixel format indicated
        by the TIFF photometric.  E.g. if it is MINISWHITE, then a pixel is
-       one sample and a value of 0 for that sample means white.
+       grayscale, composed of one sample where a value of 0 means white.
     */
     MALLOCARRAY(buf, bytesperrow);
 
@@ -578,7 +578,7 @@ analyzeColorsInRgbInput(struct pam *   const pamP,
                         CmdlineInfo    const cmdline,
                         int            const maxcolors,
                         tupletable *   const chvP,
-                        unsigned int * const colorsP,
+                        unsigned int * const colorCtP,
                         bool *         const grayscaleP) {
 /*----------------------------------------------------------------------------
    Same as analyzeColors(), except assuming input image has R/G/B tuples.
@@ -593,15 +593,15 @@ analyzeColorsInRgbInput(struct pam *   const pamP,
         pm_message("computing colormap...");
         chv = pnm_computetuplefreqtable2(pamP, NULL, maxcolors,
                                          pamP->maxval,
-                                         colorsP);
+                                         colorCtP);
         if (chv == NULL) {
             grayscale = FALSE;
         } else {
             unsigned int i;
             pm_message("%u color%s found",
-                       *colorsP, *colorsP == 1 ? "" : "s");
+                       *colorCtP, *colorCtP == 1 ? "" : "s");
             grayscale = TRUE;  /* initial assumption */
-            for (i = 0; i < *colorsP && grayscale; ++i) {
+            for (i = 0; i < *colorCtP && grayscale; ++i) {
                 if (!pnm_rgbtupleisgray(chv[i]->tuple))
                     grayscale = FALSE;
             }
@@ -635,21 +635,22 @@ analyzeColors(struct pam *   const pamP,
               CmdlineInfo    const cmdline,
               int            const maxcolors,
               tupletable *   const chvP,
-              unsigned int * const colorsP,
+              unsigned int * const colorCtP,
               bool *         const grayscaleP) {
 /*----------------------------------------------------------------------------
    Analyze the colors in the input image described by 'pamP', whose file
    is positioned to the raster.
 
-   If the colors, combined with command line options 'cmdline', indicate
-   a colormapped TIFF should be generated, return as *chvP the address
-   of a color map (in newly malloc'ed space).  If a colormapped TIFF is
-   not indicated, return *chvP == NULL.
-
    Return *grayscaleP == true iff the image should be stored as a grayscale
    image (which means the image is monochromatic and the user doesn't
    insist on color format).
 
+   If *grayscaleP is false and the colors, combined with command line options
+   'cmdline', indicate a colormapped TIFF should be generated, return as *chvP
+   the address of a color map (in newly malloc'ed space) and the number of
+   colors in it as *colorCtP.  If a colormapped color TIFF is not indicated,
+   return *chvP == NULL and nothing as *colorCtP.
+
    Leave the file position undefined.
 -----------------------------------------------------------------------------*/
     if (pamP->depth >= 3)
@@ -657,7 +658,7 @@ analyzeColors(struct pam *   const pamP,
            (tuple type RGB or RGB_ALPHA)
         */
         analyzeColorsInRgbInput(pamP, cmdline, maxcolors,
-                                chvP, colorsP, grayscaleP);
+                                chvP, colorCtP, grayscaleP);
     else {
         *chvP = NULL;
         *grayscaleP = TRUE;
@@ -667,9 +668,27 @@ analyzeColors(struct pam *   const pamP,
 
 
 static void
+reportTiffType(bool const grayscale,
+               bool const colormapped,
+               unsigned int const colorCt,
+               bool const verbose) {
+
+    if (verbose) {
+        pm_message("Generating %s TIFF", grayscale ? "grayscale" : "color");
+
+        if (colormapped)
+            pm_message("TIFF will have palette of %u colors", colorCt);
+        else
+            pm_message("TIFF will be truecolor (24 bit RGB)");
+    }
+}
+
+
+
+static void
 computeRasterParm(struct pam *     const pamP,
                   tupletable       const chv,
-                  int              const colors,
+                  int              const colorCt,
                   bool             const grayscale,
                   int              const compression,
                   bool             const minisblack,
@@ -710,14 +729,14 @@ computeRasterParm(struct pam *     const pamP,
         if (chv) {
             *samplesperpixelP = 1;  /* Pixel is just the one index value */
             *bitspersampleP =
-                colors <=   2 && indexsizeAllowed.b1 ? 1 :
-                colors <=   4 && indexsizeAllowed.b2 ? 2 :
-                colors <=  16 && indexsizeAllowed.b4 ? 4 :
-                colors <= 256 && indexsizeAllowed.b8 ? 8 :
+                colorCt <=   2 && indexsizeAllowed.b1 ? 1 :
+                colorCt <=   4 && indexsizeAllowed.b2 ? 2 :
+                colorCt <=  16 && indexsizeAllowed.b4 ? 4 :
+                colorCt <= 256 && indexsizeAllowed.b8 ? 8 :
                 0;
             if (*bitspersampleP == 0)
                 pm_error("Your -indexbits option is insufficient for the "
-                         "%d colors in this image.", colors);
+                         "%d colors in this image.", colorCt);
 
             defaultPhotometric = PHOTOMETRIC_PALETTE;
         } else {
@@ -737,11 +756,15 @@ computeRasterParm(struct pam *     const pamP,
         }
     }
 
-    if (miniswhite)
+    if (miniswhite) {
+        if (!grayscale)
+            pm_error("Image is color, so -miniswhite is invalid");
         *photometricP = PHOTOMETRIC_MINISWHITE;
-    else if (minisblack)
+    } else if (minisblack) {
+        if (!grayscale)
+            pm_error("Image is color, so -minisblack is invalid");
         *photometricP = PHOTOMETRIC_MINISBLACK;
-    else
+    } else
         *photometricP = defaultPhotometric;
 
     {
@@ -973,9 +996,9 @@ copyBufferToStdout(int const tmpfileFd) {
 
 
 static void
-destroyTiffGenerator(WriteMethod const writeMethod,
-                     TIFF *      const tifP,
-                     int         const ofd) {
+closeTiffGenerator(WriteMethod const writeMethod,
+                   TIFF *      const tifP,
+                   int         const ofd) {
 
     TIFFFlushData(tifP);
 
@@ -995,7 +1018,7 @@ static void
 createTiffColorMap(struct pam *       const pamP,
                    unsigned int       const bitspersample,
                    tupletable         const chv,
-                   unsigned int       const colors,
+                   unsigned int       const colorCt,
                    unsigned short *** const tiffColorMapP) {
 
     unsigned int const colorMapSize = 1 << bitspersample;
@@ -1010,7 +1033,7 @@ createTiffColorMap(struct pam *       const pamP,
     for (i = 0; i < colorMapSize; ++i) {
         unsigned int plane;
         for (plane = 0; plane < pamP->depth; ++plane) {
-            if (i < colors)
+            if (i < colorCt)
                 tiffColorMap[plane][i] =
                     chv[i]->tuple[plane] * 65535L / pamP->maxval;
             else
@@ -1137,7 +1160,7 @@ convertImage(FILE *       const ifP,
     tuplehash cht;
     unsigned short ** tiffColorMap;  /* malloc'ed */
     struct pam pam;
-    unsigned int colors;
+    unsigned int colorCt;
     bool grayscale;
     unsigned short photometric;
     unsigned short samplesperpixel;
@@ -1153,14 +1176,16 @@ convertImage(FILE *       const ifP,
 
     pm_tell2(ifP, &rasterPos, sizeof(rasterPos));
 
-    analyzeColors(&pam, cmdline, MAXCOLORS, &chv, &colors, &grayscale);
+    analyzeColors(&pam, cmdline, MAXCOLORS, &chv, &colorCt, &grayscale);
+
+    reportTiffType(grayscale, chv != NULL, colorCt, cmdline.verbose);
 
     /* Go back to beginning of raster */
     pm_seek2(ifP, &rasterPos, sizeof(rasterPos));
 
     /* Figure out TIFF parameters. */
 
-    computeRasterParm(&pam, chv, colors, grayscale,
+    computeRasterParm(&pam, chv, colorCt, grayscale,
                       cmdline.compression,
                       cmdline.minisblack, cmdline.miniswhite,
                       cmdline.indexsizeAllowed,
@@ -1173,10 +1198,10 @@ convertImage(FILE *       const ifP,
         cht = NULL;
         tiffColorMap = NULL;
     } else {
-        createTiffColorMap(&pam, bitspersample, chv, colors, &tiffColorMap);
+        createTiffColorMap(&pam, bitspersample, chv, colorCt, &tiffColorMap);
 
         /* Convert color vector to color hash table, for fast lookup. */
-        cht = pnm_computetupletablehash(&pam, chv, colors);
+        cht = pnm_computetupletablehash(&pam, chv, colorCt);
         pnm_freetupletable(&pam, chv);
     }
 
@@ -1194,9 +1219,6 @@ convertImage(FILE *       const ifP,
 
 
 
-
-
-
 int
 main(int argc, const char *argv[]) {
     CmdlineInfo cmdline;
@@ -1255,7 +1277,7 @@ main(int argc, const char *argv[]) {
         }
     }
 
-    destroyTiffGenerator(cmdline.writeMethod, tifP, ofd);
+    closeTiffGenerator(cmdline.writeMethod, tifP, ofd);
     pm_close(ifP);
 
     return 0;
diff --git a/converter/other/pnmtops.c b/converter/other/pnmtops.c
index c827f549..de0dfd8d 100644
--- a/converter/other/pnmtops.c
+++ b/converter/other/pnmtops.c
@@ -87,7 +87,7 @@ setSignals() {
 
 
 
-struct cmdlineInfo {
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
@@ -209,7 +209,7 @@ validateCompDimension(unsigned int const value,
 
 static void
 parseCommandLine(int argc, const char ** argv,
-                 struct cmdlineInfo * const cmdlineP) {
+                 struct CmdlineInfo * const cmdlineP) {
 
     unsigned int imagewidthSpec, imageheightSpec;
     float imagewidth, imageheight;
@@ -2035,7 +2035,7 @@ main(int argc, const char * argv[]) {
 
     FILE * ifP;
     const char * name;  /* malloc'ed */
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
 
     pm_proginit(&argc, argv);
 
diff --git a/converter/other/tifftopnm.c b/converter/other/tifftopnm.c
index aabf9d09..e72aff5e 100644
--- a/converter/other/tifftopnm.c
+++ b/converter/other/tifftopnm.c
@@ -184,10 +184,10 @@ getBps(TIFF *           const tif,
 
     unsigned short tiffBps;
     unsigned short bps;
-    int rc;
+    int fldPresent;
 
-    rc = TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &tiffBps);
-    bps = (rc == 0) ? 1 : tiffBps;
+    fldPresent = TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &tiffBps);
+    bps = fldPresent ? tiffBps : 1;
 
     if (bps < 1 || (bps > 8 && bps != 16 && bps != 32))
         pm_error("This program can process Tiff images with only "
@@ -219,26 +219,30 @@ struct tiffDirInfo {
 
 
 static void
-tiffToImageDim(unsigned int   const tiffCols,
-               unsigned int   const tiffRows,
+tiffToImageDim(unsigned int   const tiffWidth,
+               unsigned int   const tiffHeight,
                unsigned short const orientation,
                unsigned int * const imageColsP,
                unsigned int * const imageRowsP) {
-
+/*----------------------------------------------------------------------------
+   Determine the image dimensions (as *imageColsP and *imageRowsP) from the
+   width, height, and orientation of the TIFF raster ('tiffWidth',
+   'tiffHeight', and 'orientation', respectively.
+-----------------------------------------------------------------------------*/
     switch (orientation) {
     case ORIENTATION_TOPLEFT:
     case ORIENTATION_TOPRIGHT:
     case ORIENTATION_BOTRIGHT:
     case ORIENTATION_BOTLEFT:
-        *imageColsP = tiffCols;
-        *imageRowsP = tiffRows;
+        *imageColsP = tiffWidth;
+        *imageRowsP = tiffHeight;
         break;
     case ORIENTATION_LEFTTOP:
     case ORIENTATION_RIGHTTOP:
     case ORIENTATION_RIGHTBOT:
     case ORIENTATION_LEFTBOT:
-        *imageColsP = tiffRows;
-        *imageRowsP = tiffCols;
+        *imageColsP = tiffHeight;
+        *imageRowsP = tiffWidth;
         break;
     default:
         pm_error("Invalid value for orientation tag in TIFF directory: %u",
@@ -257,28 +261,96 @@ getTiffDimensions(TIFF *         const tiffP,
    dimensions of the internal raster matrix -- the dimensions of the
    actual visual image.
 -----------------------------------------------------------------------------*/
-    int ok;
+    int fldPresent;
 
     unsigned int width, length;
     unsigned short tiffOrientation;
     unsigned short orientation;
-    int present;
 
-    ok = TIFFGetField(tiffP, TIFFTAG_IMAGEWIDTH, &width);
-    if (!ok)
+    fldPresent = TIFFGetField(tiffP, TIFFTAG_IMAGEWIDTH, &width);
+    if (!fldPresent)
         pm_error("Input Tiff file is invalid.  It has no IMAGEWIDTH tag.");
-    ok = TIFFGetField(tiffP, TIFFTAG_IMAGELENGTH, &length);
-    if (!ok)
+    fldPresent = TIFFGetField(tiffP, TIFFTAG_IMAGELENGTH, &length);
+    if (!fldPresent)
         pm_error("Input Tiff file is invalid.  It has no IMAGELENGTH tag.");
 
-    present = TIFFGetField(tiffP, TIFFTAG_ORIENTATION, &tiffOrientation);
-    orientation = present ? tiffOrientation : ORIENTATION_TOPLEFT;
+    fldPresent = TIFFGetField(tiffP, TIFFTAG_ORIENTATION, &tiffOrientation);
+    orientation = fldPresent ? tiffOrientation : ORIENTATION_TOPLEFT;
 
     tiffToImageDim(width, length, orientation, colsP, rowsP);
 }
 
 
 
+static unsigned short
+planarConfigFmTiff(TIFF * const tiffP) {
+
+    int fldPresent;
+    unsigned short retval;
+
+    fldPresent = TIFFGetField(tiffP, TIFFTAG_PLANARCONFIG, &retval);
+
+    if (!fldPresent)
+        pm_error("PLANARCONFIG tag is not in Tiff file, though it "
+                 "has more than one sample per pixel.  "
+                 "TIFFGetField() of it failed.  This means the input "
+                 "is not valid Tiff.");
+
+    return retval;
+}
+
+
+
+static void
+validatePlanarConfig(unsigned short const planarconfig,
+                     unsigned short const photomet) {
+
+    switch (planarconfig) {
+    case PLANARCONFIG_CONTIG:
+        break;
+    case PLANARCONFIG_SEPARATE:
+        if (photomet != PHOTOMETRIC_RGB && 
+            photomet != PHOTOMETRIC_SEPARATED)
+            pm_error("This program can handle separate planes only "
+                     "with RGB (PHOTOMETRIC tag = %u) or SEPARATED "
+                     "(PHOTOMETRIC tag = %u) data.  The input Tiff file " 
+                     "has PHOTOMETRIC tag = %hu.",
+                     PHOTOMETRIC_RGB, PHOTOMETRIC_SEPARATED,
+                     photomet);
+        break;
+    default:
+        pm_error("Unrecognized PLANARCONFIG tag value in Tiff input: %u",
+                 planarconfig);
+    }
+}
+
+
+
+static unsigned short
+orientationFmTiff(TIFF * const tiffP) {
+
+    unsigned short tiffOrientation;
+    int fldPresent;
+
+    fldPresent = TIFFGetField(tiffP, TIFFTAG_ORIENTATION, &tiffOrientation);
+
+    return fldPresent ? tiffOrientation : ORIENTATION_TOPLEFT;
+}
+
+
+
+static void
+dumpHeader(const struct tiffDirInfo * const headerP) {
+
+    pm_message("%ux%ux%u raster matrix, oriented %u",
+               headerP->width, headerP->height,
+               headerP->bps * headerP->spp, headerP->orientation);
+    pm_message("%hu bits/sample, %hu samples/pixel",
+               headerP->bps, headerP->spp);
+}
+
+
+
 static void 
 readDirectory(TIFF *               const tiffP,
               bool                 const headerdump,
@@ -287,7 +359,7 @@ readDirectory(TIFF *               const tiffP,
    Read various values of TIFF tags from the TIFF directory, and
    default them if not in there and make guesses where values are
    invalid.  Exit program with error message if required tags aren't
-   there or values are inconsistent or beyond our capabilities.  if
+   there or values are inconsistent or beyond our capabilities.  If
    'headerdump' is true, issue informational messages about what we
    find.
 
@@ -295,7 +367,7 @@ readDirectory(TIFF *               const tiffP,
    input file contains invalid values).  We generally return those
    invalid values to our caller.
 -----------------------------------------------------------------------------*/
-    int rc;
+    int fldPresent;
     unsigned short tiffSpp;
 
     if (headerdump)
@@ -303,65 +375,35 @@ readDirectory(TIFF *               const tiffP,
 
     getBps(tiffP, &headerP->bps);
 
-    rc = TIFFGetFieldDefaulted(tiffP, TIFFTAG_FILLORDER, &headerP->fillorder);
-    rc = TIFFGetField(tiffP, TIFFTAG_SAMPLESPERPIXEL, &tiffSpp);
-    headerP->spp = (rc == 0) ? 1 : tiffSpp;
+    fldPresent =
+        TIFFGetFieldDefaulted(tiffP, TIFFTAG_FILLORDER, &headerP->fillorder);
+    fldPresent = TIFFGetField(tiffP, TIFFTAG_SAMPLESPERPIXEL, &tiffSpp);
+    headerP->spp = fldPresent ? tiffSpp: 1;
 
-    rc = TIFFGetField(tiffP, TIFFTAG_PHOTOMETRIC, &headerP->photomet);
-    if (rc == 0)
+    fldPresent = TIFFGetField(tiffP, TIFFTAG_PHOTOMETRIC, &headerP->photomet);
+    if (!fldPresent)
         pm_error("PHOTOMETRIC tag is not in Tiff file.  "
                  "TIFFGetField() of it failed.\n"
                  "This means the input is not valid Tiff.");
 
-    if (headerP->spp > 1) {
-        rc = TIFFGetField(tiffP, TIFFTAG_PLANARCONFIG, &headerP->planarconfig);
-        if (rc == 0)
-            pm_error("PLANARCONFIG tag is not in Tiff file, though it "
-                     "has more than one sample per pixel.  "
-                     "TIFFGetField() of it failed.  This means the input "
-                     "is not valid Tiff.");
-    } else
+    if (headerP->spp > 1)
+        headerP->planarconfig = planarConfigFmTiff(tiffP);
+    else
         headerP->planarconfig = PLANARCONFIG_CONTIG;
 
-    switch (headerP->planarconfig) {
-    case PLANARCONFIG_CONTIG:
-        break;
-    case PLANARCONFIG_SEPARATE:
-        if (headerP->photomet != PHOTOMETRIC_RGB && 
-            headerP->photomet != PHOTOMETRIC_SEPARATED)
-            pm_error("This program can handle separate planes only "
-                     "with RGB (PHOTOMETRIC tag = %u) or SEPARATED "
-                     "(PHOTOMETRIC tag = %u) data.  The input Tiff file " 
-                     "has PHOTOMETRIC tag = %hu.",
-                     PHOTOMETRIC_RGB, PHOTOMETRIC_SEPARATED,
-                     headerP->photomet);
-        break;
-    default:
-        pm_error("Unrecognized PLANARCONFIG tag value in Tiff input: %u.\n",
-                 headerP->planarconfig);
-    }
+    validatePlanarConfig(headerP->planarconfig, headerP->photomet);
 
-    rc = TIFFGetField(tiffP, TIFFTAG_IMAGEWIDTH, &headerP->width);
-    if (rc == 0)
+    fldPresent = TIFFGetField(tiffP, TIFFTAG_IMAGEWIDTH, &headerP->width);
+    if (!fldPresent)
         pm_error("Input Tiff file is invalid.  It has no IMAGEWIDTH tag.");
-    rc = TIFFGetField(tiffP, TIFFTAG_IMAGELENGTH, &headerP->height);
-    if (rc == 0)
+    fldPresent = TIFFGetField(tiffP, TIFFTAG_IMAGELENGTH, &headerP->height);
+    if (!fldPresent)
         pm_error("Input Tiff file is invalid.  It has no IMAGELENGTH tag.");
 
-    {
-        unsigned short tiffOrientation;
-        int present;
-        present = TIFFGetField(tiffP, TIFFTAG_ORIENTATION, &tiffOrientation);
-        headerP->orientation =
-            present ? tiffOrientation : ORIENTATION_TOPLEFT;
-    }
-    if (headerdump) {
-        pm_message("%ux%ux%u raster matrix, oriented %u",
-                   headerP->width, headerP->height,
-                   headerP->bps * headerP->spp, headerP->orientation);
-        pm_message("%hu bits/sample, %hu samples/pixel",
-                   headerP->bps, headerP->spp);
-    }
+    headerP->orientation = orientationFmTiff(tiffP);
+
+    if (headerdump)
+        dumpHeader(headerP);
 }
 
 
@@ -535,7 +577,7 @@ computeFillorder(unsigned short   const fillorderTag,
 
 
 static void
-analyzeImageType(TIFF *             const tif, 
+analyzeImageType(TIFF *             const tiffP, 
                  unsigned short     const bps, 
                  unsigned short     const spp, 
                  unsigned short     const photomet,
@@ -544,129 +586,147 @@ analyzeImageType(TIFF *             const tif,
                  xel *              const colormap,
                  bool               const headerdump,
                  struct CmdlineInfo const cmdline) {
+/*----------------------------------------------------------------------------
+   Determine from the TIFF header in *tif certain properties of the image
+   as well as the proper format of PNM image for the conversion.
 
-    bool grayscale; 
-
-        /* How come we don't deal with the photometric for the monochrome 
-           case (make sure it's one we know)?  -Bryan 00.03.04
-        */
-        switch (photomet) {
-        case PHOTOMETRIC_MINISBLACK:
-        case PHOTOMETRIC_MINISWHITE:
-            if (spp != 1)
-                pm_error("This grayscale image has %d samples per pixel.  "
-                         "We understand only 1.", spp);
-            grayscale = TRUE;
-            *maxvalP = pm_bitstomaxval(MIN(bps,16));
-            if (headerdump)
-                pm_message("grayscale image, (min=%s) output maxval %u ", 
-                           photomet == PHOTOMETRIC_MINISBLACK ? 
-                           "black" : "white",
-                           *maxvalP
-                           );
-            break;
+   *formatP and *maxvalP are the basic PNM parameters.
+-----------------------------------------------------------------------------*/
+    switch (photomet) {
+    case PHOTOMETRIC_MINISBLACK:
+    case PHOTOMETRIC_MINISWHITE:
+        if (spp != 1)
+            pm_error("This grayscale image has %d samples per pixel.  "
+                     "We understand only 1.", spp);
+
+        *formatP = bps == 1 ? PBM_TYPE : PGM_TYPE;
+
+        *maxvalP = pm_bitstomaxval(MIN(bps, 16));
+
+        if (headerdump)
+            pm_message("grayscale image, (min=%s) output maxval %u ", 
+                       photomet == PHOTOMETRIC_MINISBLACK ? 
+                       "black" : "white",
+                       *maxvalP
+                );
+        break;
             
-        case PHOTOMETRIC_PALETTE: {
-            int i;
-            int numcolors;
-            unsigned short* redcolormap;
-            unsigned short* greencolormap;
-            unsigned short* bluecolormap;
-
-            if (headerdump)
-                pm_message("colormapped");
-
-            if (spp != 1)
-                pm_error("This paletted image has %d samples per pixel.  "
-                         "We understand only 1.", spp);
-
-            if (!TIFFGetField(tif, TIFFTAG_COLORMAP, 
-                              &redcolormap, &greencolormap, &bluecolormap))
-                pm_error("error getting colormaps");
-
-            numcolors = 1 << bps;
-            if (numcolors > MAXCOLORS)
-                pm_error("too many colors");
-            *maxvalP = PNM_MAXMAXVAL;
-            grayscale = FALSE;
-            for (i = 0; i < numcolors; ++i) {
-                xelval r, g, b;
-                r = (long) redcolormap[i] * PNM_MAXMAXVAL / 65535L;
-                g = (long) greencolormap[i] * PNM_MAXMAXVAL / 65535L;
-                b = (long) bluecolormap[i] * PNM_MAXMAXVAL / 65535L;
-                PPM_ASSIGN(colormap[i], r, g, b);
-            }
+    case PHOTOMETRIC_PALETTE: {
+        int fldPresent;
+        int i;
+        int numcolors;
+        unsigned short* redcolormap;
+        unsigned short* greencolormap;
+        unsigned short* bluecolormap;
+
+        if (headerdump)
+            pm_message("colormapped");
+
+        if (spp != 1)
+            pm_error("This paletted image has %d samples per pixel.  "
+                     "We understand only 1.", spp);
+
+        fldPresent = TIFFGetField(
+            tiffP, TIFFTAG_COLORMAP, 
+            &redcolormap, &greencolormap, &bluecolormap);
+
+        if (!fldPresent)
+            pm_error("error getting colormaps");
+
+        numcolors = 1 << bps;
+        if (numcolors > MAXCOLORS)
+            pm_error("too many colors");
+
+        *formatP = PPM_TYPE;
+
+        *maxvalP = PNM_MAXMAXVAL;
+
+        for (i = 0; i < numcolors; ++i) {
+            xelval r, g, b;
+            r = (long) redcolormap[i] * PNM_MAXMAXVAL / 65535L;
+            g = (long) greencolormap[i] * PNM_MAXMAXVAL / 65535L;
+            b = (long) bluecolormap[i] * PNM_MAXMAXVAL / 65535L;
+            PPM_ASSIGN(colormap[i], r, g, b);
         }
+    }
         break;
 
-        case PHOTOMETRIC_SEPARATED: {
-            unsigned short inkset;
-
-            if (headerdump)
-                pm_message("color separation");
-            if (TIFFGetField(tif, TIFFTAG_INKNAMES, &inkset) == 1
-                && inkset != INKSET_CMYK)
-            if (inkset != INKSET_CMYK) 
-                pm_error("This color separation file uses an inkset (%d) "
-                         "we can't handle.  We handle only CMYK.", inkset);
-            if (spp != 4) 
-                pm_error("This CMYK color separation file is %d samples per "
-                         "pixel.  "
-                         "We need 4 samples, though: C, M, Y, and K.  ",
-                         spp);
-            grayscale = FALSE;
-            *maxvalP = (1 << bps) - 1;
-        }
+    case PHOTOMETRIC_SEPARATED: {
+        unsigned short inkset;
+        int fldPresent;
+
+        if (headerdump)
+            pm_message("color separation");
+
+        fldPresent = TIFFGetField(tiffP, TIFFTAG_INKNAMES, &inkset);
+        if (fldPresent && inkset != INKSET_CMYK)
+            pm_error("This color separation file uses an inkset (%d) "
+                     "we can't handle.  We handle only CMYK.", inkset);
+        if (spp != 4) 
+            pm_error("This CMYK color separation file is %d samples per "
+                     "pixel.  "
+                     "We need 4 samples, though: C, M, Y, and K.  ",
+                     spp);
+
+        *formatP = PPM_TYPE;
+
+        *maxvalP = (1 << bps) - 1;
+    }
         break;
             
-        case PHOTOMETRIC_RGB:
-            if (headerdump)
-                pm_message("RGB truecolor");
-            grayscale = FALSE;
+    case PHOTOMETRIC_RGB:
+        if (headerdump)
+            pm_message("RGB truecolor");
 
-            if (spp != 3 && spp != 4)
-                pm_error("This RGB image has %d samples per pixel.  "
-                         "We understand only 3 or 4.", spp);
+        if (spp != 3 && spp != 4)
+            pm_error("This RGB image has %d samples per pixel.  "
+                     "We understand only 3 or 4.", spp);
 
-            *maxvalP = (1 << bps) - 1;
-            break;
+        *formatP = PPM_TYPE;
+
+        *maxvalP = (1 << bps) - 1;
+        break;
 
-        case PHOTOMETRIC_MASK:
-            pm_error("don't know how to handle PHOTOMETRIC_MASK");
+    case PHOTOMETRIC_MASK:
+        pm_error("don't know how to handle PHOTOMETRIC_MASK");
 
-        case PHOTOMETRIC_DEPTH:
-            pm_error("don't know how to handle PHOTOMETRIC_DEPTH");
+    case PHOTOMETRIC_DEPTH:
+        pm_error("don't know how to handle PHOTOMETRIC_DEPTH");
 
-        case PHOTOMETRIC_YCBCR:
-            pm_error("don't know how to handle PHOTOMETRIC_YCBCR");
+    case PHOTOMETRIC_YCBCR:
+        pm_error("don't know how to handle PHOTOMETRIC_YCBCR");
 
-        case PHOTOMETRIC_CIELAB:
-            pm_error("don't know how to handle PHOTOMETRIC_CIELAB");
+    case PHOTOMETRIC_CIELAB:
+        pm_error("don't know how to handle PHOTOMETRIC_CIELAB");
 
-        case PHOTOMETRIC_LOGL:
-            pm_error("don't know how to handle PHOTOMETRIC_LOGL");
+    case PHOTOMETRIC_LOGL:
+        pm_error("don't know how to handle PHOTOMETRIC_LOGL");
 
-        case PHOTOMETRIC_LOGLUV:
-            pm_error("don't know how to handle PHOTOMETRIC_LOGLUV");
+    case PHOTOMETRIC_LOGLUV:
+        pm_error("don't know how to handle PHOTOMETRIC_LOGLUV");
             
-        default:
-            pm_error("unknown photometric: %d", photomet);
-        }
+    default:
+        pm_error("unknown photometric: %d", photomet);
+    }
     if (*maxvalP > PNM_OVERALLMAXVAL)
-        pm_error("bits/sample (%d) in the input image is too large.",
-                 bps);
-    if (grayscale) {
-        if (*maxvalP == 1) {
-            *formatP = PBM_TYPE;
-            pm_message("writing PBM file");
-        } else {
-            *formatP = PGM_TYPE;
-            pm_message("writing PGM file");
-        }
-    } else {
-        *formatP = PPM_TYPE;
-        pm_message("writing PPM file");
+        pm_error("bits/sample (%u) in the input image is too large.", bps);
+}
+
+
+
+static void
+reportOutputFormat(int const format) {
+
+    const char * formatDesc;
+
+    switch (format) {
+    case PBM_TYPE: formatDesc = "PBM"; break;
+    case PGM_TYPE: formatDesc = "PGM"; break;
+    case PPM_TYPE: formatDesc = "PPM"; break;
+    default: assert(false);
     }
+
+    pm_message("writing %s file", formatDesc);
 }
 
 
@@ -1374,9 +1434,9 @@ warnBrokenTiffLibrary(TIFF * const tiffP) {
 */
 
     unsigned short tiffOrientation;
-    int present;
-    present = TIFFGetField(tiffP, TIFFTAG_ORIENTATION, &tiffOrientation);
-    if (present) {
+    int fldPresent;
+    fldPresent = TIFFGetField(tiffP, TIFFTAG_ORIENTATION, &tiffOrientation);
+    if (fldPresent) {
         switch (tiffOrientation) {
         case ORIENTATION_LEFTTOP:
         case ORIENTATION_RIGHTTOP:
@@ -1448,16 +1508,47 @@ enum convertDisp {CONV_DONE,
                   CONV_FAILED, 
                   CONV_NOTATTEMPTED};
 
+
+static void
+convertRasterIntoProvidedMemory(pnmOut *           const pnmOutP,
+                                unsigned int       const cols,
+                                unsigned int       const rows,
+                                xelval             const maxval,
+                                TIFF *             const tif,
+                                bool               const verbose,
+                                uint32 *           const raster,
+                                enum convertDisp * const statusP) {
+
+    int const stopOnErrorFalse = false;
+
+    TIFFRGBAImage img;
+    char emsg[1024];
+    int ok;
+                
+    ok = TIFFRGBAImageBegin(&img, tif, stopOnErrorFalse, emsg);
+    if (!ok) {
+        pm_message("%s", emsg);
+        *statusP = CONV_FAILED;
+    } else {
+        int ok;
+        ok = TIFFRGBAImageGet(&img, raster, cols, rows);
+        TIFFRGBAImageEnd(&img) ;
+        if (!ok) {
+            pm_message("%s", emsg);
+            *statusP = CONV_FAILED;
+        } else {
+            *statusP = CONV_DONE;
+            convertTiffRaster(raster, cols, rows, maxval, pnmOutP);
+        }
+    } 
+}
+
+
+
 static void
 convertRasterInMemory(pnmOut *           const pnmOutP,
                       xelval             const maxval,
                       TIFF *             const tif,
-                      unsigned short     const photomet, 
-                      unsigned short     const planarconfig,
-                      unsigned short     const bps,
-                      unsigned short     const spp,
-                      unsigned short     const fillorder,
-                      xel                const colormap[],
                       bool               const verbose,
                       enum convertDisp * const statusP) {
 /*----------------------------------------------------------------------------
@@ -1477,64 +1568,49 @@ convertRasterInMemory(pnmOut *           const pnmOutP,
    programs, we simply abort the program if we are unable to allocate
    memory for other things.
 -----------------------------------------------------------------------------*/
-    unsigned int cols, rows;  /* Dimensions of output image */
+    char emsg[1024];
+    int ok;
 
     if (verbose)
         pm_message("Converting in memory ...");
 
     warnBrokenTiffLibrary(tif);
 
-    getTiffDimensions(tif, &cols, &rows);
-
-    if (rows == 0 || cols == 0) 
-        *statusP = CONV_DONE;
-    else {
-        char emsg[1024];
-        int ok;
-        ok = TIFFRGBAImageOK(tif, emsg);
-        if (!ok) {
-            pm_message("%s", emsg);
-            *statusP = CONV_UNABLE;
-        } else {
-            uint32 * raster;
+    ok = TIFFRGBAImageOK(tif, emsg);
+    if (!ok) {
+        pm_message("%s", emsg);
+        *statusP = CONV_UNABLE;
+    } else {
+        unsigned int cols, rows;  /* Dimensions of output image */
+        getTiffDimensions(tif, &cols, &rows);
 
-            /* Note that TIFFRGBAImageGet() converts any bits per sample
-               to 8.  Maxval of the raster it returns is always 255.
-            */
+        if (rows == 0 || cols == 0) 
+            *statusP = CONV_DONE;
+        else {
             if (cols > UINT_MAX/rows) {
                 pm_message("%u rows of %u columns is too large to compute",
                            rows, cols);
                 *statusP = CONV_OOM;
-                return;
-            }
-
-            MALLOCARRAY(raster, cols * rows);
-            if (raster == NULL) {
-                pm_message("Unable to allocate space for a raster of %u "
-                           "pixels.", cols * rows);
-                *statusP = CONV_OOM;
             } else {
-                int const stopOnErrorFalse = FALSE;
-                TIFFRGBAImage img;
-                int ok;
-                
-                ok = TIFFRGBAImageBegin(&img, tif, stopOnErrorFalse, emsg);
-                if (!ok) {
-                    pm_message("%s", emsg);
-                    *statusP = CONV_FAILED;
+                unsigned int const pixelCt = rows * cols;
+
+                uint32 * raster;
+
+                /* Note that TIFFRGBAImageGet() converts any bits per sample
+                   to 8.  Maxval of the raster it returns is always 255.
+                */
+                MALLOCARRAY(raster, pixelCt);
+                if (raster == NULL) {
+                    pm_message("Unable to allocate space for a raster of %u "
+                               "pixels.", pixelCt);
+                    *statusP = CONV_OOM;
                 } else {
-                    int ok;
-                    ok = TIFFRGBAImageGet(&img, raster, cols, rows);
-                    TIFFRGBAImageEnd(&img) ;
-                    if (!ok) {
-                        pm_message("%s", emsg);
-                        *statusP = CONV_FAILED;
-                    } else {
-                        *statusP = CONV_DONE;
-                        convertTiffRaster(raster, cols, rows, maxval, pnmOutP);
-                    }
-                } 
-                free(raster);
+                    convertRasterIntoProvidedMemory(
+                        pnmOutP, cols, rows, maxval, tif, verbose,
+                        raster, statusP);
+                    
+                    free(raster);
+                }
             }
         }
     }
@@ -1559,11 +1635,7 @@ convertRaster(pnmOut *           const pnmOutP,
     if (byrow || !flipOk)
         status = CONV_NOTATTEMPTED;
     else {
-        convertRasterInMemory(
-            pnmOutP, maxval,
-            tifP, tiffDir.photomet, tiffDir.planarconfig, 
-            tiffDir.bps, tiffDir.spp, fillorder,
-            colormap, verbose, &status);
+        convertRasterInMemory(pnmOutP, maxval, tifP, verbose, &status);
     }
     if (status == CONV_DONE) {
         if (tiffDir.bps > 8)
@@ -1612,6 +1684,8 @@ convertImage(TIFF *             const tifP,
     analyzeImageType(tifP, tiffDir.bps, tiffDir.spp, tiffDir.photomet, 
                      &maxval, &format, colormap, cmdline.headerdump, cmdline);
 
+    reportOutputFormat(format);
+
     pnmOut_init(imageoutFileP, alphaFileP, tiffDir.width, tiffDir.height,
                 tiffDir.orientation, maxval, format, maxval,
                 cmdline.byrow, cmdline.orientraw,
diff --git a/doc/HISTORY b/doc/HISTORY
index df95e9c1..7816967c 100644
--- a/doc/HISTORY
+++ b/doc/HISTORY
@@ -4,63 +4,93 @@ Netpbm.
 CHANGE HISTORY 
 --------------
 
-17.03.25 BJH  Release 10.77.04
+17.03.28 BJH  Release 10.78.00
 
-              tifftonm: Fix incorrect PBM output with two-color paletted TIFF
-              image.  Broken in primordial Netpbm, ca 1990.
+              ppmpat: Add -color.
+
+              ppmpat: Add -argyle1, -argyle2.
 
-17.03.19 BJH  Release 10.77.03
+              pnmtotiff: Fail with -miniswhite or -minisblack on color image
+              rather than produce an invalid TIFF.
 
               tifftopnmcmyk: Default rows per strip to the TIFF library
               default instead of whatever yields 8K strips.
 
+              --version global option: with SOURCE_DATE_EPOCH environment
+              variable, display source code datetime instead of build
+              datetime.  And when displaying build datetime, do it in the
+              local time of the process running the command instead of the
+              process that did the build.
+
+              tifftonm: Fix incorrect PBM output with two-color paletted TIFF
+              image.  Broken in primordial Netpbm, ca 1990.
+
+              tifftopnm: Fix memory corruption when image is more pixels than
+              can be represented as a C unsigned integer.  Broken in Netpbm
+              10.11 (October 2002).
+
               tifftopnmcmyk: Fix bug: fails with very wide images and no
               -rowsperstrip.  Always broken.  (Tifftopnmcmyk was new in Netpbm
               8.2 (March 2000).
 
+              svgtopam: Fix crash when out of memory.  Always broken (svgtopam
+              was new in Netpbm 10.33 (March 2006)).
+
+              pnmcrop: Add -closeness
+
+              libnetpbm: Add ppmd_pathbuilder_* functions.
+
               libnetpbm: ppmd_fill_path: remove debug trace.  Always broken
               (ppmd_fill_path was new in Netpbm 10.34 (June 2006).
 
-17.01.24 BJH  Release 10.77.02
-
-              tifftopnm: Fix memory corruption when image is more pixels
-              than can be represented as a C unsigned integer.  Broken in
-              Netpbm 10.11 (October 2002).
+              Build: don't create pointer man pages anymore.  These were
+              classic man pages, created by 'make package', one for each
+              program, that just told the user to get the manual from the web
+              and that other options for manuals are available at install
+              time.  Getting documentation online is commonplace enough now
+              that the user doesn't need to be told to do it or that there are
+              other options.  The existence of pointer man pages was,
+              meanwhile, misleading, since it looked from the outside like
+              they actually contained documentation.
 
-17.01.11 BJH  Release 10.77.01
+              Build: Don't package or install Manweb setup (for accessing
+              manuals on the web with Manweb).  Probably 100% unused and
+              distracting.
 
-              svgtopam: Fix crash when out of memory.  Always broken
-              (svgtopam was new in Netpbm 10.33 (March 2006)).
+              Debian package: change dependencies to be compatible with
+              Debian 8.
 
 16.12.25 BJH  Release 10.77.00
 
-              pnmpad: fix bug: incorrect output width.  Introduced in
+              pnmpad: Fix bug: incorrect output width.  Introduced in
               Netpbm 10.72 (July 2015).
 
-              Makeman: slight improvement to formatting of man pages.
+              Makeman: Slight improvement to formatting of man pages.
               Thanks Werner LEMBERG <wl@gnu.org>.
 
-              Test: skip tests of some parts that are configured out of the
+              Test: Skip tests of some parts that are configured out of the
               build.
 
 16.09.27 BJH  Release 10.76.00
 
-              pnmquantall: Fix failure when temporary file location is
-              not the same filesystem as the output file.
+              pnmquantall: Fix failure when temporary file location is not the
+              same filesystem as the output file.  Always broken (pnmremap was
+              new in Netpbm 10.58 (March 2012)).
 
               pnmquantall: Fix incorrect handling of when the Pnmremap or
-              the final rename fails.
+              the final rename fails.  Always broken (pnmremap was new
+              in Netpbm 10.58 (March 2012)).
 
               giftopnm: Fix bug: crash on little-endian computers that can't
               toleration unaligned memory access.  Thanks Ignatios Souvatzis
               (is@netbsd.org).  Broken in Netpbm 10.47 (June 2009).
 
-              cmuwmtopbm: fix trivial memory leak.  Always broken (cmuwmtopbm
+              cmuwmtopbm: Fix trivial memory leak.  Always broken (cmuwmtopbm
               was in primordial Pbmplus, in 1988).
 
               Build: Add PKG_CONFIG make variable.
 
-              Build: tifftopnm.c: fix undefined WIFSIGNALED, etc. in 
+              Build: tifftopnm.c: Fix undefined WIFSIGNALED, etc.
 
 16.06.26 BJH  Release 10.75.00
 
@@ -80,7 +110,7 @@ CHANGE HISTORY
 
               bmptopnm: Add ability to convert Version 4 and 5 Windows BMP.
 
-              pbmtext: remove undocumented -dump option; add 'genfontc'
+              pbmtext: Remove undocumented -dump option; add 'genfontc'
               development tool (buildtools/ directory) to replace it.
 
               pbmtext: Add -dry-run
@@ -89,49 +119,50 @@ CHANGE HISTORY
 
               pbmtext: Speedup: renders directly in raw PBM.
 
-              pbmreduce: add -randomseed.
+              pbmreduce: Add -randomseed.
 
               anytopnm, pnmmargin, pnmquant, ppmquant, pnmquantall, pgmcrater,
               ppmfade, ppmrainbow, ppmshadow, pbmtox10bm, pamstretch-gen:
               Add -version.
 
-              fiascotopnm: change -version to include Netpbm version.
+              fiascotopnm: Change -version to include Netpbm version.
 
               libnetpbm: Add pm_system2(), pm_system2_lp(), pm_system2_vp() -
               same as pm_system(), etc. but returns the termination status.
 
-              pamarith: fix incorrect output when maxvals differ, for
+              pamarith: Fix incorrect output when maxvals differ, for
               -add, -multiply, -mean, -min, -max.  Broken in Netpbm 10.41
               (December 2007).
 
               pbmtextps: Fix bug: input text or font name with Postscript
-              control characters messes up the Postscript program.
+              control characters messes up the Postscript program.  Always
+              broken (pbmtextps was new in Netpbm 10.0 (June 2002).
 
-              hpcdtoppm dummy version: update web link to real version.
+              hpcdtoppm dummy version: Update web link to real version.
 
-              ppmhist: fix incorrect color names.  Introduced in
+              ppmhist: Fix incorrect color names.  Introduced in
               Netpbm 10.19 (November 2003).
 
-              ppmshadow: fix bug: don't ignore invalid option.  Introduced in
+              ppmshadow: Fix bug: don't ignore invalid option.  Introduced in
               Netpbm 10.9 (September 2002).
 
-              pnmpaste: fix possible invalid memory access.  Introduced in
+              pnmpaste: Fix possible invalid memory access.  Introduced in
               Netpbm 1.44 (September 2008).
 
-              pbmreduce: fix undefined behavior when scale factor argument is
+              pbmreduce: Fix undefined behavior when scale factor argument is
               too big.  Always present (pbmreduce was new in September 1989).
 
               pbmtext: Fix bug: invalid memory reference when text contains
-              code points > 127.  Broken in 10.74.
+              code points > 127.  Broken in 10.74 (March 2016).
 
               pnmtofiasco, fiascotopnm: Fix incorrect math on systems with
               unusual floating point representation.  Always broken (programs
               were new in Netpbm 9.6 (July 2000).
 
-              cameratopam: fix invalid memory reference; effect unknown.
+              cameratopam: Fix invalid memory reference; effect unknown.
               Introduced in Netpbm 10.68 (September 2014).
 
-              Install on Windows: fix backward compatibility symlinks for
+              Install on Windows: Fix backward compatibility symlinks for
               pnmtoplainpnm, pnmquantall.
 
               Build: Remove use of strndup so it compiles on Mac OS X 10.6.
@@ -142,13 +173,13 @@ CHANGE HISTORY
 
 16.03.27 BJH  Release 10.74.00
 
-              pbmtext: produce same image when you run pbmtext with -width
+              pbmtext: Produce same image when you run pbmtext with -width
               explicitly set to the width you get when you don't specify
               width.
 
-              pbmtext: ignore -nomargins when -width is specified.
+              pbmtext: Ignore -nomargins when -width is specified.
 
-              pbmtext: report when line ends are dropped because of
+              pbmtext: Report when line ends are dropped because of
               truncation.
 
               pbmtext: Fix bug: if input has a code point that is not in the
@@ -156,23 +187,23 @@ CHANGE HISTORY
               font doesn't have space either.  Now it aborts the program in
               that case.
 
-              pbmtext: fix bug: Respect -width when specified.
+              pbmtext: Fix bug: Respect -width when specified.
 
-              pbmtext: fix bug: Deal correctly with negative -space.
+              pbmtext: Fix bug: Deal correctly with negative -space.
 
-              pbmtext: fix bug: Consider all characters in line, not just
+              pbmtext: Fix bug: Consider all characters in line, not just
               first and last, in determining line width.
 
-              libnetbpm font facility (so pbmtext): fix bug: undefined
+              libnetbpm font facility (so pbmtext): Fix bug: undefined
               behavior when font definitions are invalid in any of various
               ways.
 
-              libnetpbm font facility (so pbmtext): fix incorrect font names
+              libnetpbm font facility (so pbmtext): Fix incorrect font names
               in error messages.
 
               pnmtopsnr: Add -machine, -max .
 
-              Netpbmlib: add /usr/local/netpbm/lib and /etc/X11 to search
+              Netpbmlib: Add /usr/local/netpbm/lib and /etc/X11 to search
               path for rgb.txt.
 
               makeman: Add some text replacements to solve glitches.
diff --git a/doc/INSTALL b/doc/INSTALL
index f0b3e87b..a71e5179 100644
--- a/doc/INSTALL
+++ b/doc/INSTALL
@@ -109,6 +109,35 @@ If you use the 'configure' program, be sure to edit config.mk _after_ you
 run 'configure', since 'configure' generates config.mk.
 
 
+COMPILED-IN BUILD DATETIME
+--------------------------
+
+By default, the Netpbm build system builds the datetime that you built it
+into libnetpbm, so the --version global command line option can display it.
+It's actually just when you began building from a clean build tree; if you
+modify code and rebuild, the build datetime does not change.
+
+This is problematic for any of various reasons you might want to compare two
+versions of libnetpbm, or anything of which it is part, for equality.  Two
+libnetpbms that are identical except that they were built at different times
+would compare as different.
+
+Furthermore, as version information, the modification datetime of the source
+code is often more useful than the build datetime.
+
+For these reasons, it is possible to change this timestamping behavior by
+setting the environment variable 'SOURCE_DATA_EPOCH'.  That is supposed to be
+the POSIX datetime value (decimal number of seconds since 1969, excluding leap
+seconds) for when the source code was last modified.  When you set this,
+before doing 'make' for the first time in a clean build tree, the resulting
+libnetpbm contains that information and not build datetime information, and
+the --version global option causes a different message.
+
+The name and meaning of the environment variable is taken from a standard
+described at https://reproducible-builds.org/specs/source-date-epoch/ on March
+16, 2017.
+
+
 AUTOMATING THE BUILD
 --------------------
 
diff --git a/doc/TESTS b/doc/TESTS
index 544c0673..d2225164 100644
--- a/doc/TESTS
+++ b/doc/TESTS
@@ -178,7 +178,7 @@ only the test scripts which examine giftopnm and pamtogif, do:
 
 
 1.10 Valgrind
-============
+=============
 
 You can run the whole test under Valgrind.  This is an advanced feature
 intended for programmers who work on Netpbm code.
diff --git a/editor/pnmcrop.c b/editor/pnmcrop.c
index c6aabff1..17b2b6c8 100644
--- a/editor/pnmcrop.c
+++ b/editor/pnmcrop.c
@@ -1,4 +1,4 @@
-/* pnmcrop.c - crop a portable anymap
+/* pnmcrop.c - crop a Netpbm image
 **
 ** Copyright (C) 1988 by Jef Poskanzer.
 **
@@ -30,6 +30,10 @@
 #include "shhopt.h"
 #include "mallocvar.h"
 
+static double const sqrt3 = 1.73205080756887729352;
+    /* The square root of 3 */
+static double const EPSILON = 1.0e-5;
+
 enum bg_choice {BG_BLACK, BG_WHITE, BG_DEFAULT, BG_SIDES};
 
 typedef enum { LEFT = 0, RIGHT = 1, TOP = 2, BOTTOM = 3} edgeLocation;
@@ -53,7 +57,7 @@ typedef enum {
         /* Immediately after the raster */
 } imageFilePos;
 
-struct cmdlineInfo {
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
@@ -64,13 +68,14 @@ struct cmdlineInfo {
     unsigned int verbose;
     unsigned int margin;
     const char * borderfile;  /* NULL if none */
+    float closeness;
 };
 
 
 
 static void
 parseCommandLine(int argc, const char ** argv,
-                 struct cmdlineInfo *cmdlineP) {
+                 struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that the file spec array we return is stored in the storage that
    was passed to us as the argv array.
@@ -81,7 +86,7 @@ parseCommandLine(int argc, const char ** argv,
     optStruct3 opt;
 
     unsigned int blackOpt, whiteOpt, sidesOpt;
-    unsigned int marginSpec, borderfileSpec;
+    unsigned int marginSpec, borderfileSpec, closenessSpec;
     unsigned int leftOpt, rightOpt, topOpt, bottomOpt;
     
     unsigned int option_def_index;
@@ -101,6 +106,8 @@ parseCommandLine(int argc, const char ** argv,
             &marginSpec,     0);
     OPTENT3(0, "borderfile", OPT_STRING, &cmdlineP->borderfile,
             &borderfileSpec, 0);
+    OPTENT3(0, "closeness",  OPT_FLOAT,  &cmdlineP->closeness,
+            &closenessSpec,  0);
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
@@ -147,6 +154,16 @@ parseCommandLine(int argc, const char ** argv,
 
     if (!borderfileSpec)
         cmdlineP->borderfile = NULL;
+
+    if (!closenessSpec)
+        cmdlineP->closeness = 0.0;
+
+    if (cmdlineP->closeness < 0.0)
+        pm_error("-closeness value %f is negative", cmdlineP->closeness);
+
+    if (cmdlineP->closeness > 100.0)
+        pm_error("-closeness value %f is more than 100%%",
+                 cmdlineP->closeness);
 }
 
 
@@ -217,7 +234,7 @@ background2Corners(FILE * const ifP,
   Expect the file to be positioned to the start of the raster, and leave
   it positioned arbitrarily.
 ----------------------------------------------------------------------------*/
-    xel *xelrow;
+    xel * xelrow;
     xel background;   /* our return value */
     
     xelrow = pnm_allocrow(cols);
@@ -269,7 +286,24 @@ computeBackground(FILE *         const ifP,
         break;
     }
 
-    return(background);
+    return background;
+}
+
+
+
+static bool
+colorMatches(pixel        const comparand, 
+             pixel        const comparator, 
+             unsigned int const allowableDiff) {
+/*----------------------------------------------------------------------------
+   The colors 'comparand' and 'comparator' are within 'allowableDiff'
+   color levels of each other, in cartesian distance.
+-----------------------------------------------------------------------------*/
+    /* Fast path for usual case */
+    if (allowableDiff < EPSILON)
+        return PPM_EQUAL(comparand, comparator);
+
+    return PPM_DISTANCE(comparand, comparator) <= SQR(allowableDiff);
 }
 
 
@@ -281,6 +315,7 @@ findBordersInImage(FILE *         const ifP,
                    xelval         const maxval,
                    int            const format,
                    xel            const backgroundColor,
+                   double         const closeness,
                    bool *         const hasBordersP,
                    borderSet *    const borderSizeP) {
 /*----------------------------------------------------------------------------
@@ -292,9 +327,11 @@ findBordersInImage(FILE *         const ifP,
    Expect the input file to be positioned to the beginning of the
    image raster and leave it positioned arbitrarily.
 -----------------------------------------------------------------------------*/
+    unsigned int const allowableDiff = ROUNDU(sqrt3 * maxval * closeness/100);
+
     xel * xelrow;        /* A row of the input image */
     int row;
-    bool gottop;
+    bool gotTop;
     int left, right, bottom, top;
         /* leftmost, etc. nonbackground pixel found so far; -1 for none */
 
@@ -305,8 +342,7 @@ findBordersInImage(FILE *         const ifP,
     top    = rows;  /* initial value */
     bottom = -1;    /* initial value */
 
-    gottop = FALSE;
-    for (row = 0; row < rows; ++row) {
+    for (row = 0, gotTop = false; row < rows; ++row) {
         int col;
         int thisRowLeft;
         int thisRowRight;
@@ -314,12 +350,14 @@ findBordersInImage(FILE *         const ifP,
         pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
         
         col = 0;
-        while (col < cols && PNM_EQUAL(xelrow[col], backgroundColor))
+        while (col < cols &&
+               colorMatches(xelrow[col], backgroundColor, allowableDiff))
             ++col;
         thisRowLeft = col;
 
         col = cols-1;
-        while (col >= thisRowLeft && PNM_EQUAL(xelrow[col], backgroundColor))
+        while (col >= thisRowLeft &&
+               colorMatches(xelrow[col], backgroundColor, allowableDiff))
             --col;
         thisRowRight = col + 1;
 
@@ -329,9 +367,9 @@ findBordersInImage(FILE *         const ifP,
             left  = MIN(thisRowLeft,  left);
             right = MAX(thisRowRight, right);
 
-            if (!gottop) {
-                gottop = TRUE;
+            if (!gotTop) {
                 top = row;
+                gotTop = true;
             }
             bottom = row + 1;   /* New candidate */
         }
@@ -360,6 +398,7 @@ analyzeImage(FILE *         const ifP,
              xelval         const maxval,
              int            const format,
              enum bg_choice const backgroundReq,
+             double         const closeness,
              imageFilePos   const newFilePos,
              xel *          const backgroundColorP,
              bool *         const hasBordersP,
@@ -389,7 +428,7 @@ analyzeImage(FILE *         const ifP,
     pm_seek2(ifP, &rasterpos, sizeof(rasterpos));
 
     findBordersInImage(ifP, cols, rows, maxval, format, 
-                       background, hasBordersP, borderSizeP);
+                       background, closeness, hasBordersP, borderSizeP);
 
     if (newFilePos == FILEPOS_BEG)
         pm_seek2(ifP, &rasterpos, sizeof(rasterpos));
@@ -750,7 +789,7 @@ writeCroppedPBM(FILE *       const ifP,
 
 
 static void
-determineCrops(struct cmdlineInfo const cmdline,
+determineCrops(struct CmdlineInfo const cmdline,
                borderSet *        const oldBorderSizeP,
                cropSet *          const cropP) {
 
@@ -798,7 +837,7 @@ validateComputableSize(unsigned int const cols,
 
 
 static void
-cropOneImage(struct cmdlineInfo const cmdline,
+cropOneImage(struct CmdlineInfo const cmdline,
              FILE *             const ifP,
              FILE *             const bdfP,
              FILE *             const ofP) {
@@ -828,11 +867,11 @@ cropOneImage(struct cmdlineInfo const cmdline,
 
     if (bdfP)
         analyzeImage(bdfP, bcols, brows, bmaxval, bformat, cmdline.background,
-                     FILEPOS_END,
+                     cmdline.closeness, FILEPOS_END,
                      &background, &hasBorders, &oldBorder);
     else
         analyzeImage(ifP, cols, rows, maxval, format, cmdline.background,
-                     FILEPOS_BEG,
+                     cmdline.closeness, FILEPOS_BEG,
                      &background, &hasBorders, &oldBorder);
 
     if (cmdline.verbose) {
@@ -863,7 +902,7 @@ cropOneImage(struct cmdlineInfo const cmdline,
 int
 main(int argc, const char *argv[]) {
 
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
     FILE * ifP;   
         /* The program's regular input file.  Could be a seekable copy of
            it in a temporary file.
diff --git a/editor/ppmchange.c b/editor/ppmchange.c
index dea85a77..cfc34769 100644
--- a/editor/ppmchange.c
+++ b/editor/ppmchange.c
@@ -19,20 +19,22 @@
 #include "mallocvar.h"
 
 #define TCOLS 256
-#define SQRT3 1.73205080756887729352
+static double const sqrt3 = 1.73205080756887729352;
     /* The square root of 3 */
+static double const EPSILON = 1.0e-5;
 
-struct cmdlineInfo {
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
     const char *input_filespec;  /* Filespecs of input files */
     int ncolors;      /* Number of valid entries in color0[], color1[] */
-    char * oldcolorname[TCOLS];  /* colors user wants replaced */
-    char * newcolorname[TCOLS];  /* colors with which he wants them replaced */
-    int closeness;    
-       /* -closeness option value.  Zero if no -closeness option */
-    char * remainder_colorname;  
+    const char * oldcolorname[TCOLS];
+        /* colors user wants replaced */
+    const char * newcolorname[TCOLS];
+        /* colors with which he wants them replaced */
+    float closeness;    
+    const char * remainder_colorname;  
       /* Color user specified for -remainder.  Null pointer if he didn't
          specify -remainder.
       */
@@ -42,8 +44,8 @@ struct cmdlineInfo {
 
 
 static void
-parseCommandLine(int argc, char ** argv,
-                 struct cmdlineInfo * const cmdlineP) {
+parseCommandLine(int argc, const char ** const argv,
+                 struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that the file spec array we return is stored in the storage that
    was passed to us as the argv array.
@@ -59,7 +61,7 @@ parseCommandLine(int argc, char ** argv,
     MALLOCARRAY_NOFAIL(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENTRY */
-    OPTENT3(0, "closeness",     OPT_UINT,
+    OPTENT3(0, "closeness",     OPT_FLOAT,
             &cmdlineP->closeness,           &closenessSpec,     0);
     OPTENT3(0, "remainder",     OPT_STRING,
             &cmdlineP->remainder_colorname, &remainderSpec,     0);
@@ -70,15 +72,22 @@ parseCommandLine(int argc, char ** argv,
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
     opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
 
-    pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
     if (!remainderSpec)
         cmdlineP->remainder_colorname = NULL;
 
     if (!closenessSpec)
-        cmdlineP->closeness = 0;
+        cmdlineP->closeness = 0.0;
 
+    if (cmdlineP->closeness < 0.0)
+        pm_error("-closeness value %f is negative", cmdlineP->closeness);
+
+    if (cmdlineP->closeness > 100)
+        pm_error("-closeness value %f is more than 100%%",
+                 cmdlineP->closeness);
+    
     if ((argc-1) % 2 == 0) 
         cmdlineP->input_filespec = "-";
     else
@@ -99,26 +108,19 @@ parseCommandLine(int argc, char ** argv,
 
 
 
-static double
-sqrf(float const F) {
-    return F*F;
-}
-
-
-
-static int 
-colormatch(pixel const comparand, 
-           pixel const comparator, 
-           float const closeness) {
+static bool
+colorMatches(pixel        const comparand, 
+             pixel        const comparator, 
+             unsigned int const allowableDiff) {
 /*----------------------------------------------------------------------------
-   Return true iff 'comparand' matches 'comparator' in color within the
-   fuzz factor 'closeness'.
+   The colors 'comparand' and 'comparator' are within 'allowableDiff'
+   color levels of each other, in cartesian distance.
 -----------------------------------------------------------------------------*/
     /* Fast path for usual case */
-    if (closeness == 0)
+    if (allowableDiff < EPSILON)
         return PPM_EQUAL(comparand, comparator);
 
-    return PPM_DISTANCE(comparand, comparator) <= sqrf(closeness);
+    return PPM_DISTANCE(comparand, comparator) <= SQR(allowableDiff);
 }
 
 
@@ -132,15 +134,18 @@ changeRow(const pixel * const inrow,
           const pixel         colorto[],
           bool          const remainder_specified, 
           pixel         const remainder_color, 
-          float         const closeness) {
+          unsigned int  const allowableDiff) {
 /*----------------------------------------------------------------------------
    Replace the colors in a single row.  There are 'ncolors' colors to 
    replace.  The to-replace colors are in the array colorfrom[], and the
    replace-with colors are in corresponding elements of colorto[].
    Iff 'remainder_specified' is true, replace all colors not mentioned
-   in colorfrom[] with 'remainder_color'.  Use the closeness factor
-   'closeness' in determining if something in the input row matches
-   a color in colorfrom[].
+   in colorfrom[] with 'remainder_color'.  
+
+   Consider the color in inrow[] to match a color in colorfrom[] if it is
+   within 'allowableDiff' color levels of it, in cartesian distance (e.g.
+   color (1,1,1) is sqrt(12) = 3.5 color levels distant from (3,3,3),
+   so if 'allowableDiff' is 4, they match).
 
    The input row is 'inrow'.  The output is returned as 'outrow', in
    storage which must be already allocated.  Both are 'cols' columns wide.
@@ -157,7 +162,7 @@ changeRow(const pixel * const inrow,
 
         haveMatch = FALSE;  /* haven't found a match yet */
         for (i = 0; i < ncolors && !haveMatch; ++i) {
-            haveMatch = colormatch(inrow[col], colorfrom[i], closeness);
+            haveMatch = colorMatches(inrow[col], colorfrom[i], allowableDiff);
             newcolor = colorto[i];
         }
         if (haveMatch)
@@ -172,16 +177,23 @@ changeRow(const pixel * const inrow,
 
 
 int
-main(int argc, char *argv[]) {
-    struct cmdlineInfo cmdline;
+main(int argc, const char ** const argv) {
+
+    struct CmdlineInfo cmdline;
     FILE * ifP;
     int format;
     int rows, cols;
     pixval maxval;
-    float closeness;
+    unsigned int allowableDiff;
+        /* The amount of difference between two colors we allow and still
+           consider those colors to be the same, for the purposes of
+           determining which pixels in the image to change.  This is a
+           cartesian distance between the color triples, on a maxval scale
+           (which means it can be as high as sqrt(3) * maxval)
+        */
     int row;
-    pixel* inrow;
-    pixel* outrow;
+    pixel * inrow;
+    pixel * outrow;
 
     pixel oldcolor[TCOLS];  /* colors user wants replaced */
     pixel newcolor[TCOLS];  /* colors with which he wants them replaced */
@@ -190,7 +202,7 @@ main(int argc, char *argv[]) {
          specify -remainder.
       */
 
-    ppm_init(&argc, argv);
+    pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
     
@@ -210,8 +222,8 @@ main(int argc, char *argv[]) {
                                           cmdline.closeok);
         }
     }
-    closeness = SQRT3 * maxval * cmdline.closeness/100;
-
+    allowableDiff = ROUNDU(sqrt3 * maxval * cmdline.closeness/100);
+    
     ppm_writeppminit( stdout, cols, rows, maxval, 0 );
     inrow = ppm_allocrow(cols);
     outrow = ppm_allocrow(cols);
@@ -222,7 +234,7 @@ main(int argc, char *argv[]) {
 
         changeRow(inrow, outrow, cols, cmdline.ncolors, oldcolor, newcolor,
                   cmdline.remainder_colorname != NULL,
-                  remainder_color, closeness);
+                  remainder_color, allowableDiff);
 
         ppm_writeppmrow(stdout, outrow, cols, maxval, 0);
     }
diff --git a/generator/ppmpat.c b/generator/ppmpat.c
index fe1a1d27..f4b066ee 100644
--- a/generator/ppmpat.c
+++ b/generator/ppmpat.c
@@ -10,15 +10,21 @@
 ** implied warranty.
 */
 
-#define _XOPEN_SOURCE  /* get M_PI in math.h */
+#define _DEFAULT_SOURCE /* New name for SVID & BSD source defines */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+                           /* get M_PI in math.h */
+#define _BSD_SOURCE  /* Make sure strdup() is in <string.h> */
+#define SPIROGRAPHS 0   /* Spirograph to be added soon */
 
 #include <assert.h>
 #include <math.h>
 #include <limits.h>
+#include <string.h>
 
 #include "pm_c_util.h"
 #include "mallocvar.h"
 #include "shhopt.h"
+#include "nstring.h"
 #include "ppm.h"
 #include "ppmdraw.h"
 
@@ -28,28 +34,138 @@ typedef enum {
     PAT_GINGHAM3,
     PAT_MADRAS,
     PAT_TARTAN,
+    PAT_ARGYLE1,
+    PAT_ARGYLE2,
     PAT_POLES,
     PAT_SQUIG,
     PAT_CAMO,
-    PAT_ANTICAMO
-} pattern;
+    PAT_ANTICAMO,
+    PAT_SPIRO1,
+    PAT_SPIRO2,
+    PAT_SPIRO3
+} Pattern;
 
-struct cmdlineInfo {
+typedef struct {
+/*----------------------------------------------------------------------------
+   An ordered list of colors with a cursor.
+-----------------------------------------------------------------------------*/
+    unsigned int count;
+    unsigned int index;
+        /* Current position in the list */
+    pixel *      color;
+        /* Malloced array 'count' in size. */
+} ColorTable;
+
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
-    pattern basePattern;
+    Pattern      basePattern;
     unsigned int width;
     unsigned int height;
+    unsigned int colorSpec;
+    ColorTable   colorTable;
     unsigned int randomseed;
     unsigned int randomseedSpec;
 };
 
 
+static void
+validateColorCount(Pattern      const basePattern,
+                   unsigned int const colorCount) {
+
+    if (colorCount == 0)
+        pm_error("-color: no colors specified"); 
+
+    switch (basePattern) {
+    case PAT_GINGHAM2:
+    case PAT_ARGYLE1:
+    case PAT_SPIRO1:
+        if (colorCount != 2)
+            pm_error("Wrong number of colors: %u. "
+                     "2 colors are required for the specified pattern.",
+                     colorCount); 
+        break;
+    case PAT_GINGHAM3:
+    case PAT_MADRAS:
+    case PAT_TARTAN:
+    case PAT_ARGYLE2:
+        if (colorCount != 3)
+            pm_error("Wrong number of colors: %u. "
+                     "3 colors are required for the specified pattern.",
+                     colorCount); 
+        break;
+    case PAT_POLES:
+        if (colorCount < 2)
+            pm_error("Too few colors: %u. " 
+                     "At least 2 colors are required "
+                     "for the specified pattern.",
+                     colorCount); 
+        break;
+    case PAT_SQUIG:
+    case PAT_CAMO:
+    case PAT_ANTICAMO:
+        if (colorCount < 3)
+            pm_error("Wrong number of colors: %u. "
+                     "At least 3 colors are required "
+                     "for the specified pattern.",
+                     colorCount); 
+        break;
+
+    case PAT_SPIRO2:
+    case PAT_SPIRO3:
+    default:
+        pm_error("INTERNAL ERROR.");
+    }
+}
+
+
+
+static void
+parseColorOpt(const char ** const colorText,
+              ColorTable  * const colorTableP,
+              Pattern       const basePattern) {
+/*----------------------------------------------------------------------------
+    String-list argument to -color is a comma-separated array of
+    color names or values, e.g.:
+    "-color=red,white,blue"
+    "-color=rgb:ff/ff/ff,rgb:00/00/00,rgb:80/80/ff"
+
+    Input:
+      Color name/value string-list: colorText[] 
+
+    Output values:
+      Color array: colorTableP->color[]
+      Number of colors found: colorTableP->colors
+----------------------------------------------------------------------------*/
+    unsigned int colorCount;
+    unsigned int i;
+    pixel * inColor;
+
+    for (colorCount = 0; colorText[colorCount] != NULL; ++colorCount)
+        ;
+
+    MALLOCARRAY(inColor, colorCount);
+
+    if (!inColor)
+        pm_error("Failed to allocate table space for %u colors "
+                 "specified by -color", colorCount);
+
+    for (i = 0; i < colorCount; ++i)
+        inColor[i] = ppm_parsecolor(colorText[i], PPM_MAXMAXVAL);
+
+    validateColorCount(basePattern, colorCount); 
+
+    colorTableP->count = colorCount;
+    colorTableP->index = 0;  /* initial value */
+    colorTableP->color = inColor;
+}
+
+
 
 static void
 parseCommandLine(int argc, const char ** argv,
-                 struct cmdlineInfo * const cmdlineP) {
+                 struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that the file spec array we return is stored in the storage that
    was passed to us as the argv array.
@@ -60,15 +176,21 @@ parseCommandLine(int argc, const char ** argv,
     optStruct3 opt;
 
     unsigned int option_def_index;
+    const char ** colorText;
     unsigned int basePatternCount;
     unsigned int gingham2;
     unsigned int gingham3;
     unsigned int madras;
     unsigned int tartan;
+    unsigned int argyle1;
+    unsigned int argyle2;
     unsigned int poles;
     unsigned int squig;
     unsigned int camo;
     unsigned int anticamo;
+    unsigned int spiro1;
+    unsigned int spiro2;
+    unsigned int spiro3;
 
     MALLOCARRAY_NOFAIL(option_def, 100);
 
@@ -85,6 +207,10 @@ parseCommandLine(int argc, const char ** argv,
             &madras,     0);
     OPTENT3(0, "tartan",        OPT_FLAG,   NULL,
             &tartan,     0);
+    OPTENT3(0, "argyle1",       OPT_FLAG,   NULL,
+            &argyle1,     0);
+    OPTENT3(0, "argyle2",       OPT_FLAG,   NULL,
+            &argyle2,     0);
     OPTENT3(0, "poles",         OPT_FLAG,   NULL,
             &poles,      0);
     OPTENT3(0, "squig",         OPT_FLAG,   NULL,
@@ -93,7 +219,19 @@ parseCommandLine(int argc, const char ** argv,
             &camo,       0);
     OPTENT3(0, "anticamo",      OPT_FLAG,   NULL,
             &anticamo,   0);
-    OPTENT3(0, "randomseed",    OPT_UINT,   &cmdlineP->randomseed,
+#if SPIROGRAPHS != 0
+    OPTENT3(0, "spiro1",        OPT_FLAG,   NULL,
+            &spiro1,     0);
+    OPTENT3(0, "spiro2",        OPT_FLAG,   NULL,
+            &spiro1,     0);
+    OPTENT3(0, "spiro3",        OPT_FLAG,   NULL,
+            &spiro1,     0);
+#else
+    spiro1 = spiro2 = spiro3 = 0;
+#endif
+    OPTENT3(0, "color",         OPT_STRINGLIST, &colorText,
+            &cmdlineP->colorSpec,           0);
+    OPTENT3(0, "randomseed",    OPT_UINT,       &cmdlineP->randomseed,
             &cmdlineP->randomseedSpec,      0);
 
     opt.opt_table = option_def;
@@ -102,16 +240,14 @@ parseCommandLine(int argc, const char ** argv,
 
     pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+    free(option_def);
 
     basePatternCount =
-        gingham2 +
-        gingham3 +
-        madras +
-        tartan +
+        gingham2 + gingham3 + madras + tartan + argyle1 + argyle2 +
         poles +
         squig +
-        camo +
-        anticamo;
+        camo + anticamo +
+        spiro1 + spiro2 + spiro3;
 
     if (basePatternCount < 1)
         pm_error("You must specify a base pattern option such as -gingham2");
@@ -127,6 +263,10 @@ parseCommandLine(int argc, const char ** argv,
             cmdlineP->basePattern = PAT_MADRAS;
         else if (tartan)
             cmdlineP->basePattern = PAT_TARTAN;
+        else if (argyle1)
+            cmdlineP->basePattern = PAT_ARGYLE1;
+        else if (argyle2)
+            cmdlineP->basePattern = PAT_ARGYLE2;
         else if (poles)
             cmdlineP->basePattern = PAT_POLES;
         else if (squig)
@@ -135,9 +275,22 @@ parseCommandLine(int argc, const char ** argv,
             cmdlineP->basePattern = PAT_CAMO;
         else if (anticamo)
             cmdlineP->basePattern = PAT_ANTICAMO;
+        else if (spiro1)
+            cmdlineP->basePattern = PAT_SPIRO1;
+        else if (spiro2)
+            cmdlineP->basePattern = PAT_SPIRO2;
+        else if (spiro3)
+            cmdlineP->basePattern = PAT_SPIRO3;
         else
             assert(false);  /* Every possibility is accounted for */
     }
+
+    if (cmdlineP->colorSpec) {
+        parseColorOpt(colorText, &cmdlineP->colorTable, cmdlineP->basePattern);
+        free(colorText);
+    } else
+        cmdlineP->colorTable.count = 0;
+
     if (argc-1 != 2)
         pm_error("You must specify 2 non-option arguments: width and height "
                  "in pixels.  You specified %u", argc-1);
@@ -150,7 +303,15 @@ parseCommandLine(int argc, const char ** argv,
         if (cmdlineP->height < 1)
             pm_error("Height must be at least 1 pixel");
     }
-    free(option_def);
+}
+
+
+
+static void
+freeCmdline(struct CmdlineInfo const cmdline) {
+    
+    if (cmdline.colorSpec)
+        free(cmdline.colorTable.color);
 }
 
 
@@ -257,6 +418,29 @@ average_drawproc(pixel **     const pixels,
 
 
 
+static void
+nextColor(ColorTable * const colorTableP) {
+/*----------------------------------------------------------------------------
+  Increment index, return it to 0 if we have used all the colors
+-----------------------------------------------------------------------------*/
+    colorTableP->index = (colorTableP->index + 1) % colorTableP->count;
+}
+
+
+
+static void
+nextColorBg(ColorTable * const colorTableP) {
+/*----------------------------------------------------------------------------
+  Increment index, return it to 1 if we have used all the colors (color[0] is
+  the background color, it's outside the cycle)
+-----------------------------------------------------------------------------*/
+    colorTableP->index = colorTableP->index % (colorTableP->count - 1) + 1;
+        /* Works when index == 0, but no callers rely on this. */
+
+}
+
+
+
 /*----------------------------------------------------------------------------
    Camouflage stuff
 -----------------------------------------------------------------------------*/
@@ -362,15 +546,18 @@ rnduni(void) {
 
 
 static void
-clearBackground(pixel **     const pixels,
-                unsigned int const cols,
-                unsigned int const rows,
-                pixval       const maxval,
-                bool         const antiflag) {
+clearBackgroundCamo(pixel **     const pixels,
+                    unsigned int const cols,
+                    unsigned int const rows,
+                    pixval       const maxval,
+                    ColorTable * const colorTableP,
+                    bool         const antiflag) {
 
     pixel color;
 
-    if (antiflag)
+    if (colorTableP->count > 0) {
+        color = colorTableP->color[0];
+    } else if (antiflag)
         color = randomAnticamoColor(maxval);
     else
         color = randomCamoColor(maxval);
@@ -381,17 +568,22 @@ clearBackground(pixel **     const pixels,
 }
 
 
+
 static void
 camoFill(pixel **         const pixels,
          unsigned int     const cols,
          unsigned int     const rows,
          pixval           const maxval,
          struct fillobj * const fh,
+         ColorTable     * const colorTableP,
          bool             const antiflag) {
          
     pixel color;
 
-    if (antiflag)
+    if (colorTableP->count > 0) {
+        color = colorTableP->color[colorTableP->index];
+        nextColorBg(colorTableP);
+    } else if (antiflag)
         color = randomAnticamoColor(maxval);
     else
         color = randomCamoColor(maxval);
@@ -448,14 +640,20 @@ static void
 camo(pixel **     const pixels,
      unsigned int const cols,
      unsigned int const rows,
+     ColorTable * const colorTableP,
      pixval       const maxval,
      bool         const antiflag) {
 
-    unsigned int const n = (rows * cols) / (BLOBRAD * BLOBRAD) * 5;
+    unsigned int const n = (rows * cols) / SQR(BLOBRAD) * 5;
 
     unsigned int i;
 
-    clearBackground(pixels, cols, rows, maxval, antiflag);
+    clearBackgroundCamo(pixels, cols, rows, maxval, colorTableP, antiflag);
+
+    if (colorTableP) {
+        assert(colorTableP->count > 1);
+        colorTableP->index = 1;  /* Foreground colors start at 1 */
+    }
 
     for (i = 0; i < n; ++i) {
         unsigned int const pointCt =
@@ -476,7 +674,7 @@ camo(pixel **     const pixels,
             pixels, cols, rows, maxval, x0, y0, pointCt, xs, ys, x0, y0,
             ppmd_fill_drawproc, fh);
         
-        camoFill(pixels, cols, rows, maxval, fh, antiflag);
+        camoFill(pixels, cols, rows, maxval, fh, colorTableP, antiflag);
         
         ppmd_fill_destroy(fh);
     }
@@ -485,19 +683,21 @@ camo(pixel **     const pixels,
 
 
 /*----------------------------------------------------------------------------
-   Gingham stuff
+   Plaid patterns
 -----------------------------------------------------------------------------*/
 
-
-
 static void
 gingham2(pixel **     const pixels,
          unsigned int const cols,
          unsigned int const rows,
+         ColorTable   const colorTable,
          pixval       const maxval) {
 
-    pixel const backcolor = randomDarkColor(maxval);
-    pixel const forecolor = randomBrightColor(maxval);
+    bool  const colorSpec = (colorTable.count > 0);
+    pixel const backcolor = colorSpec ?
+                            colorTable.color[0] : randomDarkColor(maxval);
+    pixel const forecolor = colorSpec ?
+                            colorTable.color[1] : randomBrightColor(maxval);
     unsigned int const colso2 = cols / 2;
     unsigned int const rowso2 = rows / 2;
 
@@ -524,15 +724,19 @@ static void
 gingham3(pixel **     const pixels,
          unsigned int const cols,
          unsigned int const rows,
+         ColorTable   const colorTable,
          pixval       const maxval) {
 
+    bool  const colorSpec = (colorTable.count > 0);
+    pixel const backcolor = colorSpec ?
+                            colorTable.color[0] : randomDarkColor(maxval);
+    pixel const fore1color = colorSpec ?
+                            colorTable.color[1] : randomBrightColor(maxval);
+    pixel const fore2color = colorSpec ?
+                            colorTable.color[2] : randomBrightColor(maxval);
     unsigned int const colso4 = cols / 4;
     unsigned int const rowso4 = rows / 4;
 
-    pixel const backcolor  = randomDarkColor(maxval);
-    pixel const fore1color = randomBrightColor(maxval);
-    pixel const fore2color = randomBrightColor(maxval);
-
     /* Warp. */
     ppmd_filledrectangle(
         pixels, cols, rows, maxval, 0, 0, colso4, rows, PPMD_NULLDRAWPROC,
@@ -568,8 +772,16 @@ static void
 madras(pixel **     const pixels,
        unsigned int const cols,
        unsigned int const rows,
+       ColorTable   const colorTable,
        pixval       const maxval) {
 
+    bool  const colorSpec = (colorTable.count > 0);
+    pixel const backcolor = colorSpec ?
+                            colorTable.color[0] : randomDarkColor(maxval);
+    pixel const fore1color = colorSpec ?
+                            colorTable.color[1] : randomBrightColor(maxval);
+    pixel const fore2color = colorSpec ?
+                            colorTable.color[2] : randomBrightColor(maxval);
     unsigned int const cols2  = cols * 2 / 44;
     unsigned int const rows2  = rows * 2 / 44;
     unsigned int const cols3  = cols * 3 / 44;
@@ -580,9 +792,6 @@ madras(pixel **     const pixels,
     unsigned int const rows6a = rows12 / 2;
     unsigned int const cols6b = cols12 - cols6a;
     unsigned int const rows6b = rows12 - rows6a;
-    pixel const backcolor  = randomDarkColor(maxval);
-    pixel const fore1color = randomBrightColor(maxval);
-    pixel const fore2color = randomBrightColor(maxval);
 
     /* Warp. */
     ppmd_filledrectangle(
@@ -692,8 +901,16 @@ static void
 tartan(pixel **     const pixels,
        unsigned int const cols,
        unsigned int const rows,
+       ColorTable   const colorTable,
        pixval       const maxval) {
 
+    bool  const colorSpec = (colorTable.count > 0);
+    pixel const backcolor = colorSpec ?
+                            colorTable.color[0] : randomDarkColor(maxval);
+    pixel const fore1color = colorSpec ?
+                            colorTable.color[1] : randomBrightColor(maxval);
+    pixel const fore2color = colorSpec ?
+                            colorTable.color[2] : randomBrightColor(maxval);
     unsigned int const cols1  = cols / 22;
     unsigned int const rows1  = rows / 22;
     unsigned int const cols3  = cols * 3 / 22;
@@ -704,9 +921,6 @@ tartan(pixel **     const pixels,
     unsigned int const rows5a = rows10 / 2;
     unsigned int const cols5b = cols10 - cols5a;
     unsigned int const rows5b = rows10 - rows5a;
-    pixel const backcolor  = randomDarkColor(maxval);
-    pixel const fore1color = randomBrightColor(maxval);
-    pixel const fore2color = randomBrightColor(maxval);
 
     /* Warp. */
     ppmd_filledrectangle(
@@ -763,6 +977,71 @@ tartan(pixel **     const pixels,
 
 
 
+static void
+drawAndFillDiamond(pixel **     const pixels,
+                   unsigned int const cols,
+                   unsigned int const rows,
+                   pixval       const maxval,
+                   pixel        const forecolor) {
+
+    unsigned int const colso2 = cols / 2;
+    unsigned int const rowso2 = rows / 2;
+
+    ppmd_pathbuilder * const pathBuilderP = ppmd_pathbuilder_create();
+
+    ppmd_pathbuilder_setBegPoint(pathBuilderP,
+                 ppmd_makePoint (colso2, 0));
+
+    ppmd_pathbuilder_addLineLeg(pathBuilderP,
+                 ppmd_makeLineLeg(ppmd_makePoint(cols-1, rowso2)));
+    ppmd_pathbuilder_addLineLeg(pathBuilderP,
+                 ppmd_makeLineLeg(ppmd_makePoint(colso2, rows-1)));
+    ppmd_pathbuilder_addLineLeg(pathBuilderP,
+                 ppmd_makeLineLeg(ppmd_makePoint(0,      rowso2)));
+    ppmd_pathbuilder_addLineLeg(pathBuilderP,
+                 ppmd_makeLineLeg(ppmd_makePoint(colso2, 0)));
+
+    ppmd_fill_path(pixels, cols, rows, maxval,
+                   ppmd_pathbuilder_pathP(pathBuilderP), forecolor);
+}
+
+
+
+static void
+argyle(pixel **     const pixels,
+       unsigned int const cols,
+       unsigned int const rows,
+       ColorTable   const colorTable,
+       pixval       const maxval,
+       bool         const stripes) {
+
+    bool  const colorSpec = (colorTable.count > 0);
+    pixel const backcolor = colorSpec ?
+        colorTable.color[0] : randomDarkColor(maxval);
+    pixel const forecolor = colorSpec ?
+        colorTable.color[1] : randomBrightColor(maxval);
+
+    /* Fill canvas with background to start */
+    ppmd_filledrectangle(
+        pixels, cols, rows, maxval, 0, 0, cols, rows, PPMD_NULLDRAWPROC,
+        &backcolor);
+
+    drawAndFillDiamond(pixels, cols, rows, maxval, forecolor);
+
+    if (stripes) {
+         /* Connect corners with thin stripes */
+         pixel const stripecolor =
+             colorSpec ? colorTable.color[2] : randomBrightColor(maxval);
+
+         ppmd_line(pixels, cols, rows, maxval, 0, 0, cols-1, rows-1,
+              PPMD_NULLDRAWPROC, (char *) &stripecolor);
+         ppmd_line(pixels, cols, rows, maxval, cols-1, 0, 0, rows-1,
+              PPMD_NULLDRAWPROC, (char *) &stripecolor);
+    }
+}
+
+
+
 /*----------------------------------------------------------------------------
    Poles stuff
 -----------------------------------------------------------------------------*/
@@ -780,14 +1059,21 @@ placeAndColorPolesRandomly(int *        const xs,
                            unsigned int const cols,
                            unsigned int const rows,
                            pixval       const maxval,
+                           ColorTable * const colorTableP, 
                            unsigned int const poleCt) {
 
     unsigned int i;
 
     for (i = 0; i < poleCt; ++i) {
+
         xs[i] = rand() % cols;
         ys[i] = rand() % rows;
-        colors[i] = randomBrightColor(maxval);
+
+        if (colorTableP->count > 0) {
+            colors[i] = colorTableP->color[colorTableP->index];
+            nextColor(colorTableP);
+        } else
+            colors[i] = randomBrightColor(maxval);
     }
 }
 
@@ -820,6 +1106,7 @@ static void
 poles(pixel **     const pixels,
       unsigned int const cols,
       unsigned int const rows,
+      ColorTable * const colorTableP,
       pixval       const maxval) {
 
     unsigned int const poleCt = MAX(2, MIN(MAXPOLES, cols * rows / 30000));
@@ -828,7 +1115,8 @@ poles(pixel **     const pixels,
     pixel colors[MAXPOLES];
     unsigned int row;
 
-    placeAndColorPolesRandomly(xs, ys, colors, cols, rows, maxval, poleCt);
+    placeAndColorPolesRandomly(xs, ys, colors, cols, rows, maxval,
+                               colorTableP, poleCt);
 
     /* Interpolate points */
 
@@ -871,11 +1159,15 @@ poles(pixel **     const pixels,
 #define SQ_POINTS 7
 #define SQ_MAXCIRCLE_POINTS 5000
 
-static int sq_circlecount;
-static pixel sq_colors[SQ_MAXCIRCLE_POINTS];
-static ppmd_point sq_offs[SQ_MAXCIRCLE_POINTS];
-
+struct Squig {
+    unsigned int circleCt;
+    pixel        color[SQ_MAXCIRCLE_POINTS];
+    ppmd_point   off[SQ_MAXCIRCLE_POINTS];
+};
 
+typedef struct {
+    struct Squig * squigP;
+} SqClientData;
 
 static void
 validateSquigAspect(unsigned int const cols,
@@ -909,7 +1201,11 @@ sqMeasureCircleDrawproc(pixel**      const pixels,
                         ppmd_point   const p,
                         const void * const clientdata) {
 
-    sq_offs[sq_circlecount++] = p;
+    const SqClientData * const sqClientDataP = clientdata;
+
+    struct Squig * const squigP = sqClientDataP->squigP;
+
+    squigP->off[squigP->circleCt++] = p;
 }
 
 
@@ -924,29 +1220,59 @@ sqRainbowCircleDrawproc(pixel **     const pixels,
                         ppmd_point   const p,
                         const void * const clientdata) {
 
+    const SqClientData * const sqClientDataP = clientdata;
+
+    struct Squig * const squigP = sqClientDataP->squigP;
+
     unsigned int i;
 
-    for (i = 0; i < sq_circlecount; ++i)
+    for (i = 0; i < squigP->circleCt; ++i)
         ppmd_point_drawprocp(
-            pixels, cols, rows, maxval, vectorSum(p, sq_offs[i]),
-            &sq_colors[i]);
+            pixels, cols, rows, maxval, vectorSum(p, squigP->off[i]),
+            &squigP->color[i]);
 }
 
 
 
 static void
+chooseSqPoleColors(ColorTable * const colorTableP,
+                   pixval       const maxval,
+                   pixel *      const color1P,
+                   pixel *      const color2P,
+                   pixel *      const color3P) {
+
+    if (colorTableP->count > 0) {
+        *color1P = colorTableP->color[colorTableP->index];
+        nextColor(colorTableP);
+        *color2P = colorTableP->color[colorTableP->index];
+        nextColor(colorTableP);
+        *color3P = colorTableP->color[colorTableP->index];
+        nextColor(colorTableP);
+    } else {
+        *color1P = randomBrightColor(maxval);
+        *color2P = randomBrightColor(maxval);
+        *color3P = randomBrightColor(maxval);
+    }
+}
+    
+
+
+static void
 sqAssignColors(unsigned int const circlecount,
                pixval       const maxval,
+               ColorTable * const colorTableP,
                pixel *      const colors) {
 
-    pixel const rc1 = randomBrightColor(maxval);
-    pixel const rc2 = randomBrightColor(maxval);
-    pixel const rc3 = randomBrightColor(maxval);
     float const cco3 = (circlecount - 1) / 3.0;
 
+    pixel rc1;
+    pixel rc2;
+    pixel rc3;
     unsigned int i;
 
-    for (i = 0; i < circlecount ; ++i) {
+    chooseSqPoleColors(colorTableP, maxval, &rc1, &rc2, &rc3);
+
+    for (i = 0; i < circlecount; ++i) {
         if (i < cco3) {
             float const frac = (float)i/cco3;
             PPM_ASSIGN(colors[i],
@@ -984,14 +1310,19 @@ sqAssignColors(unsigned int const circlecount,
 
 
 static void
-clearImageToBlack(pixel **     const pixels,
-                  unsigned int const cols,
-                  unsigned int const rows,
-                  pixval       const maxval) {
+clearBackgroundSquig(pixel **     const pixels,
+                     unsigned int const cols,
+                     unsigned int const rows,
+                     ColorTable * const colorTableP,
+                     pixval       const maxval) {
 
     pixel color;
 
-    PPM_ASSIGN(color, 0, 0, 0);
+    if (colorTableP->count > 0) {
+        color = colorTableP->color[0];
+        colorTableP->index = 1;
+    } else 
+        PPM_ASSIGN(color, 0, 0, 0);
 
     ppmd_filledrectangle(
         pixels, cols, rows, maxval, 0, 0, cols, rows, PPMD_NULLDRAWPROC,
@@ -1091,27 +1422,37 @@ static void
 squig(pixel **     const pixels,
       unsigned int const cols,
       unsigned int const rows,
+      ColorTable * const colorTableP,
       pixval       const maxval) {
 
     int i;
 
     validateSquigAspect(cols, rows);
     
-    clearImageToBlack(pixels, cols, rows, maxval);
+    clearBackgroundSquig(pixels, cols, rows, colorTableP, maxval);
 
     /* Draw the squigs. */
     ppmd_setlinetype(PPMD_LINETYPE_NODIAGS);
     ppmd_setlineclip(0);
+
     for (i = SQUIGS; i > 0; --i) {
         unsigned int const radius = (cols + rows) / 2 / (25 + i * 2);
 
+        struct Squig squig;
+
+        SqClientData sqClientData;
+
         ppmd_point c[SQ_POINTS];
         ppmd_point p0, p1, p2, p3;
-        sq_circlecount = 0;
+
+        squig.circleCt = 0;
+
+        sqClientData.squigP = &squig;
+
         ppmd_circlep(pixels, cols, rows, maxval,
                      ppmd_makePoint(0, 0), radius,
-                     sqMeasureCircleDrawproc, NULL);
-        sqAssignColors(sq_circlecount, maxval, sq_colors);
+                     sqMeasureCircleDrawproc, &sqClientData);
+        sqAssignColors(squig.circleCt, maxval, colorTableP, squig.color);
 
         chooseWrapAroundPoint(cols, rows, &c[0], &c[SQ_POINTS-1],
                               &p0, &p1, &p2, &p3);
@@ -1131,13 +1472,13 @@ squig(pixel **     const pixels,
 
         ppmd_linep(
             pixels, cols, rows, maxval, p0, p1,
-            sqRainbowCircleDrawproc, NULL);
+            sqRainbowCircleDrawproc, &sqClientData);
         ppmd_polysplinep(
             pixels, cols, rows, maxval, p1, SQ_POINTS, c, p2,
-            sqRainbowCircleDrawproc, NULL);
+            sqRainbowCircleDrawproc, &sqClientData);
         ppmd_linep(
             pixels, cols, rows, maxval, p2, p3,
-            sqRainbowCircleDrawproc, NULL);
+            sqRainbowCircleDrawproc, &sqClientData);
     }
 }
 
@@ -1146,7 +1487,7 @@ squig(pixel **     const pixels,
 int
 main(int argc, const char ** argv) {
 
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
     pixel ** pixels;
 
     pm_proginit(&argc, argv);
@@ -1161,35 +1502,53 @@ main(int argc, const char ** argv) {
 
     switch (cmdline.basePattern) {
     case PAT_GINGHAM2:
-        gingham2(pixels, cmdline.width, cmdline.height, PPM_MAXMAXVAL);
+        gingham2(pixels, cmdline.width, cmdline.height,
+                 cmdline.colorTable, PPM_MAXMAXVAL);
         break;
 
     case PAT_GINGHAM3:
-        gingham3(pixels, cmdline.width, cmdline.height, PPM_MAXMAXVAL);
+        gingham3(pixels, cmdline.width, cmdline.height,
+                 cmdline.colorTable, PPM_MAXMAXVAL);
         break;
 
     case PAT_MADRAS:
-        madras(pixels, cmdline.width, cmdline.height, PPM_MAXMAXVAL);
+        madras(pixels, cmdline.width, cmdline.height,
+               cmdline.colorTable, PPM_MAXMAXVAL);
         break;
 
     case PAT_TARTAN:
-        tartan(pixels, cmdline.width, cmdline.height, PPM_MAXMAXVAL);
+        tartan(pixels, cmdline.width, cmdline.height,
+               cmdline.colorTable, PPM_MAXMAXVAL);
+        break;
+
+    case PAT_ARGYLE1:
+        argyle(pixels, cmdline.width, cmdline.height,
+               cmdline.colorTable, PPM_MAXMAXVAL, FALSE);
+        break;
+
+    case PAT_ARGYLE2:
+        argyle(pixels, cmdline.width, cmdline.height,
+               cmdline.colorTable, PPM_MAXMAXVAL, TRUE);
         break;
 
     case PAT_POLES:
-        poles(pixels, cmdline.width, cmdline.height, PPM_MAXMAXVAL);
+        poles(pixels, cmdline.width, cmdline.height,
+              &cmdline.colorTable, PPM_MAXMAXVAL);
         break;
 
     case PAT_SQUIG:
-        squig(pixels, cmdline.width, cmdline.height, PPM_MAXMAXVAL);
+        squig(pixels, cmdline.width, cmdline.height,
+              &cmdline.colorTable, PPM_MAXMAXVAL);
         break;
 
     case PAT_CAMO:
-        camo(pixels, cmdline.width, cmdline.height, PPM_MAXMAXVAL, 0);
+        camo(pixels, cmdline.width, cmdline.height,
+             &cmdline.colorTable, PPM_MAXMAXVAL, 0);
         break;
 
     case PAT_ANTICAMO:
-        camo(pixels, cmdline.width, cmdline.height, PPM_MAXMAXVAL, 1);
+        camo(pixels, cmdline.width, cmdline.height,
+             &cmdline.colorTable, PPM_MAXMAXVAL, 1);
         break;
 
     default:
@@ -1201,6 +1560,8 @@ main(int argc, const char ** argv) {
 
     ppm_freearray(pixels, cmdline.height);
 
+    freeCmdline(cmdline);
+
     return 0;
 }
 
diff --git a/lib/libpm.c b/lib/libpm.c
index d5bad7a4..2e2097ec 100644
--- a/lib/libpm.c
+++ b/lib/libpm.c
@@ -517,13 +517,51 @@ pm_init(const char * const progname,
 
 
 
+static const char *
+dtMsg(time_t const dateTime) {
+/*----------------------------------------------------------------------------
+   Text for the version message to indicate datetime 'dateTime'.
+-----------------------------------------------------------------------------*/
+    struct tm * const brokenTimeP = localtime(&dateTime);
+
+    char buffer[100];
+    
+    strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", brokenTimeP);
+
+    return pm_strdup(buffer);
+}
+
+
+
 static void
 showVersion(void) {
-    pm_message( "Using libnetpbm from Netpbm Version: %s", NETPBM_VERSION );
-#if defined(COMPILE_TIME) && defined(COMPILED_BY)
-    pm_message( "Compiled %s by user \"%s\"",
-                COMPILE_TIME, COMPILED_BY );
+
+    pm_message("Using libnetpbm from Netpbm Version: %s", NETPBM_VERSION);
+
+    /* SOURCE_DATETIME is defined when the user wants a reproducible build,
+       so wants the source code modification datetime instead of the build
+       datetime in the object code.
+    */
+#if defined(SOURCE_DATETIME)
+    {
+        const char * const sourceDtMsg = dtMsg(SOURCE_DATETIME);
+        pm_message("Built from source dated %s", sourceDtMsg);
+        pm_strfree(sourceDtMsg);
+    }
+#else
+  #if defined(BUILD_DATETIME)
+    {
+        const char * const buildDtMsg = dtMsg(BUILD_DATETIME);
+        pm_message("Built at %s", buildDtMsg);
+        pm_strfree(buildDtMsg);
+    }
+  #endif
 #endif
+
+#if defined(COMPILED_BY)
+    pm_message("Built by %s", COMPILED_BY);
+#endif
+
 #ifdef BSD
     pm_message( "BSD defined" );
 #endif /*BSD*/
diff --git a/lib/libppmd.c b/lib/libppmd.c
index 262679ec..02c34015 100644
--- a/lib/libppmd.c
+++ b/lib/libppmd.c
@@ -981,7 +981,7 @@ typedef struct fillobj {
 
     /* The only reason we have a struct fillState separate from
        struct fillobj is that the drawproc interface is defined to
-       have drawing not modify the fillobj, i.e. it passed
+       have drawing not modify the fillobj, i.e. it passes
        const fillobj * to the drawing program.
     */
     struct fillState * stateP;
@@ -1116,14 +1116,13 @@ continueSegment(struct fillState * const stateP,
 
 
 
-
 /* ppmd_fill_drawprocp() is a drawproc that turns an outline drawing function
    into a filled shape function.  This is a somewhat off-label application of
    a drawproc:  A drawproc is intended just to draw a point.  So e.g. you
    might draw a circle with a fat brush by calling ppmd_circle with a drawproc
    that draws a point as a 10-pixel disk.
 
-   But ppmd_fill_drawproc() just draws a point the trivial way: as one pixel.
+   But ppmd_fill_drawprocp() just draws a point the trivial way: as one pixel.
    However, it tracks every point that is drawn in a form that a subsequent
    ppmd_fill() call can use to to fill in the shape drawn, assuming it turns
    out to be a closed shape.
diff --git a/lib/path.c b/lib/path.c
index 10ae92d2..8d53eb9e 100644
--- a/lib/path.c
+++ b/lib/path.c
@@ -58,13 +58,156 @@
 /* NOTE NOTE NOTE
 
    In all the path logic below, we call the direction of increasing row
-   number "up" because we think of the raster as standard Cartesian
+   number "up" because we think of the raster as a standard Cartesian
    plane.  So visualize the image as upside down in the first quadrant
    of the Cartesian plane -- the real top left corner of the image is at
    the origin.
 */
 
 
+ppmd_pathleg
+ppmd_makeLineLeg(ppmd_point const point) {
+
+    ppmd_pathleg retval;
+
+    retval. type = PPMD_PATHLEG_LINE;
+
+    retval.u.linelegparms.end = point;
+
+    return retval;
+}
+
+
+
+ppmd_pathbuilder *
+ppmd_pathbuilder_create() {
+
+    ppmd_pathbuilder * retval;
+
+    MALLOCVAR(retval);
+
+    if (!retval)
+        pm_error("Failed to allocate memory "
+                 "for a ppmd_pathuilder structure");
+
+    retval->path.version = 0;
+    retval->path.legCount = 0;
+    retval->path.legSize = sizeof(ppmd_pathleg);
+    retval->path.legs = NULL;
+
+    retval->begIsSet = false;
+    retval->legsAreAutoAllocated = true;
+    retval->legsAllocSize = 0;
+
+    return retval;
+}
+
+
+
+void
+ppmd_pathbuilder_destroy(ppmd_pathbuilder * const pathBuilderP) {
+
+    if (pathBuilderP->legsAreAutoAllocated) {
+        if (pathBuilderP->path.legs)
+            free(pathBuilderP->path.legs);
+    }
+    free(pathBuilderP);
+}
+
+
+
+void
+ppmd_pathbuilder_setLegArray(ppmd_pathbuilder * const pathBuilderP,
+                             ppmd_pathleg *     const legs,
+                             unsigned int       const legCount) {
+
+    if (pathBuilderP->path.legs)
+        pm_error("Legs array is already set up");
+
+    if (legCount < 1)
+        pm_error("Leg array size must be at least one leg in size");
+
+    if (legs == NULL)
+        pm_error("Leg array pointer is null");
+
+    pathBuilderP->legsAreAutoAllocated = false;
+    
+    pathBuilderP->legsAllocSize = legCount;
+
+    pathBuilderP->path.legs = legs;
+}
+
+
+
+void
+ppmd_pathbuilder_preallocLegArray(ppmd_pathbuilder * const pathBuilderP,
+                                  unsigned int       const legCount) {
+
+    if (pathBuilderP->path.legs)
+        pm_error("Legs array is already set up");
+
+    if (legCount < 1)
+        pm_error("Leg array size must be at least one leg in size");
+
+    MALLOCARRAY(pathBuilderP->path.legs, legCount);
+
+    if (!pathBuilderP->path.legs)
+        pm_error("Unable to allocate memory for %u legs", legCount);
+
+    pathBuilderP->legsAllocSize = legCount;
+}
+
+
+
+void
+ppmd_pathbuilder_setBegPoint(ppmd_pathbuilder * const pathBuilderP,
+                             ppmd_point         const begPoint) {
+
+    pathBuilderP->path.begPoint = begPoint;
+    
+    pathBuilderP->begIsSet = true;
+}
+
+
+
+void
+ppmd_pathbuilder_addLineLeg(ppmd_pathbuilder * const pathBuilderP,
+                            ppmd_pathleg       const leg) {
+
+    if (!pathBuilderP->begIsSet)
+        pm_error("Attempt to add a leg to a path when the "
+                 "beginning point of the path has not been set");
+
+    if (pathBuilderP->path.legCount + 1 > pathBuilderP->legsAllocSize) {
+        if (pathBuilderP->legsAreAutoAllocated) {
+            pathBuilderP->legsAllocSize =
+                MAX(16, pathBuilderP->legsAllocSize * 2);
+
+            REALLOCARRAY(pathBuilderP->path.legs, 
+                         pathBuilderP->legsAllocSize);
+
+            if (pathBuilderP->path.legs == NULL)
+                pm_error("Unable to allocate memory for %u legs", 
+                         pathBuilderP->legsAllocSize);
+        } else
+            pm_error("Out of space in user-supplied legs array "
+                     "(has space for %u legs)", pathBuilderP->legsAllocSize);
+    }
+
+    assert(pathBuilderP->path.legCount + 1 <= pathBuilderP->legsAllocSize);
+
+    pathBuilderP->path.legs[pathBuilderP->path.legCount++] = leg;
+}
+
+
+
+const ppmd_path *
+ppmd_pathbuilder_pathP(ppmd_pathbuilder * const pathBuilderP) {
+
+    return &pathBuilderP->path;
+}
+
+
 
 static bool
 pointEqual(ppmd_point const comparator,
@@ -411,12 +554,12 @@ fillLeg(ppmd_point  const begPoint,
 
 
 void
-ppmd_fill_path(pixel **      const pixels, 
-               int           const cols, 
-               int           const rows, 
-               pixval        const maxval,
-               ppmd_path *   const pathP,
-               pixel         const color) {
+ppmd_fill_path(pixel **          const pixels, 
+               int               const cols, 
+               int               const rows, 
+               pixval            const maxval,
+               const ppmd_path * const pathP,
+               pixel             const color) {
 /*----------------------------------------------------------------------------
    Draw a path which defines a closed figure (or multiple closed figures)
    and fill it in.
diff --git a/lib/ppmdraw.h b/lib/ppmdraw.h
index df22b44d..5fd4148c 100644
--- a/lib/ppmdraw.h
+++ b/lib/ppmdraw.h
@@ -9,6 +9,7 @@
 */
 
 #include <netpbm/pm_config.h>
+#include <netpbm/ppm.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -59,6 +60,9 @@ typedef struct {
     } u;
 } ppmd_pathleg;
 
+ppmd_pathleg
+ppmd_makeLineLeg(ppmd_point const point);
+
 typedef struct {
 /*----------------------------------------------------------------------------
    A closed path
@@ -75,9 +79,58 @@ typedef struct {
            as the definition of ppmd_pathleg changes.
         */
     ppmd_pathleg * legs;
+        /* An array of the legs of the path, in order, starting at 'begPoint'.
+        */
 } ppmd_path;
 
+typedef struct {
+
+    ppmd_path path;
+        /* The path we are building (or have built).
+           Null for path.legs means we don't have a leg array yet.
+        */
+
+    bool begIsSet;
+        /* User has set path.begPoint.  If this is false, path.begPoint is
+           meaningless.
+        */
+    
+    unsigned int legsAllocSize;
+        /* How many legs of space is allocated in the leg array path.legs */
+
+    bool legsAreAutoAllocated;
+        /* The array 'legs' is allocated or reallocated automatically by
+           ppmd_path_addlineline(), as opposed to being supplied by the
+           user as part of initializing this structure, never to be altered.
+        */
+    
+} ppmd_pathbuilder;
+
+ppmd_pathbuilder *
+ppmd_pathbuilder_create(void);
+
+void
+ppmd_pathbuilder_destroy(ppmd_pathbuilder * const pathBuilderP);
+
+void
+ppmd_pathbuilder_setLegArray(ppmd_pathbuilder * const pathBuilderP,
+                             ppmd_pathleg *     const legs,
+                             unsigned int       const legCount);
+
+void
+ppmd_pathbuilder_preallocLegArray(ppmd_pathbuilder * const pathBuilderP,
+                                  unsigned int       const legCount);
+
+void
+ppmd_pathbuilder_setBegPoint(ppmd_pathbuilder * const pathBuilderP,
+                             ppmd_point         const begPoint);
+
+void
+ppmd_pathbuilder_addLineLeg(ppmd_pathbuilder * const pathBuilderP,
+                            ppmd_pathleg       const leg);
 
+const ppmd_path *
+ppmd_pathbuilder_pathP(ppmd_pathbuilder * const pathBuilderP);
 
 typedef void ppmd_drawprocp(pixel **, unsigned int, unsigned int,
                             pixval, ppmd_point, const void *);
@@ -272,12 +325,12 @@ ppmd_filledrectangle(pixel **      const pixels,
 
 
 void
-ppmd_fill_path(pixel **      const pixels, 
-               int           const cols, 
-               int           const rows, 
-               pixval        const maxval,
-               ppmd_path *   const pathP,
-               pixel         const color);
+ppmd_fill_path(pixel **          const pixels, 
+               int               const cols, 
+               int               const rows, 
+               pixval            const maxval,
+               const ppmd_path * const pathP,
+               pixel             const color);
     /* Fills in a closed path.  Not much different from ppmd_fill(),
        but with a different interface.
     */
diff --git a/lib/util/nstring.h b/lib/util/nstring.h
index 7238a76e..794fb232 100644
--- a/lib/util/nstring.h
+++ b/lib/util/nstring.h
@@ -3,6 +3,7 @@
 
 #include <stdarg.h>
 #include <string.h>
+#include <strings.h>  /* For strncasecmp */
 #include <ctype.h>
 
 #include "pm_c_util.h"
diff --git a/test/Available-Testprog b/test/Available-Testprog
index 67df13c1..8176b57a 100755
--- a/test/Available-Testprog
+++ b/test/Available-Testprog
@@ -15,6 +15,10 @@ fi
 # Normal operation: Walk through the argument list and exit if an
 # unavailable program is encountered.
 
+# See http://netpbm.sourceforge.net/prereq.html and the makefiles in
+# each directory (for example converter/other/Makefile) for library
+# requirements and relevant variables.
+
 for i in $@
     do
     case $i in
@@ -39,14 +43,33 @@ for i in $@
       pnmtotiff|\
       pnmtotiffcmyk|\
       tifftopnm)
-        [ "${TIFFLIB}" = "NONE" ] && exit 1 ;;
+        [ "${TIFFLIB}" = "NONE" -o \
+          "${JPEGLIB}" = "NONE" -o \
+          "${ZLIB}" = "NONE" ] && exit 1 ;;
 
       pnmtorle|\
       rletopnm)
         [ "${URTLIB}" = "NONE" ] && exit 1 ;;
 
+      pamx)
+        [ "${X11LIB}" = "NONE" ] && exit 1 ;;
+
+      svgtopam)
+        [ "${XML2_LIBS}" = "NONE" ] && exit 1 ;;
+
+      thinkjettopbm)
+        [ -z "${LEX}" ] && exit 1 ;;
+
+      zlib)
+        [ "${ZLIB}" = "NONE" ] && exit 1 ;;
+
     esac
 done
 
 # All checks passed.  Exit with success status.
-exit 0
\ No newline at end of file
+exit 0
+
+
+# TODO: We don't have a good method for testing whether PNGLIB is
+# available for linking.
+# Affected programs: pamtopng, pngtopam, pngtopnm, pnmtopng
diff --git a/test/Test-Order b/test/Test-Order
index 38873bd4..8c5a7aae 100644
--- a/test/Test-Order
+++ b/test/Test-Order
@@ -124,6 +124,7 @@ gif-quant-roundtrip.test
 hdiff-roundtrip.test
 ilbm-roundtrip.test
 jbig-roundtrip.test
+jpeg2k-roundtrip.test
 leaf-roundtrip.test
 macp-roundtrip.test
 mda-roundtrip.test
@@ -136,6 +137,7 @@ pict-roundtrip.test
 png-roundtrip.test
 png-roundtrip2.test
 ps-roundtrip.test
+ps-flate-roundtrip.test
 ps-alt-roundtrip.test
 sgi-roundtrip.test
 sbig-roundtrip.test
@@ -144,6 +146,7 @@ sunicon-roundtrip.test
 sunrast-roundtrip.test
 targa-roundtrip.test
 tiff-roundtrip.test
+tiff-flate-lzw-roundtrip.test
 utahrle-roundtrip.test
 wbmp-roundtrip.test
 winicon-roundtrip.test
@@ -155,4 +158,5 @@ xwd-roundtrip.test
 # Round-trip tests : lossy converters
 
 fiasco-roundtrip.test
+jpeg-roundtrip.test
 yuv-roundtrip.test
diff --git a/test/fiasco-roundtrip.ok b/test/fiasco-roundtrip.ok
index e26677f4..8a5e8ff6 100644
--- a/test/fiasco-roundtrip.ok
+++ b/test/fiasco-roundtrip.ok
@@ -1 +1 @@
-215556145 102615
+  3 1 1 1
\ No newline at end of file
diff --git a/test/fiasco-roundtrip.test b/test/fiasco-roundtrip.test
index f733c04a..6fd43432 100755
--- a/test/fiasco-roundtrip.test
+++ b/test/fiasco-roundtrip.test
@@ -1,8 +1,20 @@
 #! /bin/bash
 # This script tests: pnmtofiasco fiascotopnm
-# Also requires: pnmpad
+# Also requires: pnmpad pnmpsnr
 
-# Should print 215556145 102615
+# Pnmtofiasco: number of rows, cols in input file must be even
+# Pnmpsnr output: 15.11 22.71 30.09
+# TODO: As in jpeg-rountrip.test the threshold has been determined
+# without much thought.
 
-pnmpad --black --bottom 1 --left 1 testimg.ppm | \
-    pnmtofiasco --progress-meter 0 | fiascotopnm | cksum
+# Should print 3 1 1 1
+
+tmpdir=${tmpdir:-/tmp}
+padded_ppm=${tmpdir}/padded.ppm
+
+pnmpad --black --bottom 1 --left 1 testimg.ppm > ${padded_ppm} &&
+pnmtofiasco --progress-meter 0 ${padded_ppm} | fiascotopnm | \
+    pnmpsnr -machine - ${padded_ppm} | \
+    awk '{printf("%3d %1d %1d %1d",NF,$1>14.0,$2>21.0,$3>$29.0)}'
+
+rm ${padded_ppm}
\ No newline at end of file
diff --git a/test/jpeg-roundtrip.ok b/test/jpeg-roundtrip.ok
new file mode 100644
index 00000000..f32c58a2
--- /dev/null
+++ b/test/jpeg-roundtrip.ok
@@ -0,0 +1,3 @@
+  3 1 1 1
+  3 1 1 1
+  3 1 1 1
diff --git a/test/jpeg-roundtrip.test b/test/jpeg-roundtrip.test
new file mode 100755
index 00000000..1afc5423
--- /dev/null
+++ b/test/jpeg-roundtrip.test
@@ -0,0 +1,21 @@
+#! /bin/bash
+# This script tests: pnmtojpeg jpegtopnm
+# Also requires: pnmpsnr
+
+# TODO: threshold has been determined without much thought.
+# Observed pnmpsnr output: 56.20 58.26 49.38
+# A small margin has been added to the above numbers.
+
+# Should print 3 1 1 1 three times
+
+pnmtojpeg testimg.ppm | jpegtopnm | \
+  pnmpsnr -machine - testimg.ppm |\
+  awk '{printf("%3d %1d %1d %1d\n",NF,$1>55.0,$2>57.0,$3>48.0)}'
+
+pnmtojpeg testimg.ppm -opt | jpegtopnm | \
+  pnmpsnr -machine - testimg.ppm |\
+  awk '{printf("%3d %1d %1d %1d\n",NF,$1>55.0,$2>57.0,$3>48.0)}'
+
+pnmtojpeg testimg.ppm -progressive | jpegtopnm | \
+  pnmpsnr -machine - testimg.ppm |\
+  awk '{printf("%3d %1d %1d %1d\n",NF,$1>55.0,$2>57.0,$3>48.0)}'
\ No newline at end of file
diff --git a/test/jpeg2k-roundtrip.ok b/test/jpeg2k-roundtrip.ok
new file mode 100644
index 00000000..82eac5a8
--- /dev/null
+++ b/test/jpeg2k-roundtrip.ok
@@ -0,0 +1 @@
+1926073387 101484
diff --git a/test/jpeg2k-roundtrip.test b/test/jpeg2k-roundtrip.test
new file mode 100755
index 00000000..eab6fb1e
--- /dev/null
+++ b/test/jpeg2k-roundtrip.test
@@ -0,0 +1,7 @@
+#! /bin/bash
+# This script tests: pamtojpeg2k jpeg2ktopam
+# Also requires: pnmpsnr
+
+# Should produce 1926073387 101484
+
+pamtojpeg2k testimg.ppm | jpeg2ktopam | cksum
\ No newline at end of file
diff --git a/test/ppmchange.ok b/test/ppmchange.ok
index 130c3c45..aba3a7a8 100644
--- a/test/ppmchange.ok
+++ b/test/ppmchange.ok
@@ -1,4 +1,9 @@
 22488533 203
 1008787190 613
-3885709071 613
-2101746192 613
+1983174784 613
+2146447222 613
+1216791938 613
+     0     0     0	    0	     78 
+     0     0   255	   29	     40 
+     0    50     2	   30	     41 
+   100     0     1	   30	     41 
diff --git a/test/ppmchange.test b/test/ppmchange.test
index 397b290f..a749a5d2 100755
--- a/test/ppmchange.test
+++ b/test/ppmchange.test
@@ -1,6 +1,6 @@
 #! /bin/bash
 # This script tests: ppmchange
-# Also requires: ppmrainbow pgmramp
+# Also requires: ppmrainbow pgmramp ppmhist
 
 
 #  Failure message
@@ -10,9 +10,10 @@
 
 tmpdir=${tmpdir:-/tmp}
 rainbow_ppm=${tmpdir}/rainbow.ppm
+changed_ppm=${tmpdir}/changed.ppm
 
-# Explicit values for intermediate colors: rgb.txt may be defining them
-# in unusual ways.
+# Explicit values for intermediate colors: rgb.txt may not be the one
+# Netpbm provides; they may be defined in unusual ways.
 
 brown=rgb:67/43/00
 cyan=rgb:00/ff/ff
@@ -20,7 +21,7 @@ yellow=rgb:ff/ff/00
 gray=rgb:7f/7f/7f
 
 
-# Test 1. Should print 811868957 60
+# Test 1. Should print 22488533 203
 pgmramp -lr 8 8 | ppmchange black black  white white  $gray $gray \
   -close=10 -remainder=blue | cksum
 
@@ -31,29 +32,31 @@ ppmrainbow -tmpdir=$tmpdir -width=200 -height=1 red green blue | \
   tee ${rainbow_ppm} | \
   ppmchange red $brown   green $brown   blue $brown | cksum
 
+# Validate ${rainbow_ppm}
+# Should print 1983174784 613
 
-# Test 3. Should print 3885709071 613
+cat ${rainbow_ppm} | cksum
+
+
+# Test 3. Should print 2146447222 613
 
 ppmchange red $brown   green $cyan   blue $yellow \
   -closeness=25 ${rainbow_ppm} | cksum
 
 
-# Test 4. Should print 2101746192 613
+# Test 4. Should print 1216791938 613
 
 ppmchange red rgb:64/00/01 rgb:00/ff/00 rgb:00/32/02 blue blue \
-  -remainder=black -closeness=25 ${rainbow_ppm} | cksum
+  -remainder=black -closeness=25 ${rainbow_ppm} | tee ${changed_ppm} | cksum
 
-rm ${rainbow_ppm}
+# Produce a histogram of the colors in the output image
+# Should produce
+#     0     0     0	    0	     78 
+#     0     0   255	   29	     40 
+#     0    50     2	   30	     41 
+#   100     0     1	   30	     41 
 
+ppmhist -sort=rgb -noheader ${changed_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
+rm ${rainbow_ppm} ${changed_ppm}
diff --git a/test/ps-flate-roundtrip.ok b/test/ps-flate-roundtrip.ok
new file mode 100644
index 00000000..57fb124f
--- /dev/null
+++ b/test/ps-flate-roundtrip.ok
@@ -0,0 +1,3 @@
+1926073387 101484
+1926073387 101484
+1386192571 507420
diff --git a/test/ps-flate-roundtrip.test b/test/ps-flate-roundtrip.test
new file mode 100755
index 00000000..de1105f0
--- /dev/null
+++ b/test/ps-flate-roundtrip.test
@@ -0,0 +1,48 @@
+#! /bin/bash
+# This script tests: pnmtops pstopnm
+# Also requires: pamtopnm gs zlib
+
+# This script tests the optional flate (zlib) compression feature of
+# pstopnm.
+# Flate compression allows you to make smaller output (.ps) files:
+# it is useful, but not essential.  Flate compression is not neccessary for
+# printing pages with postscript printers - which is why many people need
+# pnmtops on their systems.
+
+# Failure message
+## This test fails when ghostscript is not available.
+
+tmpdir=${tmpdir:-/tmp}
+
+# pstopnm does not use libnetpbm functions for output.
+# Output is filtered through pamtopnm.
+
+# Test 1.  Should print: 1926073387 101484 twice
+test1_ps=${tmpdir}/testimg1.ps
+
+for flag in "-ps -flate" "-ps -rle -ascii -flate"
+  do
+  pnmtops -nocenter -equalpixels -dpi 72 -noturn ${flag} testimg.ppm \
+    > ${test1_ps} && \
+  xysize1=`awk  '/BoundingBox/ {print "-xsize="$4,"-ysize="$5}' \
+    ${test1_ps}` && \
+  pstopnm -portrait -xborder=0 -yborder=0 $xysize1 -stdout -quiet \
+    ${test1_ps} | pamtopnm | cksum
+  done
+
+rm ${test1_ps}
+
+# Test 2. Should print: 1386192571 507420
+# See comments in ps-roundtrip.test
+
+test3_ps=${tmpdir}/testimg3.ps
+flag="-ps -bitspersample=12 -flate -rle -vmreclaim"
+cat testimg.ppm testimg.ppm testimg.ppm testgrid.pbm testgrid.pbm | \
+pnmtops -nocenter -equalpixels -dpi 72 -noturn -setpage ${flag} \
+  > ${test3_ps} &&
+xysize3=`awk  '/BoundingBox/ {print "-xsize="$4,"-ysize="$5 ; exit}' \
+  ${test3_ps}` &&
+pstopnm -portrait -xborder=0 -yborder=0 $xysize3 -stdout  ${test3_ps} | \
+  pamtopnm | cksum
+
+rm ${test3_ps}
diff --git a/test/ps-roundtrip.ok b/test/ps-roundtrip.ok
index 0ebfb94a..5ef66cc4 100644
--- a/test/ps-roundtrip.ok
+++ b/test/ps-roundtrip.ok
@@ -2,7 +2,6 @@
 1926073387 101484
 1926073387 101484
 1926073387 101484
-1926073387 101484
 2918318199 62
 2918318199 62
 2918318199 62
diff --git a/test/ps-roundtrip.test b/test/ps-roundtrip.test
index 873bbdef..207646cc 100755
--- a/test/ps-roundtrip.test
+++ b/test/ps-roundtrip.test
@@ -4,21 +4,17 @@
 
 
 # Failure message
-## This test fails when:
-## (1) zlib was not linked.
-## (2) ghostscript is not available.
+## This test fails when ghostscript is not available.
 
 tmpdir=${tmpdir:-/tmp}
 
 # pstopnm does not use libnetpbm functions for output.
 # Output is filtered through pamtopnm.
 
-# Test 1.  Should print: 1926073387 101484 five times
-# *NOTE* Fifth iteration fails if pnmtops was compiled without zlib
-# (flate compression) support.
+# Test 1.  Should print: 1926073387 101484 four times
 test1_ps=${tmpdir}/testimg1.ps
 
-for flag in "" "-ps" "-rle" "-ps -ascii" "-ps -flate"
+for flag in "" "-ps" "-rle" "-ps -ascii"
   do
   pnmtops -nocenter -equalpixels -dpi 72 -noturn ${flag} testimg.ppm \
     > ${test1_ps} && \
@@ -57,8 +53,6 @@ for flag in "" "-rle" "-ps -rle -ascii" \
 rm ${grid_ps} ${g_pbm} ${t_pbm}
 
 #Test 3. Should print: 1386192571 507420 three times
-# *NOTE* Second iteration fails if pnmtops was compiled without zlib
-# (flate compression) support.
 #
 # Special care is needed when conducting round-trips with multiple-image
 # files as input.
@@ -72,7 +66,7 @@ rm ${grid_ps} ${g_pbm} ${t_pbm}
 test3_ps=${tmpdir}/testimg3.ps
 
 for flag in "" "-ps" \
-            "-ps -bitspersample=12 -flate -rle -vmreclaim"
+            "-ps -bitspersample=12 -rle -vmreclaim"
   do
 cat testimg.ppm testimg.ppm testimg.ppm testgrid.pbm testgrid.pbm | \
 pnmtops -nocenter -equalpixels -dpi 72 -noturn -setpage ${flag} \
diff --git a/test/tiff-flate-lzw-roundtrip.ok b/test/tiff-flate-lzw-roundtrip.ok
new file mode 100644
index 00000000..0ee3e083
--- /dev/null
+++ b/test/tiff-flate-lzw-roundtrip.ok
@@ -0,0 +1,7 @@
+764594701 101484
+764594701 101484
+764594701 101484
+764594701 101484
+764594701 101484
+764594701 101484
+2425386270 41
diff --git a/test/tiff-flate-lzw-roundtrip.test b/test/tiff-flate-lzw-roundtrip.test
new file mode 100755
index 00000000..7e28a899
--- /dev/null
+++ b/test/tiff-flate-lzw-roundtrip.test
@@ -0,0 +1,33 @@
+#! /bin/bash
+# This script tests: pamtotiff tifftopnm
+# Also requires: pnmquant
+
+# Failure message
+## If tiff-rountrip.test succeeds and this test fails, the likely
+## cause is an old TIFF library which lacks certain compression
+## features.
+
+tmpdir=${tmpdir:-/tmp}
+
+test40_ppm=${tmpdir}/testimg40.ppm
+
+# Make a test image with reduced colors which compresses better
+# cksum is 764594701 101484
+ 
+pnmquant 40 testimg.ppm | tee ${test40_ppm} | cksum
+pamtotiff ${test40_ppm} | tifftopnm | cksum
+
+# test flate compression
+pamtotiff -flate ${test40_ppm} | tifftopnm | cksum
+
+# test adobeflate compression
+pamtotiff -adobeflate ${test40_ppm} | tifftopnm | cksum
+
+# test LZW compression
+pamtotiff -lzw ${test40_ppm} | tifftopnm | cksum
+pamtotiff -lzw -predictor=1 ${test40_ppm} | tifftopnm | cksum
+
+# PBM image: test flate compression
+pamtotiff -flate testgrid.pbm | tifftopnm | cksum
+
+rm ${test40_ppm}
diff --git a/test/tiff-roundtrip.ok b/test/tiff-roundtrip.ok
index 0e712ce7..f6f04103 100644
--- a/test/tiff-roundtrip.ok
+++ b/test/tiff-roundtrip.ok
@@ -1,4 +1,8 @@
 1926073387 101484
 1926073387 101484
+1926073387 101484
+1926073387 101484
+1926073387 101484
+2425386270 41
 2425386270 41
 2425386270 41
diff --git a/test/tiff-roundtrip.test b/test/tiff-roundtrip.test
index 624337f1..747006b3 100755
--- a/test/tiff-roundtrip.test
+++ b/test/tiff-roundtrip.test
@@ -3,20 +3,22 @@
 # Also requires:
 
 # Failure message
-## Second test fails if Netpbm was built without the flate library
+## If this test fails, the cause may be a problem in the TIFF library.
 
-pamtotiff testimg.ppm 1<>${tmpdir}/test1.tiff &&
-  tifftopnm ${tmpdir}/test1.tiff | cksum
+# PPM image
+# Should print 1926073387 101484 five times
 
-# test flate compression
-pamtotiff -flate testimg.ppm 1<>${tmpdir}/test2.tiff &&
-  tifftopnm ${tmpdir}/test2.tiff | cksum
+pamtotiff testimg.ppm | tifftopnm | cksum
+pamtotiff -truecolor testimg.ppm | tifftopnm | cksum
 
-pamtotiff testgrid.pbm 1<>${tmpdir}/test3.tiff &&
-  tifftopnm ${tmpdir}/test3.tiff | cksum
+pamtotiff -truecolor -packbits testimg.ppm | tifftopnm | cksum
+pamtotiff -truecolor -rowsperstrip=2 testimg.ppm | tifftopnm | cksum
+pamtotiff -truecolor -lsb2msb  testimg.ppm | \
+  tifftopnm -respectfillorder | cksum
 
-# test G4 compression
-pamtotiff -g4 testgrid.pbm 1<>${tmpdir}/test4.tiff &&
-  tifftopnm ${tmpdir}/test4.tiff | cksum
+# PBM image
+# Should print 2425386270 41 three times
 
-rm ${tmpdir}/test[1234].tiff
+pamtotiff testgrid.pbm | tifftopnm | cksum
+pamtotiff -g3 -fill testgrid.pbm | tifftopnm | cksum
+pamtotiff -g4 testgrid.pbm | tifftopnm | cksum
diff --git a/version.mk b/version.mk
index 6b662432..28ae0067 100644
--- a/version.mk
+++ b/version.mk
@@ -1,3 +1,3 @@
 NETPBM_MAJOR_RELEASE = 10
-NETPBM_MINOR_RELEASE = 77
-NETPBM_POINT_RELEASE = 4
+NETPBM_MINOR_RELEASE = 78
+NETPBM_POINT_RELEASE = 0