about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--GNUmakefile2
-rw-r--r--Makefile.version4
-rw-r--r--buildtools/Makefile.manpage98
-rwxr-xr-xbuildtools/configure.pl40
-rwxr-xr-xbuildtools/makeman50
-rw-r--r--converter/other/bmepsoe.c217
-rw-r--r--converter/other/giftopnm.c679
-rw-r--r--converter/other/jpegdatasource.c36
-rw-r--r--converter/other/jpegdatasource.h3
-rw-r--r--converter/other/jpegtopnm.c143
-rw-r--r--converter/other/pamtogif.c413
-rw-r--r--converter/other/pamtoxvmini.c3
-rw-r--r--converter/other/pnmtojpeg.c3
-rw-r--r--converter/other/pnmtops.c4
-rw-r--r--converter/other/xwdtopnm.c496
-rw-r--r--converter/pgm/pgmtopgm.c4
-rw-r--r--converter/ppm/picttoppm.c760
-rw-r--r--doc/HISTORY290
-rw-r--r--editor/pambackground.c362
-rw-r--r--editor/pamcut.c6
-rw-r--r--editor/pamditherbw.c192
-rw-r--r--editor/pamflip.c6
-rw-r--r--editor/pammixinterlace.c285
-rw-r--r--editor/ppm3d.c357
-rw-r--r--editor/ppmbrighten.c11
-rw-r--r--lib/fileio.h6
-rw-r--r--lib/libpam.c11
-rw-r--r--lib/libpamread.c2
-rw-r--r--lib/libpbm3.c28
-rw-r--r--lib/libpm.c29
-rw-r--r--lib/pm.h5
-rw-r--r--lib/util/pm_c_util.h8
-rw-r--r--lib/util/shhopt.c121
-rw-r--r--other/Makefile2
-rw-r--r--other/pamfixtrunc.c175
35 files changed, 3353 insertions, 1498 deletions
diff --git a/GNUmakefile b/GNUmakefile
index 0f5c04fd..a65ac0d4 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -174,7 +174,7 @@ package: build_complete package_build advise_installnetpbm
 
 build_complete:
 # The regular build creates this file as its last act, so if it doesn't exist,
-# that means either the user skipping the build step, or the build failed.
+# that means either the user skipped the build step, or the build failed.
 	@echo "You must build Netpbm before you can package Netpbm. "
 	@echo "The usual way to do this is to type 'make' with no arguments."
 	@echo "If you did that, then the build apparently failed.  There "
diff --git a/Makefile.version b/Makefile.version
index e0fd217a..5099c5fe 100644
--- a/Makefile.version
+++ b/Makefile.version
@@ -1,4 +1,4 @@
 NETPBM_MAJOR_RELEASE = 10
-NETPBM_MINOR_RELEASE = 37
-NETPBM_POINT_RELEASE = 06
+NETPBM_MINOR_RELEASE = 38
+NETPBM_POINT_RELEASE = 0
 
diff --git a/buildtools/Makefile.manpage b/buildtools/Makefile.manpage
index e8c17972..b25bc19e 100644
--- a/buildtools/Makefile.manpage
+++ b/buildtools/Makefile.manpage
@@ -4,7 +4,7 @@
 
 MAKEMAN = makeman
 
-MANDIR = /usr/share/man/man1
+MANDIR = /usr/share/man/
 
 # These can convert to man pages cleanly
 MAN1 = \
@@ -16,6 +16,7 @@ MAN1 = \
 	bmptopnm.1 \
 	bmptoppm.1 \
 	brushtopbm.1 \
+	cameratopam.1 \
 	cmuwmtopbm.1 \
 	ddbugtopbm.1 \
 	escp2topbm.1 \
@@ -41,6 +42,7 @@ MAN1 = \
 	leaftoppm.1 \
 	lispmtopgm.1 \
 	macptopbm.1 \
+	manweb.1 \
 	mdatopbm.1 \
 	mgrtopbm.1 \
 	mrf.1 \
@@ -58,6 +60,7 @@ MAN1 = \
 	pamedge.1 \
 	pamendian.1 \
 	pamfile.1 \
+	pamfixtrunc.1 \
 	pamflip.1 \
 	pamfunc.1 \
 	pamgauss.1 \
@@ -74,8 +77,8 @@ MAN1 = \
 	pamstereogram.1 \
 	pamstretch-gen.1 \
 	pamstretch.1 \
-	pamsummcol.1 \
 	pamsumm.1 \
+	pamsummcol.1 \
 	pamtodjvurle.1 \
 	pamtohdiff.1 \
 	pamtohtmltbl.1 \
@@ -186,8 +189,8 @@ MAN1 = \
 	pnmquant.1 \
 	pnmremap.1 \
 	pnmrotate.1 \
-	pnmscalefixed.1 \
 	pnmscale.1 \
+	pnmscalefixed.1 \
 	pnmshear.1 \
 	pnmsmooth.1 \
 	pnmsplit.1 \
@@ -208,8 +211,8 @@ MAN1 = \
 	pnmtorle.1 \
 	pnmtosgi.1 \
 	pnmtosir.1 \
-	pnmtotiffcmyk.1 \
 	pnmtotiff.1 \
+	pnmtotiffcmyk.1 \
 	pnmtoxwd.1 \
 	ppm3d.1 \
 	ppmbrighten.1 \
@@ -230,8 +233,8 @@ MAN1 = \
 	ppmnorm.1 \
 	ppmntsc.1 \
 	ppmpat.1 \
-	ppmquantall.1 \
 	ppmquant.1 \
+	ppmquantall.1 \
 	ppmrainbow.1 \
 	ppmrelief.1 \
 	ppmrough.1 \
@@ -243,12 +246,12 @@ MAN1 = \
 	ppmtoarbtxt.1 \
 	ppmtobmp.1 \
 	ppmtoeyuv.1 \
-	ppmtogif.1 \
 	ppmtoicr.1 \
 	ppmtoilbm.1 \
 	ppmtojpeg.1 \
 	ppmtoleaf.1 \
 	ppmtolj.1 \
+	ppmtomap.1 \
 	ppmtomitsu.1 \
 	ppmtompeg.1 \
 	ppmtoneo.1 \
@@ -301,6 +304,35 @@ MAN1 = \
 	yuvsplittoppm.1 \
 	yuvtoppm.1 \
 	zeisstopnm.1 \
+        pamaddnoise.1 \
+        pambackground.1 \
+        pambayer.1 \
+        pamdepth.1 \
+        pamenlarge.1 \
+        pamgradient.1 \
+        pammasksharpen.1 \
+        pammixinterlace.1 \
+        pampick.1 \
+        pamrgbatopng.1 \
+        pamsplit.1 \
+        pamthreshold.1 \
+        pamtilt.1 \
+        pamtofits.1 \
+        pamtogif.1 \
+        pamtosvg.1 \
+        pamtotiff.1 \
+        pamtoxvmini.1 \
+        pamx.1 \
+        pbmtoibm23xx.1 \
+        pbmtomatrixorbital.1 \
+        pgmdeshadow.1 \
+        pgmmake.1 \
+        pgmmedian.1 \
+        ppmdcfont.1 \
+        ppmddumpfont.1 \
+        ppmdmkfont.1 \
+        ppmdraw.1 \
+        rlatopam.1 \
 
 MAN3 = \
 	libnetpbm.3 \
@@ -318,39 +350,57 @@ MAN5 = \
 	extendedopacity.5 \
 	pam.5 \
 	pbm.5 \
+        pfm.5 \
 	pgm.5 \
 	pnm.5 \
 	ppm.5 \
 
+# These things do get converted to man pages and installed.
 MANPAGES = $(MAN1) netpbm.1 $(MAN3) $(MAN5)
 HTMLMANUALS = $(MAN1:.1=.html) $(MAN3:.3=.html) $(MAN5:.5=.html)
+
+# These things don't get converted to manual pages.
+EXCEPTIONS = directory.html libnetpbm_dir.html libnetpbm_draw.html error.html
+STUBS = pcdindex.1 ppmcolors.1 pnmflip.1 ppmtogif.1
+
+# This works if you've done a full SVN checkout.
+USERGUIDE= ../../userguide
+
 XML = $(HTMLMANUALS:.html=.xml) netpbm.xml
 
-# These things don't get converted to manual pages
-# They're basically link lists, not useful in the man hierarchy.
-EXCEPTIONS = directory.html libnetpbm_dir.html
+# List everything in the userguide directory that is not categorized above.
+# Use this to check that 'make manpages' converts as much as possible
+# of the HTML documentation.
+uncategorized:
+	@echo $(HTMLMANUALS) $(EXCEPTIONS) $(STUBS) | tr " " "\n" | sort >LIST1
+	@(cd $(USERGUIDE); ls | sort) >LIST2
+	@comm -3 LIST1 LIST2
+	@rm LIST1 LIST2
 
-# Make man pages -- reports bad lines to standard error
+# Make man pages -- reports bad lines to standard error.
 manpages:
-	@python $(MAKEMAN) index.html $(HTMLMANUALS) 
-	mv index.1 netpbm.1
+	@python $(MAKEMAN) -d $(USERGUIDE) index.html $(HTMLMANUALS) 
+	@mv index.1 netpbm.1
 
 # Make XML pages, and validate them.
 xmlpages:
-	@for x in $(MANPAGES); do doclifter $$x; done
-	@for x in $(XML); do xmllint -xinclude --postvalid $$x >/dev/null; done
+	@for x in $(MANPAGES); do doclifter -v $$x; done
+	@for x in $(MANPAGES); do xmllint -xinclude --postvalid $$x.xml >/dev/null; done
 
 # This will install the generated man pages
-installman: manpages
-	for f in $(MAN1); do \
-	  if [ -f $$f ]; then gzip <$$f >$(MANDIR)/man1/$$f.gz; fi; \
-	  done
-	for f in $(MAN3); do \
-	  if [ -f $$f ]; then gzip <$$f >$(MANDIR)/man3/$$f.gz; fi; \
-	  done
-	for f in $(MAN5); do \
-	  if [ -f $$f ]; then gzip <$$f >$(MANDIR)/man5/$$f.gz; fi; \
-	  done
+installman:
+	set -x
+	for f in $(MAN1); do if [ -f $$f ]; then gzip <$$f >$(MANDIR)/man1/$$f.gz; fi; done
+	for f in $(MAN3); do if [ -f $$f ]; then gzip <$$f >$(MANDIR)/man3/$$f.gz; fi; done
+	for f in $(MAN5); do if [ -f $$f ]; then gzip <$$f >$(MANDIR)/man5/$$f.gz; fi; done
+
+# This will uninstall them
+uninstallman:
+	for f in $(MAN1); do rm -f $(MANDIR)/man1/$$f.gz; fi; done
+	for f in $(MAN3); do rm -f $(MANDIR)/man3/$$f.gz; fi; done
+	for f in $(MAN5); do rm -f $(MANDIR)/man5/$$f.gz; fi; done
+
+oldclean:
 	# Clean up old locations on Fedora Core 2
 	rm -f $(MANDIR)/man1/extendedopacity.1.gz 
 	rm -f $(MANDIR)/man3/directory.3.gz
diff --git a/buildtools/configure.pl b/buildtools/configure.pl
index d0ff36b9..cb1723eb 100755
--- a/buildtools/configure.pl
+++ b/buildtools/configure.pl
@@ -1077,27 +1077,27 @@ sub getLinuxsvgaLibrary($@) {
     my ($svgalib, $svgalibhdr_dir);
 
     if ($platform eq "GNU") {
-        {
-            my $default;
+        my $default;
 
-            if (-d('/usr/link/svgalib')) {
-                $default = '/usr/link/svgalib/libvga.so';
-            } elsif (-d('/usr/lib/svgalib')) {
-                $default = '/usr/lib/svgalib/libvga.so';
-            } else {
-                $default = 'libvga.so';
-            }
-            
-            print("What is your Svgalib library?\n");
+        if (-d('/usr/link/svgalib')) {
+            $default = '/usr/link/svgalib/libvga.so';
+        } elsif (-d('/usr/lib/svgalib')) {
+            $default = '/usr/lib/svgalib/libvga.so';
+        } elsif (system('ldconfig -p | grep libvga &>/dev/null')) {
+            $default = 'libvga.so';
+        } else {
+            $default = 'none';
+        }
             
-            my $response = prompt("library filename or 'none'", $default);
+        print("What is your Svgalib library?\n");
+        
+        my $response = prompt("library filename or 'none'", $default);
             
-            if ($response ne "none") {
-                $svgalib = $response;
-            }
+        if ($response ne 'none') {
+            $svgalib = $response;
         }
     }
-    if (defined($svgalib)) {
+    if (defined($svgalib) && $svgalib ne 'none') {
         my $default;
         
         if (-d('/usr/include/svgalib')) {
@@ -2150,13 +2150,11 @@ close(MAKEFILE_CONFIG) or
     die("Error:  Close of Makefile.config failed.\n");
 
 print("\n");
-print("We have created the file 'Makefile.config'.  Note, however, that \n");
-print("we guessed a lot at your configuration and you may want to look \n");
-print("at Makefile.config and edit it to your requirements and taste \n");
-print("before doing the make.\n");
+print("We have created the file 'Makefile.config'.  You may want to look \n");
+print("at it and edit it to your requirements and taste before doing the \n");
+print("make.\n");
 print("\n");
 
-
 print("Now you may proceed with 'make'\n");
 print("\n");
 
diff --git a/buildtools/makeman b/buildtools/makeman
index 634a2c79..2e122779 100755
--- a/buildtools/makeman
+++ b/buildtools/makeman
@@ -19,7 +19,8 @@
 #  * Loses summary information from tables.
 #  * Only permits one <HR> in the HTML, right before the index.
 #
-# Use the makeman: passthrough to insert format lines for tables.
+# You can use the <?makeman ?> PI to pass text directly through to the
+# generated manual page,  A major use is to insert format lines for tables.
 #
 # By Eric S. Raymond <esr@thyrsus.com>
 # Version 1.0, July 26 2004
@@ -29,7 +30,7 @@ import os, sys, exceptions, re
 source = "netpbm documentation"
 section = 1
 
-warning = '''\
+warning = r'''\
 .\" This man page was generated by the Netpbm tool 'makeman' from HTML source.
 .\" Do not hand-hack it!  If you have bug fixes or improvements, please find
 .\" the corresponding HTML page on the Netpbm website, generate a patch
@@ -47,11 +48,15 @@ def makeman(name, file, indoc):
     # Dot at left margin confuses troff.
     # This program generates these,
     indoc = indoc.replace("\n.", "\n@%@%@")
+    # Protect escapes before we try generating font changes.
+    indoc = indoc.replace("\\", r"\e")
     # Header-bashing
+    indoc = indoc.replace('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "DTD/xhtml11.dtd">', "")
+    indoc = indoc.replace('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">', "")
     indoc = indoc.replace('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">\n',"")
     indoc = indoc.replace('<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">', "")
+    indoc = indoc.replace('<meta http-equiv="Content-Type" content="text/html; charset=us-ascii"/>', "")
     indoc = indoc.replace('<?xml version="1.1" encoding="iso-8859-1" ?>\n',"")
-    indoc = indoc.replace('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "DTD/xhtml11.dtd">', "")
     indoc = indoc.replace('<html xmlns="http://www.w3.org/1999/xhtml">', "")
     indoc = indoc.replace("<HEAD>", "").replace("</HEAD>", "")
     indoc = indoc.replace("<head>", "").replace("</head>", "")
@@ -93,8 +98,8 @@ def makeman(name, file, indoc):
     # Literal layout
     indoc = re.sub("(?i)\n *<PRE>", "\n.nf", indoc)
     indoc = re.sub("(?i)\n *</PRE>", "\n.fi", indoc)
-    indoc = re.sub("(?i)\n *<BLOCKQUOTE>", "\n.nf", indoc)
-    indoc = re.sub("(?i)\n *</BLOCKQUOTE>", "\n.fi", indoc)
+    indoc = re.sub("(?i)\n *<BLOCKQUOTE>", "\n.RS", indoc)
+    indoc = re.sub("(?i)\n *</BLOCKQUOTE>", "\n.RE", indoc)
     # Highlight processing
     indoc = re.sub("(?i)<B>", r"\\fB", indoc)
     indoc = re.sub("(?i)</B>", r"\\fP", indoc)
@@ -108,6 +113,8 @@ def makeman(name, file, indoc):
     indoc = re.sub("(?i)</TT>", r"\\fP", indoc)
     indoc = re.sub("(?i)<KBD>", r"\\f(CW", indoc)
     indoc = re.sub("(?i)</KBD>", r"\\fP", indoc)
+    indoc = re.sub("(?i)<CODE>", r"\\f(CW", indoc)
+    indoc = re.sub("(?i)</CODE>", r"\\fP", indoc)
     indoc = re.sub("(?i)<STRONG>", r"\\fB", indoc)
     indoc = re.sub("(?i)</STRONG>", r"\\fP", indoc)
     indoc = re.sub("(?i)<SUP>", r"\\u", indoc)
@@ -150,6 +157,7 @@ def makeman(name, file, indoc):
     indoc = indoc.replace("&lt;", "@#!#@").replace("&gt;", "#@!@#").replace("&amp;", "#!@!@!#")
     indoc = indoc.replace("&#215;", r"\(mu")
     indoc = indoc.replace("&#174;", r"\*R")
+    indoc = indoc.replace("&copy;", r"\(co")
     # Turn anchors into .UN tags
     indoc = re.sub('(?i)<A NAME *= *"#?([a-zA-Z][a-zA-Z0-9.-]+)">(?:&nbsp;)*</A>\s*', ".UN \\1\n", indoc)
     # Strip off the index trailer
@@ -159,17 +167,17 @@ def makeman(name, file, indoc):
     indoc = indoc.replace("</BODY>", "").replace("</HTML>", "")
     indoc = indoc.replace("</body>", "").replace("</html>", "")
     # Recognize sections with IDs
-    indoc = re.sub('(?i)<H2><A (?:ID|NAME)="([a-zA-Z]+)">([^><]*)</A></H2>',
+    indoc = re.sub('(?i)<H2><A (?:ID|NAME)="([a-zA-Z][_a-zA-Z0-9-]+)">([^><]*)</A></H2>',
                    ".UN \\1\n.SH \\2", indoc)
-    indoc = re.sub('(?i)<H3><A (?:ID|NAME)="([a-zA-Z]+)">([^><]*)</A></H3>',
+    indoc = re.sub('(?i)<H3><A (?:ID|NAME)="([a-zA-Z][_a-zA-Z0-9-]+)">([^><]*)</A></H3>',
                    ".UN \\1\n.SS \\2", indoc)
-    indoc = re.sub('(?i)<H4><A (?:ID|NAME)="([a-zA-Z]+)">([^><]*)</A></H4>',
+    indoc = re.sub('(?i)<H4><A (?:ID|NAME)="([a-zA-Z][_a-zA-Z0-9-]+)">([^><]*)</A></H4>',
                    ".UN \\1\n.B \\2", indoc)
-    indoc = re.sub('(?i)<H2 (?:ID|NAME)="([a-zA-Z]+)">([^><]*)</H2>',
+    indoc = re.sub('(?i)<H2 (?:ID|NAME)="([a-zA-Z][_a-zA-Z0-9-]+)">([^><]*)</H2>',
                    ".UN \\1\n.SH \\2", indoc)
-    indoc = re.sub('(?i)<H3 (?:ID|NAME)="([a-zA-Z]+)">([^><]*)</H3>',
+    indoc = re.sub('(?i)<H3 (?:ID|NAME)="([a-zA-Z][_a-zA-Z0-9-]+)">([^><]*)</H3>',
                    ".UN \\1\n.SS \\2", indoc)
-    indoc = re.sub('(?i)<H4 (?:ID|NAME)="([a-zA-Z]+)">([^><]*)</H4>',
+    indoc = re.sub('(?i)<H4 (?:ID|NAME)="([a-zA-Z][_a-zA-Z0-9-]+)">([^><]*)</H4>',
                    ".UN \\1\n.B \\2", indoc)
     # Sections without IDs
     indoc = re.sub('(?i)<H2>([^><]*)</H2>', ".SH \\1", indoc)
@@ -192,7 +200,7 @@ def makeman(name, file, indoc):
     # Passthrough
     indoc = re.sub(r"<\?makeman (.*) \?>", r'\1', indoc)
     # Comments
-    indoc = re.sub("<!--([^-])*-->", r'.\"\1', indoc)
+    indoc = re.sub("<!--([^\n])*-->", r'.\"\1', indoc)
     # Image tags
     indoc = re.sub(' *<img src="([^"]*)" alt="([^"]*)"( *[a-z]*="?[0-9]*"?)*>', ".B \\2\n.IMG -C \\1", indoc)
     # Special characters
@@ -217,7 +225,7 @@ def makeman(name, file, indoc):
     # Time for error checking now
     badlines = []
     for line in indoc.split("\n"):
-        if "<" in line or ">" in line or re.search("&.*;", line):
+        if "<" in line or ">" in line.replace(" >", "") or re.search("&.*;", line):
             badlines.append(line)
     if badlines:
         sys.stderr.write(("Bad lines from %s:\n-----------------\n" % file) + "\n".join(badlines) + "\n-----------------\n")
@@ -236,19 +244,22 @@ def makeman(name, file, indoc):
 def main(args, mainout=sys.stdout, mainerr=sys.stderr):
     global sectmap
     import getopt
-    (options, arguments) = getopt.getopt(args, "v")
+    (options, arguments) = getopt.getopt(args, "vd:")
+    dirprefix = ""
     verbosity = 0
     for (switch, val) in options:
-        if switch == '-v':
+        if switch == '-d':	# Set HTML input directory
+            dirprefix = val
+        elif switch == '-v':	# Enable verbose error reporting
             verbosity += 1
     try:
         # First pass: gather locations for crossreferences:
         sectmap = {}
         for file in arguments:
             try: 
-                infp = open(file)
+                infp = open(os.path.join(dirprefix, file))
             except:
-                sys.stderr.write("can't open %s" % name)
+                sys.stderr.write("makeman: can't open %s\n" % file)
                 continue
             indoc = infp.read()
             infp.close()
@@ -277,9 +288,9 @@ def main(args, mainout=sys.stdout, mainerr=sys.stderr):
         # Second pass: do formatting
         for file in arguments:
             try: 
-                infp = open(file)
+                infp = open(os.path.join(dirprefix, file))
             except:
-                sys.stderr.write("can't open %s" % name)
+                sys.stderr.write("makeman: can't open %s\n" % file)
                 continue
             indoc = infp.read()
             infp.close()
@@ -321,6 +332,7 @@ def main(args, mainout=sys.stdout, mainerr=sys.stderr):
             (exc_type, exc_value, exc_traceback) = sys.exc_info()
             raise exc_type, exc_value, exc_traceback
         else:
+            mainerr.write("makeman: internal error!\n")
             return 5
 
 if __name__ == "__main__":
diff --git a/converter/other/bmepsoe.c b/converter/other/bmepsoe.c
index cdb52779..1df687f9 100644
--- a/converter/other/bmepsoe.c
+++ b/converter/other/bmepsoe.c
@@ -209,76 +209,85 @@ static void after_flate_add(Output_Encoder *o, int b)
   
 }
 
-static void do_flate_flush(Output_Encoder *o, int final)
-{
-  Bytef *iptr, *optr, *xptr;
-  uLong  is, os, xs;
-  int err, must_continue;
-  
-  iptr = o->fl_i_buffer; optr = o->fl_o_buffer;
-  is = o->fl_i_size; os = o->fl_o_size;
-  
-  if(iptr && optr && is && os) {
-    is = o->fl_i_used;
-    if(is) {
-      (o->flate_stream).next_in = iptr;
-      (o->flate_stream).avail_in = is;
-      if(final) { 
-    must_continue = 1;
-    while(must_continue) {
-      (o->flate_stream).next_out = optr;
-      (o->flate_stream).avail_out = os;
-      must_continue = 0;
-      err = deflate(&(o->flate_stream), Z_FINISH);
-      switch(err) {
-        case Z_STREAM_END: { 
-              xptr = optr;
+
+
+static void
+do_flate_flush(Output_Encoder * const oP,
+               int              const final) {
+
+    Bytef *iptr, *optr, *xptr;
+    unsigned long is;
+    unsigned long os;
+    unsigned long xs;
+    int err;
+    int mustContinue;
+  
+    iptr = oP->fl_i_buffer; optr = oP->fl_o_buffer;
+    is = oP->fl_i_size; os = oP->fl_o_size;
+  
+    if (iptr && optr && is && os) {
+        is = oP->fl_i_used;
+        if (is) {
+            oP->flate_stream.next_in = iptr;
+            oP->flate_stream.avail_in = is;
+            if (final) { 
+                mustContinue = 1;
+                while (mustContinue) {
+                    oP->flate_stream.next_out = optr;
+                    oP->flate_stream.avail_out = os;
+                    mustContinue = 0;
+                    err = deflate(&oP->flate_stream, Z_FINISH);
+                    switch (err) {
+                    case Z_STREAM_END: { 
+                        xptr = optr;
           
-          xs = os - ((o->flate_stream).avail_out);
-          while(xs--) {
-        after_flate_add(o, (*(xptr++)));
-          }
-        } break;
-        case Z_OK : {
-          must_continue = 1;
-              xptr = optr;
+                        xs = os - (oP->flate_stream.avail_out);
+                        while (xs--) {
+                            after_flate_add(oP, *(xptr++));
+                        }
+                    } break;
+                    case Z_OK : {
+                        mustContinue = 1;
+                        xptr = optr;
           
-          xs = os - ((o->flate_stream).avail_out);
-          while(xs--) {
-        after_flate_add(o, (*(xptr++)));
-          }
-        } break;
-        default : { 
-        } break;
-      }
-    }
-      } else { 
-    must_continue = 1;
-    while(must_continue) {
-      must_continue = 0;
-      (o->flate_stream).avail_out = os; (o->flate_stream).next_out = optr;
-      err = deflate(&(o->flate_stream), 0);
-      switch(err) {
-        case Z_OK: {
-          if((o->flate_stream).avail_in) {
-        must_continue = 1;
-          }
+                        xs = os - (oP->flate_stream.avail_out);
+                        while (xs--) {
+                            after_flate_add(oP, *(xptr++));
+                        }
+                    } break;
+                    default : { 
+                    } break;
+                    }
+                }
+            } else { 
+                mustContinue = 1;
+                while (mustContinue) {
+                    mustContinue = 0;
+                    oP->flate_stream.avail_out = os;
+                    oP->flate_stream.next_out = optr;
+                    err = deflate(&oP->flate_stream, 0);
+                    switch (err) {
+                    case Z_OK: {
+                        if (oP->flate_stream.avail_in) {
+                            mustContinue = 1;
+                        }
           
-          xptr = optr; xs = os - ((o->flate_stream).avail_out);
-          while(xs--) {
-        after_flate_add(o, (*(xptr++)));
-          }
-        } break;
-        default : { 
-        } break;
-      }
-    }
-      }
+                        xptr = optr; xs = os - (oP->flate_stream.avail_out);
+                        while (xs--) {
+                            after_flate_add(oP, *(xptr++));
+                        }
+                    } break;
+                    default : { 
+                    } break;
+                    }
+                }
+            }
+        }
     }
-  }
-  
 }
 
+
+
 static void flate_add(Output_Encoder *o, int b)
 {
   Byte bt;
@@ -457,17 +466,20 @@ static void rl_flush(Output_Encoder *o)
   
 }
 
-static void internal_byte_add(Output_Encoder *o, int b)
-{
-  
-  if((o->mode) & OE_RL) {
-    rl_add(o,b);
-  } else {
-    after_rl_add(o,b);
-  }
+
+
+static void
+internal_byte_add(Output_Encoder * const oP,
+                  int              const b) {
   
+    if (oP->mode & OE_RL)
+        rl_add(oP, b);
+    else
+        after_rl_add(oP, b);
 }
 
+
+
 static void internal_byte_flush(Output_Encoder *o)
 {
   
@@ -479,19 +491,23 @@ static void internal_byte_flush(Output_Encoder *o)
   
 }
 
-void oe_bit_add(Output_Encoder *o, int b)
-{
-  
-  o->bit_value = 2 * o->bit_value + (b ? 1 : 0);
-  o->bit_consumed = o->bit_consumed + 1;
-  if(o->bit_consumed >= 8) {
-    o->bit_consumed = 0;
-    internal_byte_add(o, (o->bit_value));
-    o->bit_value = 0;
-  }
+
+
+void
+oe_bit_add(Output_Encoder * const oP,
+           int              const b) {
   
+    oP->bit_value = 2 * oP->bit_value + (b ? 1 : 0);
+    oP->bit_consumed = oP->bit_consumed + 1;
+    if (oP->bit_consumed >= 8) {
+        oP->bit_consumed = 0;
+        internal_byte_add(oP, oP->bit_value);
+        oP->bit_value = 0;
+    }
 }
 
+
+
 void oe_bit_flush(Output_Encoder *o)
 {
   
@@ -511,26 +527,31 @@ void oe_bit_flush(Output_Encoder *o)
   
 }
 
-void oe_byte_add(Output_Encoder *o, int b)
-{
+
+
+void
+oe_byte_add(Output_Encoder * const oP,
+            int              const b) {
   
-  if(o->bit_consumed) {
-    int testval,i;
-    testval = 128;
-    for(i = 0; i < 8; i++) {
-      if(b & testval) {
-    oe_bit_add(o,1);
-      } else {
-    oe_bit_add(o,0);
-      }
-      testval = testval / 2;
+    if (oP->bit_consumed) {
+        int testval;
+        int i;
+        testval = 128;
+        for (i = 0; i < 8; ++i) {
+            if (b & testval) {
+                oe_bit_add(oP, 1);
+            } else {
+                oe_bit_add(oP, 0);
+            }
+            testval = testval / 2;
+        }
+    } else {
+        internal_byte_add(oP, b);
     }
-  } else {
-    internal_byte_add(o,b);
-  }
-  
 }
 
+
+
 void oe_byte_flush(Output_Encoder *o)
 {
   
diff --git a/converter/other/giftopnm.c b/converter/other/giftopnm.c
index d7d54775..0e261da4 100644
--- a/converter/other/giftopnm.c
+++ b/converter/other/giftopnm.c
@@ -23,10 +23,10 @@
 #include <string.h>
 #include <assert.h>
 
-#include "pnm.h"
-#include "shhopt.h"
 #include "mallocvar.h"
 #include "nstring.h"
+#include "shhopt.h"
+#include "pnm.h"
 
 #define GIFMAXVAL 255
 #define MAXCOLORMAPSIZE 256
@@ -54,6 +54,32 @@ ReadOK(FILE *          const fileP,
 }
 
 
+
+static void
+readFile(FILE *          const ifP,
+         unsigned char * const buffer,
+         size_t          const len,
+         const char **   const errorP) {
+
+    size_t bytesRead;
+
+    bytesRead = fread(buffer, len, 1, ifP);
+
+    if (bytesRead == len)
+        *errorP = NULL;
+    else {
+        if (ferror(ifP))
+            asprintfN(errorP, "Error reading file.  errno=%d (%s)",
+                      errno, strerror(errno));
+        else if (feof(ifP))
+            asprintfN(errorP, "End of file encountered");
+        else
+            asprintfN(errorP, "Short read -- %u bytes of %u", bytesRead, len);
+    }
+}
+
+
+
 #define LM_to_uint(a,b)                        (((b)<<8)|(a))
 
 static int const maxnum_lzwCode = (1<<MAX_LZW_BITS);
@@ -72,6 +98,7 @@ struct cmdlineInfo {
         */
     const char * alpha_filename;
     unsigned int quitearly;
+    unsigned int repair;
 };
 
 
@@ -101,8 +128,10 @@ parseCommandLine(int argc, char ** argv,
             &cmdlineP->verbose,         0);
     OPTENT3(0, "comments",    OPT_FLAG, NULL,
             &cmdlineP->comments,        0);
-    OPTENT3(0, "quitearly",    OPT_FLAG, NULL,
+    OPTENT3(0, "quitearly",   OPT_FLAG, NULL,
             &cmdlineP->quitearly,       0);
+    OPTENT3(0, "repair",      OPT_FLAG, NULL,
+            &cmdlineP->repair,          0);
     OPTENT3(0, "image",       OPT_STRING, &image,
             &imageSpec,                 0);
     OPTENT3(0, "alphaout",    OPT_STRING, &cmdlineP->alpha_filename, 
@@ -232,13 +261,15 @@ readColorMap(FILE *ifP, const int colormapsize,
 
 static bool zeroDataBlock = FALSE;
     /* the most recently read DataBlock was an EOD marker, i.e. had
-       zero length */
+       zero length
+    */
 
 static void
 getDataBlock(FILE *          const ifP, 
              unsigned char * const buf, 
              bool *          const eofP,
-             unsigned int *  const lengthP) {
+             unsigned int *  const lengthP,
+             const char **   const errorP) {
 /*----------------------------------------------------------------------------
    Read a DataBlock from file 'ifP', return it at 'buf'.
 
@@ -258,10 +289,11 @@ getDataBlock(FILE *          const ifP,
     unsigned char count;
     bool successfulRead;
     
-    long const pos=ftell(ifP);
+    long const pos = ftell(ifP);
     successfulRead = ReadOK(ifP, &count, 1);
     if (!successfulRead) {
         pm_message("EOF or error in reading DataBlock size from file" );
+        *errorP = FALSE;
         *eofP = TRUE;
         *lengthP = 0;
     } else {
@@ -270,17 +302,21 @@ getDataBlock(FILE *          const ifP,
         *eofP = FALSE;
         *lengthP = count;
 
-        if (count == 0) 
+        if (count == 0) {
+            *errorP = NULL;
             zeroDataBlock = TRUE;
-        else {
+        } else {
             bool successfulRead;
 
             zeroDataBlock = FALSE;
             successfulRead = ReadOK(ifP, buf, count); 
-            
-            if (!successfulRead) 
-                pm_error("EOF or error reading data portion of %d byte "
-                         "DataBlock from file", count);
+
+            if (successfulRead) 
+                *errorP = NULL;
+            else
+                asprintfN(errorP,
+                          "EOF or error reading data portion of %u byte "
+                          "DataBlock from file", count);
         }
     }
 }
@@ -303,14 +339,15 @@ readThroughEod(FILE * const ifP) {
     while (!eod) {
         bool eof;
         unsigned int count;
+        const char * error;
 
-        getDataBlock(ifP, buf, &eof, &count);
-        if (eof)
+        getDataBlock(ifP, buf, &eof, &count, &error);
+        if (error || eof)
             pm_message("EOF encountered before EOD marker.  The GIF "
                        "file is malformed, but we are proceeding "
                        "anyway as if an EOD marker were at the end "
                        "of the file.");
-        if (eof || count == 0)
+        if (error || eof || count == 0)
             eod = TRUE;
     }
 }
@@ -334,7 +371,11 @@ doCommentExtension(FILE * const ifP) {
     done = FALSE;
     while (!done) {
         bool eof;
-        getDataBlock(ifP, (unsigned char*) buf, &eof, &blocklen); 
+        const char * error;
+        getDataBlock(ifP, (unsigned char*) buf, &eof, &blocklen, &error); 
+        if (error)
+            pm_error("Error reading a data block in a comment extension.  %s",
+                     error);
         if (blocklen == 0 || eof)
             done = TRUE;
         else {
@@ -355,8 +396,11 @@ doGraphicControlExtension(FILE *         const ifP,
     bool eof;
     unsigned int length;
     static unsigned char buf[256];
+    const char * error;
 
-    getDataBlock(ifP, buf, &eof, &length);
+    getDataBlock(ifP, buf, &eof, &length, &error);
+    if (error)
+        pm_error("Error reading 1st data block of Graphic Control Extension");
     if (eof)
         pm_error("EOF/error encountered reading "
                  "1st DataBlock of Graphic Control Extension.");
@@ -451,28 +495,16 @@ struct getCodeState {
         */
     bool streamExhausted;
         /* The last time we read from the input stream, we got an EOD marker
-           or EOF
+           or EOF or an error that prevents further reading of the stream.
         */
 };
 
 
 
 static void
-initGetCode(struct getCodeState * const getCodeStateP) {
-    
-    /* Fake a previous data block */
-    getCodeStateP->buf[0] = 0;
-    getCodeStateP->buf[1] = 0;
-    getCodeStateP->bufCount = 2;
-    getCodeStateP->curbit = getCodeStateP->bufCount * 8;
-    getCodeStateP->streamExhausted = FALSE;
-}
-
-
-
-static void
-getAnotherBlock(FILE * const ifP, 
-                struct getCodeState * const gsP) {
+getAnotherBlock(FILE *                const ifP, 
+                struct getCodeState * const gsP,
+                const char **         const errorP) {
 
     unsigned int count;
     unsigned int assumed_count;
@@ -490,91 +522,104 @@ getAnotherBlock(FILE * const ifP,
     gsP->bufCount = 2;
         
     /* Add the next block to the buffer */
-    getDataBlock(ifP, &gsP->buf[gsP->bufCount], &eof, &count);
-    if (eof) {
-        pm_message("EOF encountered in image "
-                   "before EOD marker.  The GIF "
-                   "file is malformed, but we are proceeding "
-                   "anyway as if an EOD marker were at the end "
-                   "of the file.");
-        assumed_count = 0;
-    } else
-        assumed_count = count;
-
-    gsP->streamExhausted = (assumed_count == 0);
+    getDataBlock(ifP, &gsP->buf[gsP->bufCount], &eof, &count, errorP);
+    if (*errorP)
+        gsP->streamExhausted = TRUE;
+    else {
+        if (eof) {
+            pm_message("EOF encountered in image "
+                       "before EOD marker.  The GIF "
+                       "file is malformed, but we are proceeding "
+                       "anyway as if an EOD marker were at the end "
+                       "of the file.");
+            assumed_count = 0;
+        } else
+            assumed_count = count;
 
-    gsP->bufCount += assumed_count;
+        gsP->streamExhausted = (assumed_count == 0);
+        
+        gsP->bufCount += assumed_count;
+    }
 }
 
 
 
-static void
-doGetCode(FILE *                const ifP, 
-          int                   const codeSize,
-          struct getCodeState * const gsP,
-          int *                 const retvalP) {
-
-    if ((gsP->curbit+codeSize) > gsP->bufCount*8 && !gsP->streamExhausted) 
-        /* Not enough left in buffer to satisfy request.  Get the next
-           data block into the buffer.
-        */
-        getAnotherBlock(ifP, gsP);
+static struct getCodeState getCodeState;
 
-    if ((gsP->curbit+codeSize) > gsP->bufCount*8) {
-        /* If the buffer still doesn't have enough bits in it, that means
-           there were no data blocks left to read.
-        */
-        *retvalP = -1;  /* EOF */
-
-        {
-            int const bitsUnused = gsP->bufCount*8 - gsP->curbit;
-            if (bitsUnused > 0)
-                pm_message("Stream ends with a partial code "
-                           "(%d bits left in file; "
-                           "expected a %d bit code).  Ignoring.",
-                           bitsUnused, codeSize);
-        }
-    } else {
-        int i, j;
-        int code;
-        unsigned char * const buf = gsP->buf;
-
-        code = 0;  /* initial value */
-        for (i = gsP->curbit, j = 0; j < codeSize; ++i, ++j)
-            code |= ((buf[ i / 8 ] & (1 << (i % 8))) != 0) << j;
-        gsP->curbit += codeSize;
-        *retvalP = code;
-    }
+static void
+getCode_init(struct getCodeState * const getCodeStateP) {
+    
+    /* Fake a previous data block */
+    getCodeStateP->buf[0] = 0;
+    getCodeStateP->buf[1] = 0;
+    getCodeStateP->bufCount = 2;
+    getCodeStateP->curbit = getCodeStateP->bufCount * 8;
+    getCodeStateP->streamExhausted = FALSE;
 }
 
 
 
-static int
-getCode(FILE * const ifP, 
-        int    const codeSize, 
-        bool   const init)
-{
+static void
+getCode_get(struct getCodeState * const gsP,
+            FILE *                const ifP, 
+            int                   const codeSize,
+            bool *                const eofP,
+            unsigned int *        const codeP,
+            const char **         const errorP) {
 /*----------------------------------------------------------------------------
-   If 'init', initialize the code getter.
+  Read and return the next lzw code from the file *ifP.
+
+  'codeSize' is the number of bits in the code we are to get.
 
-   Otherwise, read and return the next lzw code from the file *ifP.
+  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.
 
-   'codeSize' is the number of bits in the code we are to get.
+  If there are bits left in the stream, but not 'codeSize' of them, we
+  call that a success with *eofP == TRUE.
 
-   Return -1 instead of a code if we encounter the end of the file.
+  Return the code read (assuming *eofP == FALSE and *errorP == NULL)
+  as *codeP.
 -----------------------------------------------------------------------------*/
-    static struct getCodeState getCodeState;
+    if ((gsP->curbit+codeSize) > gsP->bufCount*8 && !gsP->streamExhausted) 
+        /* Not enough left in buffer to satisfy request.  Get the next
+           data block into the buffer.
+        */
+        getAnotherBlock(ifP, gsP, errorP);
+    else
+        *errorP = NULL;
 
-    int retval;
+    if (!*errorP) {
+        if ((gsP->curbit+codeSize) > gsP->bufCount*8) {
+            /* The buffer still doesn't have enough bits in it; that means
+               there were no data blocks left to read.
+            */
+            *eofP = TRUE;
+
+            {
+                int const bitsUnused = gsP->bufCount * 8 - gsP->curbit;
+                if (bitsUnused > 0)
+                    pm_message("Stream ends with a partial code "
+                               "(%d bits left in file; "
+                               "expected a %d bit code).  Ignoring.",
+                               bitsUnused, codeSize);
+            }
+        } else {
+            int i, j;
+            unsigned int code;
+            unsigned char * const buf = gsP->buf;
+            
+            code = 0;  /* initial value */
+            for (i = gsP->curbit, j = 0; j < codeSize; ++i, ++j)
+                code |= ((buf[ i / 8 ] & (1 << (i % 8))) != 0) << j;
+            gsP->curbit += codeSize;
+            *eofP = FALSE;
+            *codeP = code;
+        }
+    }
+}
 
-    if (init) {
-        initGetCode(&getCodeState);
-        retval = 0;
-    } else 
-        doGetCode(ifP, codeSize, &getCodeState, &retval);
 
-    return retval;
-}
 
 
 struct stack {
@@ -633,6 +678,7 @@ termStack(struct stack * const stackP) {
     stackP->stack = NULL;
 }
 
+    
 
 /*----------------------------------------------------------------------------
    Some notes on LZW.
@@ -689,7 +735,7 @@ struct decompressor {
         */
     int      next_tableSlot;
         /* Index in the code translation table of the next free entry */
-    int      firstcode;
+    unsigned int firstcode;
         /* This is always a true data element code */
     int      prevcode;
         /* The code just before, in the image, the one we're processing now */
@@ -754,7 +800,7 @@ lzwInit(struct decompressor * const decompP,
     }
     resetDecompressor(decompP);
 
-    getCode(decompP->ifP, 0, TRUE);
+    getCode_init(&getCodeState);
     
     decompP->fresh = TRUE;
     
@@ -774,7 +820,7 @@ lzwTerm(struct decompressor * const decompP) {
 static void
 expandCodeOntoStack(struct decompressor * const decompP,
                     int                   const incode,
-                    bool *                const errorP) {
+                    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.
@@ -785,12 +831,13 @@ expandCodeOntoStack(struct decompressor * const decompP,
    Also add to the translation table where appropriate.
 
    Iff the translation table contains a cycle (which means the LZW stream
-   from which it was built is invalid), return *errorP == TRUE.
+   from which it was built is invalid), fail (return text explanation
+   as *errorP).
 -----------------------------------------------------------------------------*/
     int code;
-    bool error;
+    const char * error;
 
-    error = FALSE;
+    error = NULL; /* Initial value */
 
     if (incode < decompP->next_tableSlot) 
         code = incode;
@@ -812,8 +859,8 @@ expandCodeOntoStack(struct decompressor * const decompP,
 
         while (code > decompP->max_dataVal && !error) {
             if (stringCount > maxnum_lzwCode) {
-                pm_message("Error in GIF image: contains LZW string loop");
-                error = TRUE;
+                asprintfN(&error,
+                          "Error in GIF image: contains LZW string loop");
             } else {
                 ++stringCount;
                 pushStack(&decompP->stack, decompP->table[1][code]);
@@ -842,78 +889,105 @@ expandCodeOntoStack(struct decompressor * const decompP,
         }
     }
 
-    decompP->prevcode = incode;
     *errorP = error;
+
+    decompP->prevcode = incode;
 }
 
 
 
-static int
-lzwReadByte(struct decompressor * const decompP) {
+static void
+lzwReadByteFresh(struct getCodeState * const getCodeStateP,
+                 struct decompressor * const decompP,
+                 bool *                const endOfImageP,
+                 unsigned int *        const dataReadP,
+                 const char **         const errorP) {
+                     
+    /* Read off all initial clear codes, read the first non-clear code,
+       and return it.  There are no strings in the table yet, so the next
+       code must be a direct true data code.
+    */
+    bool eof;
+    do {
+        getCode_get(getCodeStateP, decompP->ifP, decompP->codeSize,
+                    &eof, &decompP->firstcode, errorP);
+        decompP->prevcode = decompP->firstcode;
+    } while (decompP->firstcode == decompP->clear_code && !*errorP && !eof);
+
+    if (!*errorP) {
+        if (eof)
+            *endOfImageP = TRUE;
+        else if (decompP->firstcode == decompP->end_code) {
+            if (!zeroDataBlock)
+                readThroughEod(decompP->ifP);
+            *endOfImageP = TRUE;
+        } else {
+            *endOfImageP = FALSE;
+            *dataReadP = decompP->firstcode;
+        }
+    }
+}
+
+
+
+static void
+lzwReadByte(struct decompressor * const decompP,
+            unsigned int *        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.
 
-  We read and return the next byte of the decompressed image, or:
+  We read and return the next byte of the decompressed image.
 
-    Return -1 if we hit EOF prematurely (i.e. before an "end" code.  We
-    forgive the case that the "end" code is followed by EOF instead of
-    an EOD marker (zero length DataBlock)).
+  If we can't, because the stream is too corrupted to make sense out of
+  it or the stream ends, we fail (return text description of why as
+  *errorP).
 
-    Return -2 if there are no more bytes in the image.  In that case,
-    make sure the file is positioned immediately after the image (i.e.
-    after the EOD marker that marks the end of the image or EOF).
+  We forgive the case that the "end" code is the end of the stream --
+  not followed by an EOD marker (zero length DataBlock).
 
-    Return -3 if we encounter errors in the LZW stream.
+  Iff we can't read a byte because we've hit the end of the image,
+  we return *endOfImageP = true.
 -----------------------------------------------------------------------------*/
-    int retval;
-
-    if (!stackIsEmpty(&decompP->stack))
-        retval = popStack(&decompP->stack);
-    else if (decompP->fresh) {
+    if (!stackIsEmpty(&decompP->stack)) {
+        *errorP = NULL;
+        *endOfImageP = FALSE;
+        *dataReadP = popStack(&decompP->stack);
+    } else if (decompP->fresh) {
         decompP->fresh = FALSE;
-        /* Read off all initial clear codes, read the first non-clear code,
-           and return it.  There are no strings in the table yet, so the next
-           code must be a direct true data code.
-        */
-        do {
-            decompP->firstcode =
-                getCode(decompP->ifP, decompP->codeSize, FALSE);
-            decompP->prevcode = decompP->firstcode;
-        } while (decompP->firstcode == decompP->clear_code);
-        if (decompP->firstcode == decompP->end_code) {
-            if (!zeroDataBlock)
-                readThroughEod(decompP->ifP);
-            retval = -2;
-        } else
-            retval = decompP->firstcode;
+
+        lzwReadByteFresh(&getCodeState, decompP, endOfImageP, dataReadP,
+                         errorP);
     } else {
-        int code;
-        code = getCode(decompP->ifP, decompP->codeSize, FALSE);
-        if (code == -1)
-            retval = -1;
-        else {
-            assert(code >= 0);  /* -1 is only possible error return */
-            if (code == decompP->clear_code) {
-                resetDecompressor(decompP);
-                retval = lzwReadByte(decompP);
-            } else {
-                if (code == decompP->end_code) {
-                    if (!zeroDataBlock)
-                        readThroughEod(decompP->ifP);
-                    retval = -2;
+        unsigned int code;
+        bool eof;
+        getCode_get(&getCodeState, decompP->ifP, decompP->codeSize,
+                    &eof, &code, errorP);
+        if (!*errorP) {
+            if (eof)
+                asprintfN(errorP,
+                          "Premature end of file; no proper GIF closing");
+            else {
+                if (code == decompP->clear_code) {
+                    resetDecompressor(decompP);
+                    lzwReadByte(decompP, dataReadP, endOfImageP, errorP);
                 } else {
-                    bool error;
-                    expandCodeOntoStack(decompP, code, &error);
-                    if (error)
-                        retval = -3;
-                    else
-                        retval = popStack(&decompP->stack);
+                    if (code == decompP->end_code) {
+                        if (!zeroDataBlock)
+                            readThroughEod(decompP->ifP);
+                        *endOfImageP = TRUE;
+                        *errorP = NULL;
+                    } else {
+                        expandCodeOntoStack(decompP, code, errorP);
+                        if (!*errorP)
+                            *dataReadP = popStack(&decompP->stack);
+                    }
                 }
             }
         }
     }
-    return retval;
 }
 
 
@@ -1022,6 +1096,124 @@ addPixelToRaster(unsigned int       const cmapIndex,
 
 
 static void
+verifyPixelRead(bool          const endOfImage,
+                const char *  const readError,
+                unsigned int  const cols,
+                unsigned int  const rows,
+                unsigned int  const failedRowNum,
+                const char ** const errorP) {
+
+    if (readError)
+        *errorP = strdup(readError);
+    else {
+        if (endOfImage)
+            asprintfN(errorP,
+                      "Error in GIF image: Not enough raster data to fill "
+                      "%u x %u dimensions.  Ran out of raster data in "
+                      "row %u.  The image has proper ending sequence, so "
+                      "this is not just a truncated file.",
+                      cols, rows, failedRowNum);
+        else
+            *errorP = NULL;
+    }
+}
+
+
+
+static void
+readRaster(struct decompressor * const decompP,
+           xel **                const xels, 
+           unsigned int          const cols,
+           unsigned int          const rows,
+           gifColorMap                 cmap, 
+           unsigned int          const cmapSize,
+           bool                  const interlace,
+           int                   const transparentIndex,
+           bit **                const alphabits,
+           bool                  const tolerateBadInput) {
+                   
+    struct pnmBuffer pnmBuffer;
+    enum pass pass;
+    bool fillingMissingPixels;
+
+    pass = MULT8PLUS0;
+    pnmBuffer.xels = xels;
+    pnmBuffer.col  = 0;
+    pnmBuffer.row  = 0;
+    fillingMissingPixels = false;  /* initial value */
+
+    while (pnmBuffer.row < rows) {
+        unsigned int colorIndex;
+
+        if (fillingMissingPixels)
+            colorIndex = 0;
+        else {
+            const char * error;
+
+            const char * readError;
+            unsigned int readColorIndex;
+            bool endOfImage;
+
+            lzwReadByte(decompP, &readColorIndex, &endOfImage, &readError);
+
+            verifyPixelRead(endOfImage, readError, cols, rows, pnmBuffer.row,
+                            &error);
+
+            if (readError)
+                strfree(readError);
+
+            if (error) {
+                if (tolerateBadInput) {
+                    pm_message("WARNING: %s.  "
+                               "Filling bottom %u rows with arbitrary color",
+                               error, rows - pnmBuffer.row);
+                    fillingMissingPixels = true;
+                } else
+                    pm_error("Unable to read input image.  %s.  Use the "
+                             "-repair option to try to salvage some of "
+                             "the image",
+                             error);
+
+                colorIndex = 0;
+            } else
+                colorIndex = readColorIndex;
+        }
+        addPixelToRaster(colorIndex, &pnmBuffer, cols, rows, cmap, cmapSize,
+                         interlace, transparentIndex, alphabits, &pass);
+    }
+}
+
+
+
+static void
+skipExtraneousData(struct decompressor * const decompP) {
+
+    unsigned int byteRead;
+    bool endOfImage;
+    const char * error;
+
+    lzwReadByte(decompP, &byteRead, &endOfImage, &error);
+
+    if (error)
+        strfree(error);
+    else if (!endOfImage) {
+        pm_message("Extraneous data at end of image.  "
+                   "Skipped to end of image");
+
+        while (!endOfImage && !error)
+            lzwReadByte(decompP, &byteRead, &endOfImage, &error);
+
+        if (error) {
+            pm_message("Error encountered skipping to end of image: %s",
+                       error);
+            strfree(error);
+        }
+    }
+}
+
+
+
+static void
 readImageData(FILE *       const ifP, 
               xel **       const xels, 
               unsigned int const cols,
@@ -1030,20 +1222,13 @@ readImageData(FILE *       const ifP,
               unsigned int const cmapSize,
               bool         const interlace,
               int          const transparentIndex,
-              bit **       const alphabits) {
+              bit **       const alphabits,
+              bool         const tolerateBadInput) {
 
     unsigned char lzwMinCodeSize;      
-    enum pass pass;
     struct decompressor decomp;
-    struct pnmBuffer pnmBuffer;
     bool gotMinCodeSize;
 
-    pass = MULT8PLUS0;
-
-    pnmBuffer.xels = xels;
-    pnmBuffer.col  = 0;
-    pnmBuffer.row  = 0;
-
     gotMinCodeSize =  ReadOK(ifP, &lzwMinCodeSize, 1);
     if (!gotMinCodeSize)
         pm_error("GIF stream ends (or read error) "
@@ -1057,29 +1242,10 @@ readImageData(FILE *       const ifP,
 
     lzwInit(&decomp, ifP, lzwMinCodeSize);
 
-    while (pnmBuffer.row < rows) {
-        int const rc = lzwReadByte(&decomp);
+    readRaster(&decomp, xels, cols, rows, cmap, cmapSize, interlace,
+               transparentIndex, alphabits, tolerateBadInput);
 
-        switch (rc) {
-        case -3:
-            pm_error("Error in GIF input stream");
-            break;
-        case -2:
-            pm_error("Error in GIF image: Not enough raster data to fill "
-                     "%u x %u dimensions.  Ran out of raster data in "
-                     "row %u", cols, rows, pnmBuffer.row);
-            break;
-        case -1:
-            pm_error("Premature end of file; no proper GIF closing");
-            break;
-        default:
-            addPixelToRaster(rc, &pnmBuffer, cols, rows, cmap, cmapSize,
-                             interlace, transparentIndex, alphabits, &pass);
-        }
-    }
-    if (lzwReadByte(&decomp) >= 0)
-        pm_message("Extraneous data at end of image.  "
-                   "Skipped to end of image");
+    skipExtraneousData(&decomp);
 
     lzwTerm(&decomp);
 }
@@ -1087,33 +1253,36 @@ readImageData(FILE *       const ifP,
 
 
 static void
-writePnm(FILE *outfile, xel ** const xels, 
-         const int cols, const int rows,
-         const int hasGray, const int hasColor) {
+writePnm(FILE * const outfileP,
+         xel ** const xels, 
+         int    const cols,
+         int    const rows,
+         int    const hasGray,
+         int    const hasColor) {
 /*----------------------------------------------------------------------------
-   Write a PNM image to the current position of file 'outfile' with
+   Write a PNM image to the current position of file *outfileP with
    dimensions 'cols' x 'rows' and raster 'xels'.
    
    Make it PBM, PGM, or PBM according to 'hasGray' and 'hasColor'.
 -----------------------------------------------------------------------------*/
     int format;
-    const char *format_name;
+    const char * formatName;
            
     if (hasColor) {
         format = PPM_FORMAT;
-        format_name = "PPM";
+        formatName = "PPM";
     } else if (hasGray) {
         format = PGM_FORMAT;
-        format_name = "PGM";
+        formatName = "PGM";
     } else {
         format = PBM_FORMAT;
-        format_name = "PBM";
+        formatName = "PBM";
     }
     if (verbose) 
-        pm_message("writing a %s file", format_name);
+        pm_message("writing a %s file", formatName);
     
-    if (outfile) 
-        pnm_writepnm(outfile, xels, cols, rows,
+    if (outfileP) 
+        pnm_writepnm(outfileP, xels, cols, rows,
                      (xelval) GIFMAXVAL, format, FALSE);
 }
 
@@ -1218,7 +1387,8 @@ readGifHeader(FILE * const gifFile, struct gifScreen * const gifScreenP) {
 static void
 readExtensions(FILE*          const ifP, 
                struct gif89 * const gif89P,
-               bool *         const eodP) {
+               bool *         const eodP,
+               const char **  const errorP) {
 /*----------------------------------------------------------------------------
    Read extension blocks from the GIF stream to which the file *ifP is
    positioned.  Read up through the image separator that begins the
@@ -1227,33 +1397,50 @@ readExtensions(FILE*          const ifP,
    If we encounter EOD (end of GIF stream) before we find an image 
    separator, we return *eodP == TRUE.  Else *eodP == FALSE.
 
-   If we hit end of file before an EOD marker, we abort the program with
-   an error message.
+   If we hit end of file before an EOD marker, we fail.
 -----------------------------------------------------------------------------*/
     bool imageStart;
     bool eod;
 
+    *errorP = NULL;  /* initial value */
+
     eod = FALSE;
     imageStart = FALSE;
 
     /* Read the image descriptor */
-    while (!imageStart && !eod) {
+    while (!imageStart && !eod && !*errorP) {
         unsigned char c;
+        const char * error;
 
-        if (! ReadOK(ifP,&c,1))
-            pm_error("EOF / read error on image data" );
+        readFile(ifP, &c, 1, &error);
 
-        if (c == ';') {         /* GIF terminator */
-            eod = TRUE;
-        } else if (c == '!') {         /* Extension */
-            if (! ReadOK(ifP,&c,1))
-                pm_error("EOF / "
-                         "read error on extension function code");
-            doExtension(ifP, c, gif89P);
-        } else if (c == ',') 
-            imageStart = TRUE;
-        else 
-            pm_message("bogus character 0x%02x, ignoring", (int) c );
+        if (error) {
+            asprintfN(errorP, "File read error where start of image "
+                      "descriptor or end of GIF expected.  %s",
+                      error);
+            strfree(error);
+        } else {
+            if (c == ';') {         /* GIF terminator */
+                eod = TRUE;
+            } else if (c == '!') {         /* Extension */
+                unsigned char functionCode;
+                const char * error;
+
+                readFile(ifP, &functionCode, 1, &error);
+
+                if (error) {
+                    asprintfN(errorP, "Failed to read function code "
+                              "of GIF extension (immediately after the '!' "
+                              "extension delimiter) from input.  %s", error);
+                    strfree(error);
+                } else {
+                    doExtension(ifP, functionCode, gif89P);
+                }
+            } else if (c == ',') 
+                imageStart = TRUE;
+            else 
+                pm_message("bogus character 0x%02x, ignoring", (int)c);
+        }
     }
     *eodP = eod;
 }
@@ -1267,9 +1454,9 @@ reportImageInfo(unsigned int const cols,
                 unsigned int const localColorMapSize,
                 bool         const interlaced) {
 
-
     pm_message("reading %u by %u%s GIF image",
                cols, rows, interlaced ? " interlaced" : "" );
+
     if (useGlobalColormap)
         pm_message("  Uses global colormap");
     else
@@ -1284,7 +1471,8 @@ convertImage(FILE *           const ifP,
              FILE *           const imageout_file, 
              FILE *           const alphafile, 
              struct gifScreen       gifScreen,
-             struct gif89     const gif89) {
+             struct gif89     const gif89,
+             bool             const tolerateBadInput) {
 /*----------------------------------------------------------------------------
    Read a single GIF image from the current position of file 'ifP'.
 
@@ -1335,7 +1523,8 @@ convertImage(FILE *           const ifP,
                      &hasGray, &hasColor);
         transparencyMessage(gif89.transparent, localColorMap);
         readImageData(ifP, xels, cols, rows, localColorMap, localColorMapSize,
-                      interlaced, gif89.transparent, alphabits);
+                      interlaced, gif89.transparent, alphabits,
+                      tolerateBadInput);
         if (!skipIt) {
             writePnm(imageout_file, xels, cols, rows,
                      hasGray, hasColor);
@@ -1344,7 +1533,8 @@ convertImage(FILE *           const ifP,
         transparencyMessage(gif89.transparent, gifScreen.ColorMap);
         readImageData(ifP, xels, cols, rows, 
                       gifScreen.ColorMap, gifScreen.ColorMapSize,
-                      interlaced, gif89.transparent, alphabits);
+                      interlaced, gif89.transparent, alphabits,
+                      tolerateBadInput);
         if (!skipIt) {
             writePnm(imageout_file, xels, cols, rows,
                      gifScreen.hasGray, gifScreen.hasColor);
@@ -1362,12 +1552,33 @@ convertImage(FILE *           const ifP,
 
 
 static void
+disposeOfReadExtensionsError(const char * const error,
+                             bool         const tolerateBadInput,
+                             unsigned int const imageSeq,
+                             bool *       const eodP) {
+    if (error) {
+        if (tolerateBadInput)
+            pm_message("Error accessing Image %u of stream; no further "
+                       "images can be accessed.  %s",
+                       imageSeq, error);
+        else
+            pm_error("Error accessing Image %u of stream.  %s",
+                     imageSeq, error);
+        strfree(error);
+        *eodP = TRUE;
+    }
+}
+
+
+
+static void
 convertImages(FILE * const ifP, 
               bool   const allImages,
               int    const requestedImageSeq, 
               bool   const drainStream,
               FILE * const imageout_file, 
-              FILE * const alphafile) {
+              FILE * const alphafile,
+              bool   const tolerateBadInput) {
 /*----------------------------------------------------------------------------
    Read a GIF stream from file 'ifP' and write one or more images from
    it as PNM images to file 'imageout_file'.  If the images have transparency
@@ -1402,20 +1613,25 @@ convertImages(FILE * const ifP,
          !eod && (imageSeq <= requestedImageSeq || allImages || drainStream);
          ++imageSeq) {
 
-        readExtensions(ifP, &gif89, &eod);
+        const char * error;
+
+        readExtensions(ifP, &gif89, &eod, &error);
+
+        disposeOfReadExtensionsError(error, tolerateBadInput, imageSeq, &eod);
 
         if (eod) {
             /* GIF stream ends before image with sequence imageSeq */
             if (!allImages && (imageSeq <= requestedImageSeq))
                 pm_error("You requested Image %d, but "
                          "only %d image%s found in GIF stream",
-                         requestedImageSeq+1,
-                         imageSeq, imageSeq>1?"s":"" );
+                         requestedImageSeq + 1,
+                         imageSeq, imageSeq > 1 ? "s" : "");
         } else {
             if (verbose)
                 pm_message("Reading Image Sequence %d", imageSeq);
             convertImage(ifP, !allImages && (imageSeq != requestedImageSeq), 
-                         imageout_file, alphafile, gifScreen, gif89);
+                         imageout_file, alphafile, gifScreen, gif89,
+                         tolerateBadInput);
         }
     }
 }
@@ -1448,13 +1664,14 @@ main(int argc, char **argv) {
         imageout_file = stdout;
 
     convertImages(ifP, cmdline.all_images, cmdline.image_no, 
-                  !cmdline.quitearly, imageout_file, alpha_file);
+                  !cmdline.quitearly, imageout_file, alpha_file,
+                  cmdline.repair);
 
     pm_close(ifP);
     if (imageout_file != NULL) 
-        pm_close( imageout_file );
+        pm_close(imageout_file);
     if (alpha_file != NULL)
-        pm_close( alpha_file );
+        pm_close(alpha_file);
 
     return 0;
 }
diff --git a/converter/other/jpegdatasource.c b/converter/other/jpegdatasource.c
index 5c1070e4..1f53c2a4 100644
--- a/converter/other/jpegdatasource.c
+++ b/converter/other/jpegdatasource.c
@@ -45,6 +45,11 @@ struct sourceManager {
     */
     struct jpeg_source_mgr jpegSourceMgr;
     FILE * ifP;
+    bool prematureEof;
+        /* We have been asked for data and were unable to comply because
+           the file had no more to give (so we supplied EOI markers
+           instead).
+        */
     JOCTET * currentBuffer;
     JOCTET * nextBuffer;
     unsigned int bytesInNextBuffer;
@@ -66,6 +71,10 @@ dsInitSource(j_decompress_ptr const cinfoP) {
 
 
 
+static const JOCTET jfifEoiMarker[] = {0xff, JPEG_EOI};
+    /* An EOI (end of image) marker */
+
+
 static boolean
 dsFillInputBuffer(j_decompress_ptr const cinfoP) {
 /*----------------------------------------------------------------------------
@@ -74,9 +83,19 @@ dsFillInputBuffer(j_decompress_ptr const cinfoP) {
 -----------------------------------------------------------------------------*/
     struct sourceManager * const srcP = (struct sourceManager *) cinfoP->src;
 
-    if (srcP->bytesInNextBuffer == 0) 
-        pm_error("End-of-file encountered in the middle of JPEG image.");
-    else {
+    if (srcP->bytesInNextBuffer == 0) {
+        /* The decompressor expects more bytes, but there aren't any, so
+           the file is corrupted -- probably truncated.  We want the
+           decompressor to decompress whatever it's read so far, so we
+           synthesize an EOI marker here, but we also set error state
+           in the source manager.  The decompressor will recognize the
+           truncation and pad out the image with gray.
+        */
+        srcP->prematureEof = TRUE;
+        
+        srcP->jpegSourceMgr.next_input_byte = jfifEoiMarker;
+        srcP->jpegSourceMgr.bytes_in_buffer = sizeof(jfifEoiMarker);
+    } else {
         /* Rotate the buffers */
         srcP->jpegSourceMgr.next_input_byte = srcP->nextBuffer;
         srcP->jpegSourceMgr.bytes_in_buffer = srcP->bytesInNextBuffer;
@@ -87,7 +106,7 @@ dsFillInputBuffer(j_decompress_ptr const cinfoP) {
             srcP->currentBuffer = tmp;
         }
 
-        /* Fill the new 'next' buffer */
+        /* Fill the new "next" buffer */
         srcP->bytesInNextBuffer = 
             fread(srcP->nextBuffer, 1, BUFFER_SIZE, srcP->ifP);
     }
@@ -139,6 +158,14 @@ dsDataLeft(struct sourceManager * const srcP) {
 
 
 
+bool
+dsPrematureEof(struct sourceManager * const srcP) {
+
+    return srcP->prematureEof;
+}
+
+
+
 struct sourceManager * 
 dsCreateSource(const char * const fileName) {
 
@@ -156,6 +183,7 @@ dsCreateSource(const char * const fileName) {
     srcP->jpegSourceMgr.resync_to_restart = jpeg_resync_to_restart;
     srcP->jpegSourceMgr.term_source = dsTermSource;
     
+    srcP->prematureEof = FALSE;
     srcP->currentBuffer = srcP->buffer1;
     srcP->nextBuffer = srcP->buffer2;
     srcP->jpegSourceMgr.bytes_in_buffer = 
diff --git a/converter/other/jpegdatasource.h b/converter/other/jpegdatasource.h
index 07f17389..58648fe4 100644
--- a/converter/other/jpegdatasource.h
+++ b/converter/other/jpegdatasource.h
@@ -12,6 +12,9 @@ dsDestroySource(struct sourceManager * const srcP);
 bool
 dsDataLeft(struct sourceManager * const srcP);
 
+bool
+dsPrematureEof(struct sourceManager * const srcP);
+
 struct jpeg_source_mgr *
 dsJpegSourceMgr(struct sourceManager * const srcP);
 
diff --git a/converter/other/jpegtopnm.c b/converter/other/jpegtopnm.c
index 60ae7e42..180ef3d4 100644
--- a/converter/other/jpegtopnm.c
+++ b/converter/other/jpegtopnm.c
@@ -109,6 +109,7 @@ struct cmdlineInfo {
     unsigned int comments;
     unsigned int dumpexif;
     unsigned int multiple;
+    unsigned int repair;
 };
 
 
@@ -116,9 +117,9 @@ static bool displayComments;
     /* User wants comments from the JPEG to be displayed */
 
 static void 
-interpret_maxmemory (bool         const maxmemorySpec,
-                     const char * const maxmemory, 
-                     long int *   const max_memory_to_use_p) { 
+interpret_maxmemory(bool         const maxmemorySpec,
+                    const char * const maxmemory, 
+                    long int *   const max_memory_to_use_p) { 
 /*----------------------------------------------------------------------------
    Interpret the "maxmemory" command line option.
 -----------------------------------------------------------------------------*/
@@ -157,8 +158,9 @@ interpret_adobe(const int adobe, const int notadobe,
 
 
 static void
-parse_command_line(const int argc, char ** argv,
-                   struct cmdlineInfo *cmdlineP) {
+parseCommandLine(int                  const argc,
+                 char **              const argv,
+                 struct cmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that many of the strings that this function returns in the
    *cmdlineP structure are actually in the supplied argv array.  And
@@ -173,10 +175,10 @@ parse_command_line(const int argc, char ** argv,
          */
     optStruct3 opt;
 
-    int i;  /* local loop variable */
+    unsigned int i;  /* local loop variable */
 
-    char *maxmemory;
-    char *dctval;
+    const char * maxmemory;
+    const char * dctval;
     unsigned int adobe, notadobe;
 
     unsigned int tracelevelSpec, exifSpec, dctvalSpec, maxmemorySpec;
@@ -206,6 +208,7 @@ parse_command_line(const int argc, char ** argv,
             &exifSpec, 0);
     OPTENT3(0, "dumpexif",    OPT_FLAG,   NULL, &cmdlineP->dumpexif,      0);
     OPTENT3(0, "multiple",    OPT_FLAG,   NULL, &cmdlineP->multiple,      0);
+    OPTENT3(0, "repair",      OPT_FLAG,   NULL, &cmdlineP->repair,        0);
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
@@ -213,7 +216,8 @@ parse_command_line(const int argc, char ** argv,
 
     /* Make private copy of arguments for optParseOptions to corrupt */
     argc_parse = argc;
-    for (i=0; i < argc; i++) argv_parse[i] = argv[i];
+    for (i=0; i < argc; ++i)
+        argv_parse[i] = argv[i];
 
     optParseOptions3( &argc_parse, argv_parse, opt, sizeof(opt), 0);
         /* Uses and sets argc_parse, argv_parse, 
@@ -458,32 +462,34 @@ read_rgb(JSAMPLE *ptr, const enum colorspace color_space,
    copy_pixel_row().  But it would be impractical to allocate and free
    the storage with every call to copy_pixel_row().
 */
-static xel *pnmbuffer;      /* Output buffer.  Input to pnm_writepnmrow() */
+static xel * pnmbuffer;      /* Output buffer.  Input to pnm_writepnmrow() */
 
 static void
-copy_pixel_row(const JSAMPROW jpegbuffer, const int width, 
-               const unsigned int samples_per_pixel, 
-               const enum colorspace color_space,
-               const unsigned int maxval,
-               FILE * const output_file, const int output_type) {
-  JSAMPLE *ptr;
-  unsigned int output_cursor;     /* Cursor into output buffer 'pnmbuffer' */
-
-  ptr = jpegbuffer;  /* Start at beginning of input row */
-
-  for (output_cursor = 0; output_cursor < width; output_cursor++) {
-      xel current_pixel;
-      if (samples_per_pixel >= 3) {
-          const rgb_type * const rgb_p = read_rgb(ptr, color_space, maxval);
-          PPM_ASSIGN(current_pixel, rgb_p->r, rgb_p->g, rgb_p->b);
-      } else {
-          PNM_ASSIGN1(current_pixel, GETJSAMPLE(*ptr));
-      }
-      ptr += samples_per_pixel;  /* move to next pixel of input */
-      pnmbuffer[output_cursor] = current_pixel;
-  }
-  pnm_writepnmrow(output_file, pnmbuffer, width,
-                  maxval, output_type, FALSE);
+copyPixelRow(JSAMPROW        const jpegbuffer,
+             unsigned int    const width, 
+             unsigned int    const samplesPerPixel, 
+             enum colorspace const colorSpace,
+             FILE *          const ofP,
+             int             const format,
+             xelval          const maxval) {
+
+    JSAMPLE * ptr;
+    unsigned int outputCursor;     /* Cursor into output buffer 'pnmbuffer' */
+
+    ptr = &jpegbuffer[0];  /* Start at beginning of input row */
+    
+    for (outputCursor = 0; outputCursor < width; ++outputCursor) {
+        xel currentPixel;
+        if (samplesPerPixel >= 3) {
+            const rgb_type * const rgb_p = read_rgb(ptr, colorSpace, maxval);
+            PPM_ASSIGN(currentPixel, rgb_p->r, rgb_p->g, rgb_p->b);
+        } else {
+            PNM_ASSIGN1(currentPixel, GETJSAMPLE(*ptr));
+        }
+        ptr += samplesPerPixel;  /* move to next pixel of input */
+        pnmbuffer[outputCursor] = currentPixel;
+    }
+    pnm_writepnmrow(ofP, pnmbuffer, width, maxval, format, FALSE);
 }
 
 
@@ -796,17 +802,42 @@ computeColorSpace(struct jpeg_decompress_struct * const cinfoP,
 
 
 static void
+convertRaster(struct jpeg_decompress_struct * const cinfoP,
+              enum colorspace                 const color_space,
+              FILE *                          const ofP,
+              xelval                          const format,
+              unsigned int                    const maxval) {
+              
+    JSAMPROW jpegbuffer;  /* Input buffer.  Filled by jpeg_scanlines() */
+
+    jpegbuffer = ((*cinfoP->mem->alloc_sarray)
+                  ((j_common_ptr) cinfoP, JPOOL_IMAGE,
+                   cinfoP->output_width * cinfoP->output_components, 
+                   (JDIMENSION) 1)
+        )[0];
+
+    while (cinfoP->output_scanline < cinfoP->output_height) {
+        jpeg_read_scanlines(cinfoP, &jpegbuffer, 1);
+        if (ofP)
+            copyPixelRow(jpegbuffer, cinfoP->output_width, 
+                         cinfoP->out_color_components,
+                         color_space, ofP, format, maxval);
+    }
+}
+
+
+
+static void
 convertImage(FILE *                          const ofP, 
              struct cmdlineInfo              const cmdline,
              struct jpeg_decompress_struct * const cinfoP) {
 
-    int output_type;
+    int format;
         /* The type of output file, PGM or PPM.  Value is either PPM_TYPE
            or PGM_TYPE, which conveniently also pass as format values
            PPM_FORMAT and PGM_FORMAT.
         */
-    JSAMPROW jpegbuffer;  /* Input buffer.  Filled by jpeg_scanlines() */
-    unsigned int maxval;  
+    xelval maxval;  
         /* The maximum value of a sample (color component), both in the input
            and the output.
         */
@@ -817,43 +848,30 @@ convertImage(FILE *                          const ofP,
                    cmdline.dct_method, 
                    cmdline.max_memory_to_use, cmdline.nosmooth);
                    
-    set_color_spaces(cinfoP->jpeg_color_space, &output_type, 
+    set_color_spaces(cinfoP->jpeg_color_space, &format,
                      &cinfoP->out_color_space);
 
-    maxval = (1 << cinfoP->data_precision) - 1;
+    maxval = pm_bitstomaxval(cinfoP->data_precision);
 
     if (cmdline.verbose) 
-        tellDetails(*cinfoP, maxval, output_type);
+        tellDetails(*cinfoP, maxval, format);
 
     /* Calculate output image dimensions so we can allocate space */
     jpeg_calc_output_dimensions(cinfoP);
 
-    jpegbuffer = ((*cinfoP->mem->alloc_sarray)
-                  ((j_common_ptr) cinfoP, JPOOL_IMAGE,
-                   cinfoP->output_width * cinfoP->output_components, 
-                   (JDIMENSION) 1)
-        )[0];
-
     /* Start decompressor */
     jpeg_start_decompress(cinfoP);
 
     if (ofP)
         /* Write pnm output header */
         pnm_writepnminit(ofP, cinfoP->output_width, cinfoP->output_height,
-                         maxval, output_type, FALSE);
+                         maxval, format, FALSE);
 
     pnmbuffer = pnm_allocrow(cinfoP->output_width);
     
     color_space = computeColorSpace(cinfoP, cmdline.inklevel);
-
-    /* Process data */
-    while (cinfoP->output_scanline < cinfoP->output_height) {
-        jpeg_read_scanlines(cinfoP, &jpegbuffer, 1);
-        if (ofP)
-            copy_pixel_row(jpegbuffer, cinfoP->output_width, 
-                           cinfoP->out_color_components,
-                           color_space, maxval, ofP, output_type);
-    }
+    
+    convertRaster(cinfoP, color_space, ofP, format, maxval);
 
     if (cmdline.comments)
         print_comments(*cinfoP);
@@ -905,17 +923,25 @@ convertImages(FILE *                          const ofP,
             convertImage(ofP, cmdline, cinfoP);
         }
     } else {
-        if (dsDataLeft(sourceManagerP))
+        if (dsDataLeft(sourceManagerP)) {
             convertImage(ofP, cmdline, cinfoP);
-        else
+        } else
             pm_error("Input stream is empty");
     }
+    if (dsPrematureEof(sourceManagerP)) {
+        if (cmdline.repair)
+            pm_message("Premature EOF on input; repaired by padding end "
+                       "of image.");
+        else
+            pm_error("Premature EOF on input.  Use -repair to salvage.");
+    }
 }
 
 
 
 int
 main(int argc, char **argv) {
+
     FILE * ofP;
     struct cmdlineInfo cmdline;
     struct jpeg_decompress_struct cinfo;
@@ -924,7 +950,7 @@ main(int argc, char **argv) {
 
     pnm_init(&argc, argv);
 
-    parse_command_line(argc, argv, &cmdline);
+    parseCommandLine(argc, argv, &cmdline);
 
     if (cmdline.exif_filespec && STREQ(cmdline.exif_filespec, "-"))
         /* He's got exif going to stdout, so there can be no image output */
@@ -967,4 +993,3 @@ main(int argc, char **argv) {
   
     exit(jerr.num_warnings > 0 ? EXIT_WARNING : EXIT_SUCCESS);
 }
-
diff --git a/converter/other/pamtogif.c b/converter/other/pamtogif.c
index 81e8edb4..68a445c6 100644
--- a/converter/other/pamtogif.c
+++ b/converter/other/pamtogif.c
@@ -21,6 +21,7 @@ static unsigned int const gifMaxval = 255;
 
 static bool verbose;
 
+
 typedef int stringCode;
     /* A code to be place in the GIF raster.  It represents
        a string of one or more pixels.  You interpret this in the context
@@ -37,9 +38,6 @@ typedef int stringCode;
 
        Ergo, this data structure must be signed and at least BITS bits
        wide plus sign bit.
-
-       TODO: Some variables that should be of this type are defined as
-             "long".
     */
 
 
@@ -52,15 +50,6 @@ struct cmap {
         /* Maps a color index, as is found in the raster part of the
            GIF, to color.
         */
-    int perm[MAXCMAPSIZE], permi[MAXCMAPSIZE];
-        /* perm[i] is the position in the sorted colormap of the color which
-           is at position i in the unsorted colormap.  permi[] is the inverse
-           function of perm[].
-           
-           The sort colormap is the one that goes into the GIF.  The
-           unsorted one is the one that is hashed so we can find colors
-           in it.
-        */
     unsigned int cmapSize;
         /* Number of entries in the GIF colormap.  I.e. number of colors
            in the image, plus possibly one fake transparency color.
@@ -69,12 +58,12 @@ struct cmap {
         /* The colormap contains an entry for transparent pixels */
     unsigned int transparent;
         /* color index number in GIF palette of the color that is to
-           be transparent.  This is a post-sort index.
+           be transparent.
 
            Meaningful only if 'haveTransparent' is true.
         */
     tuplehash tuplehash;
-        /* A hash table to translate color to GIF (presort) colormap index. */
+        /* A hash table to translate color to GIF colormap index. */
 };
 
 struct cmdlineInfo {
@@ -89,22 +78,11 @@ struct cmdlineInfo {
     const char *transparent;    /* -transparent option value.  NULL if none. */
     const char *comment;        /* -comment option value; NULL if none */
     unsigned int nolzw;         /* -nolzw option */
+    float aspect;               /* -aspect option value (the ratio).  */
     unsigned int verbose;
 };
 
 
-static unsigned int
-nSignificantBits(unsigned int const arg) {
-
-    unsigned int i;
-
-    i = 0;
-   
-    while (arg >> i != 0)
-        ++i;
-
-    return i;
-}
 
 
 
@@ -147,6 +125,8 @@ parseCommandLine(int argc, char ** argv,
     optStruct3 opt;  /* set by OPTENT3 */
     unsigned int option_def_index;
 
+    unsigned int aspectSpec;
+
     MALLOCARRAY_NOFAIL(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENT3 */
@@ -164,8 +144,10 @@ parseCommandLine(int argc, char ** argv,
             &cmdlineP->comment,        NULL, 0);
     OPTENT3(0,   "alphacolor",  OPT_STRING, 
             &cmdlineP->alphacolor,     NULL, 0);
+    OPTENT3(0,   "aspect",      OPT_FLOAT, 
+            &cmdlineP->aspect,         &aspectSpec, 0);
     OPTENT3(0,   "verbose",     OPT_FLAG, 
-            NULL,                       &cmdlineP->verbose, 0);
+            NULL,                      &cmdlineP->verbose, 0);
     
     /* Set the defaults */
     cmdlineP->mapfile = NULL;
@@ -190,6 +172,19 @@ parseCommandLine(int argc, char ** argv,
                  "specified %d", argc-1);
     else
         cmdlineP->input_filespec = argv[1];
+        
+    if (aspectSpec) { 
+        if (cmdlineP->aspect < 0.25  || cmdlineP->aspect > 4.21875)
+            pm_error("Invalid -aspect value: %f.  "
+                     "GIF allows only the range 0.25-4.0 .",
+                     cmdlineP->aspect);
+        else if (cmdlineP->aspect > 4.0)
+            pm_message("Warning: "
+                       "You specified an aspect ratio over 4.0: %f.  "
+                       "This will result in an invalid GIF.",
+                       cmdlineP->aspect);
+    } else
+        cmdlineP->aspect = 1.0;
 }
 
 
@@ -211,7 +206,7 @@ closestColor(tuple         const color,
              struct pam *  const pamP,
              struct cmap * const cmapP) {
 /*----------------------------------------------------------------------------
-   Return the pre-sort colormap index of the color in the colormap *cmapP
+   Return the colormap index of the color in the colormap *cmapP
    that is closest to the color 'color', whose format is specified by 
    *pamP.
 
@@ -450,24 +445,22 @@ gifPixel(struct pam *   const pamP,
    'alphaThreshold' is the alpha level below which we consider a
    pixel transparent for GIF purposes.
 -----------------------------------------------------------------------------*/
-    unsigned int colorIndex;
+    int colorIndex;
 
     if (alphaPlane && tuple[alphaPlane] < alphaThreshold &&
         cmapP->haveTransparent)
         colorIndex = cmapP->transparent;
     else {
-        int presortColorindex;
         int found;
 
         pnm_lookuptuple(pamP, cmapP->tuplehash, tuple,
-                        &found, &presortColorindex);
+                        &found, &colorIndex);
         
         if (!found)
-            presortColorindex = closestColor(tuple, pamP, cmapP);
-
-        colorIndex = cmapP->perm[presortColorindex];
+            colorIndex = closestColor(tuple, pamP, cmapP);
     }
-    return colorIndex;
+    assert(colorIndex >= 0);
+    return (unsigned int) colorIndex;
 }
 
 
@@ -536,8 +529,6 @@ writeCommentExtension(FILE * const ofP,
 
 #define BITS    12
 
-#define HSIZE  5003            /* 80% occupancy */
-
 /*
  *
  * GIF Image compression - modified 'compress'
@@ -560,7 +551,7 @@ static stringCode const maxCodeLimitLzw = (stringCode)1 << BITS;
 
 
 struct hashTableEntry {
-    long fcode;   /* -1 means unused slot */
+    stringCode fcode;   /* -1 means unused slot */
     unsigned int ent;
 };    
 
@@ -729,13 +720,6 @@ codeBuffer_increaseCodeSize(codeBuffer * const codeBufferP) {
     codeBufferP->maxCode = (1 << codeBufferP->nBits) - 1;
 }
 
-
-
-static unsigned long const masks[] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F,
-                                       0x001F, 0x003F, 0x007F, 0x00FF,
-                                       0x01FF, 0x03FF, 0x07FF, 0x0FFF,
-                                       0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF };
-
 static void
 codeBuffer_output(codeBuffer * const codeBufferP,
                   stringCode   const code) {
@@ -751,10 +735,10 @@ codeBuffer_output(codeBuffer * const codeBufferP,
 -----------------------------------------------------------------------------*/
     assert (code <= codeBufferP->maxCode);
 
-    codeBufferP->curAccum &= masks[codeBufferP->curBits];
+    codeBufferP->curAccum &= (1 << codeBufferP->curBits) - 1;
 
     if (codeBufferP->curBits > 0)
-        codeBufferP->curAccum |= ((long)code << codeBufferP->curBits);
+        codeBufferP->curAccum |= ((unsigned long)code << codeBufferP->curBits);
     else
         codeBufferP->curAccum = code;
 
@@ -806,6 +790,12 @@ typedef struct {
            proper data, but always using one code per pixel, and therefore
            not effecting any compression and not using the LZW patent.
         */
+    unsigned int hsize;
+        /* The number of slots in the hash table.  This variable to
+           enhance overall performance by reducing memory use when
+           encoding smaller gifs. 
+         */
+        
     unsigned int hshift;
         /* This is how many bits we shift left a string code in forming the
            primary hash of the concatenation of that string with another.
@@ -869,28 +859,82 @@ typedef struct {
 
 
 
+
+static unsigned int
+nSignificantBits( unsigned int const arg ){
+
+#if defined(__GNUC__)  && (__GNUC__ * 100 + __GNUC_MINOR__ >= 304)
+
+    return (arg == 0) ? 0 : 8 * sizeof(unsigned int) - __builtin_clz(arg);
+
+#else
+
+    unsigned int i = 0;
+    while (arg >> i != 0)
+        ++i;
+
+    return i;
+#endif
+}
+
+
+
 static lzwCompressor *
 lzw_create(FILE *       const ofP,
            unsigned int const initBits,
-           bool         const lzw) {
+           bool         const lzw,
+           unsigned int const pixelCount) {
 
-    lzwCompressor * lzwP;
+    unsigned int const hsizeTable[] = {257, 521, 1031, 2053, 4099, 5003};
+    /* If the image has 4096 or fewer pixels we use prime numbers slightly
+       above powers of two between 8 and 12.  In this case the hash table
+       never fills up; clear code is never emitted.
+    
+       Above that we use a table with 4096 slots plus 20% extra.
+       When this is not enough the clear code is emitted.
+       Due to the extra 20% the table itself never fills up.
+       
+       lzw.hsize and lzw.hshift stay constant through the image.
+
+       Variable hsize is a performance enhancement based on the fact that
+       the encoder never needs more codes than the number of pixels in
+       the image.  Typically, the ratio of pixels to codes is around
+       10:1 to 20:1.
+   
+       Logic works with fixed values lzw.hsize=5003 and t=13.
+    */
 
+    lzwCompressor * lzwP;
+       
     MALLOCVAR_NOFAIL(lzwP);
 
-    MALLOCARRAY(lzwP->hashTable, HSIZE);
-
-    if (lzwP->hashTable == NULL)
-        pm_error("Couldn't get memory for %u-entry hash table.", HSIZE);
-
     /* Constants */
     lzwP->lzw = lzw;
-    lzwP->hshift =
-        nSignificantBits(HSIZE-1) - 1 - nSignificantBits(MAXCMAPSIZE-1);
-    lzwP->clearCode = 1 << (initBits - 1);
-    lzwP->eofCode = lzwP->clearCode + 1;
+
+    lzwP->clearCode     = 1 << (initBits - 1);
+    lzwP->eofCode       = lzwP->clearCode + 1;
     lzwP->initCodeLimit = 1 << initBits;
 
+    if (lzw) {
+        unsigned int const t =
+            MIN(13, MAX(8, nSignificantBits(pixelCount +lzwP->eofCode - 2)));
+            /* Index into hsizeTable */
+    
+        lzwP->hsize = hsizeTable[t-8];
+
+        lzwP->hshift = (t == 13 ? 12 : t) - nSignificantBits(MAXCMAPSIZE-1);
+
+        MALLOCARRAY(lzwP->hashTable, lzwP->hsize);
+        
+        if (lzwP->hashTable == NULL)
+            pm_error("Couldn't get memory for %u-entry hash table.",
+                     lzwP->hsize);
+    } else {
+        /* No LZW compression.  We don't need a stringcode hash table */  
+        lzwP->hashTable = NULL;
+        lzwP->hsize     = 0;
+    }
+
     lzwP->buildingString = FALSE;
 
     lzwP->codeBufferP = codeBuffer_create(ofP, initBits, lzw);
@@ -919,12 +963,10 @@ lzwHashClear(lzwCompressor * const lzwP) {
 
     unsigned int i;
 
-    for (i = 0; i < HSIZE; ++i)
+    for (i = 0; i < lzwP->hsize; ++i)
         lzwP->hashTable[i].fcode = -1;
 
     lzwP->nextUnusedCode = lzwP->clearCode + 2;
-
-    
 }
 
 
@@ -1049,9 +1091,7 @@ primaryHash(stringCode   const baseString,
     assert(additionalPixel < MAXCMAPSIZE);
 
     hash = (additionalPixel << hshift) ^ baseString;
-
-    assert(hash < HSIZE);
-
+    
     return hash;
 }
 
@@ -1060,7 +1100,7 @@ primaryHash(stringCode   const baseString,
 static void
 lookupInHash(lzwCompressor *  const lzwP,
              unsigned int     const gifPixel,
-             long             const fcode,
+             stringCode       const fcode,
              bool *           const foundP,
              unsigned short * const codeP,
              unsigned int *   const hashP) {
@@ -1071,13 +1111,13 @@ lookupInHash(lzwCompressor *  const lzwP,
         /* Index into hash table */
 
     i = primaryHash(lzwP->stringSoFar, gifPixel, lzwP->hshift);
-    disp = (i == 0) ? 1 : HSIZE - i;
+    disp = (i == 0) ? 1 : lzwP->hsize - i;
 
     while (lzwP->hashTable[i].fcode != fcode &&
            lzwP->hashTable[i].fcode >= 0) {
         i -= disp;
         if (i < 0)
-            i += HSIZE;
+            i += lzwP->hsize;
     }
 
     if (lzwP->hashTable[i].fcode == fcode) {
@@ -1109,9 +1149,10 @@ lzw_encodePixel(lzwCompressor * const lzwP,
         lzwP->stringSoFar = gifPixel;
         lzwP->buildingString = TRUE;
     } else {
-        long const fcode = ((long) gifPixel << BITS) + lzwP->stringSoFar;
+        stringCode const fcode =
+            ((stringCode) gifPixel << BITS) + lzwP->stringSoFar;
             /* The encoding of the string we've already recognized plus the
-               instant pixel, to be looked up in the hash of knows strings.
+               instant pixel, to be looked up in the hash of known strings.
             */
     
         lookupInHash(lzwP, gifPixel, fcode, &found, &code, &hash);
@@ -1196,7 +1237,7 @@ writeRaster(struct pam *  const pamP,
            number of the current row.
         */
     
-    lzwP = lzw_create(ofP, initBits, lzw);
+    lzwP = lzw_create(ofP, initBits, lzw, pamP->height * pamP->width);
 
     tuplerow = pnm_allocpamrow(pamP);
 
@@ -1236,12 +1277,6 @@ writeRaster(struct pam *  const pamP,
 
 
 
-
-
-
-
-
-
 static void
 writeGlobalColorMap(FILE *              const ofP,
                     const struct cmap * const cmapP,
@@ -1272,9 +1307,9 @@ writeGlobalColorMap(FILE *              const ofP,
 
     for (i = 0; i < colorMapSize; ++i) {
         if (i < cmapP->cmapSize) {
-            tuple const color = cmapP->color[cmapP->permi[i]];
+            tuple const color = cmapP->color[i];
 
-            assert(cmapP->permi[i] < cmapP->cmapSize);
+            assert(i < cmapP->cmapSize);
 
             pnm_scaletuple(&pam, tupleRgb255, color, 255);
             pnm_maketuplergb(&pam, tupleRgb255);
@@ -1300,14 +1335,15 @@ writeGifHeader(FILE *              const ofP,
                unsigned int        const background, 
                unsigned int        const bitsPerPixel,
                const struct cmap * const cmapP,
-               char                const comment[]) {
+               char                const comment[],
+               float               const aspect) {
 
     unsigned int const resolution = bitsPerPixel;
 
     unsigned char b;
 
     /* Write the Magic header */
-    if (cmapP->haveTransparent || comment)
+    if (cmapP->haveTransparent || comment || aspect != 1.0 )
         fwrite("GIF89a", 1, 6, ofP);
     else
         fwrite("GIF87a", 1, 6, ofP);
@@ -1332,9 +1368,11 @@ writeGifHeader(FILE *              const ofP,
     assert((unsigned char)background == background);
     fputc(background, ofP);
 
-    /* Byte of 0's (future expansion) */
-    fputc(0x00, ofP);
-
+    {
+        int const aspectValue = aspect == 1.0 ? 0 : ROUND(aspect * 64) - 15;
+        assert(0 <= aspectValue && aspectValue <= 255); 
+        fputc(aspectValue, ofP);
+    }
     writeGlobalColorMap(ofP, cmapP, bitsPerPixel);
 
     if (cmapP->haveTransparent) 
@@ -1398,6 +1436,7 @@ gifEncode(struct pam *  const pamP,
           unsigned int  const bitsPerPixel,
           struct cmap * const cmapP,
           char          const comment[],
+          float         const aspect,
           bool          const lzw) {
 
     unsigned int const leftOffset = 0;
@@ -1420,13 +1459,13 @@ gifEncode(struct pam *  const pamP,
     if (pamP->width > 65535)
         pm_error("Image width %u too large for GIF format.  (Max 65535)",
                  pamP->width);
-    
+     
     if (pamP->height > 65535)
         pm_error("Image height %u too large for GIF format.  (Max 65535)",
                  pamP->height);
 
     writeGifHeader(ofP, pamP->width, pamP->height, background,
-                   bitsPerPixel, cmapP, comment);
+                   bitsPerPixel, cmapP, comment, aspect);
 
     /* Write an Image separator */
     fputc(',', ofP);
@@ -1457,7 +1496,7 @@ reportTransparent(struct cmap * const cmapP) {
 
     if (verbose) {
         if (cmapP->haveTransparent) {
-            tuple const color = cmapP->color[cmapP->permi[cmapP->transparent]];
+            tuple const color = cmapP->color[cmapP->transparent];
             pm_message("Color %u (%lu, %lu, %lu) is transparent",
                        cmapP->transparent,
                        color[PAM_RED_PLANE],
@@ -1505,7 +1544,7 @@ computeTransparent(char          const colorarg[],
         bool exact;
         tuple transcolor;
         bool found;
-        int presortColorindex;
+        int colorindex;
         
         if (colorarg[0] == '=') {
             colorspec = &colorarg[1];
@@ -1517,15 +1556,14 @@ computeTransparent(char          const colorarg[],
 
         transcolor = pnm_parsecolor(colorspec, cmapP->pam.maxval);
         pnm_lookuptuple(&cmapP->pam, cmapP->tuplehash, transcolor, &found,
-                        &presortColorindex);
+                        &colorindex);
         
         if (found) {
             cmapP->haveTransparent = TRUE;
-            cmapP->transparent = cmapP->perm[presortColorindex];
+            cmapP->transparent = colorindex;
         } else if (!exact) {
             cmapP->haveTransparent = TRUE;
-            cmapP->transparent =
-                cmapP->perm[closestColor(transcolor, &cmapP->pam, cmapP)];
+            cmapP->transparent = closestColor(transcolor, &cmapP->pam, cmapP);
         } else {
             cmapP->haveTransparent = FALSE;
             pm_message("Warning: specified transparent color "
@@ -1543,61 +1581,66 @@ computeTransparent(char          const colorarg[],
 
 
 static unsigned int
-sortOrder(tuple        const tuple,
-          struct pam * const pamP) {
-    
-    if (pamP->depth < 3)
-        return tuple[0];
-    else
-        return ((tuple[0] * MAXCMAPSIZE) + tuple[1]) * MAXCMAPSIZE + tuple[2];
+sortOrderColor(tuple const tuple) {
+
+    return ((tuple[PAM_RED_PLANE] * MAXCMAPSIZE) +
+            tuple[PAM_GRN_PLANE]) * MAXCMAPSIZE +
+           tuple[PAM_BLU_PLANE];
+}
+
+
+
+#ifndef LITERAL_FN_DEF_MATCH
+static qsort_comparison_fn sortCompareColor;
+#endif
+
+static int
+sortCompareColor(const void * const entry1P,
+                 const void * const entry2P) {
+
+    struct tupleint * const * const tupleint1PP = entry1P;
+    struct tupleint * const * const tupleint2PP = entry2P;
+
+    return (sortOrderColor((*tupleint1PP)->tuple) 
+            - sortOrderColor((*tupleint2PP)->tuple));
 }
 
 
 
+#ifndef LITERAL_FN_DEF_MATCH
+static qsort_comparison_fn sortCompareGray;
+#endif
+
+static int
+sortCompareGray(const void * const entry1P,
+                const void * const entry2P){
+
+    struct tupleint * const * const tupleint1PP = entry1P;
+    struct tupleint * const * const tupleint2PP = entry2P;
+
+    return ((*tupleint1PP)->tuple[0] - (*tupleint2PP)->tuple[0]);
+}
+
+
 
 static void
-sortColormap(bool          const sort,
-             struct cmap * const cmapP) {
+sortTupletable(struct pam * const mapPamP,
+               unsigned int const colors,
+               tupletable   const tuplefreq) {
 /*----------------------------------------------------------------------------
-   Sort (in place) the colormap *cmapP.
+   Sort the colormap *cmapP.
 
-   Create the perm[] and permi[] mappings for the colormap.
-
-   'sort' is logical:  true means to sort the colormap by red intensity,
-   then by green intensity, then by blue intensity.  False means a null
-   sort -- leave it in the same order in which we found it.
+   Sort the colormap by red intensity, then by green intensity,
+   then by blue intensity.
 -----------------------------------------------------------------------------*/
-    tuple * const color = cmapP->color;
-    int * const perm = cmapP->perm;
-    int * const permi = cmapP->permi;
-    struct pam * const pamP = &cmapP->pam;
-    unsigned int const cmapSize = cmapP->cmapSize;
-    
-    unsigned int i;
 
-    /* We will sort permi[].  So we first set up permi[] to reflect the
-       original, unsorted order.
-    */
-    for (i=0; i < cmapSize; i++)
-        permi[i] = i;
-
-    if (sort) {
-        pm_message("sorting colormap");
-        for (i = 0; i < cmapSize; ++i) {
-            unsigned int j;
-            for (j = i+1; j < cmapSize; ++j) {
-                if (sortOrder(color[permi[i]], pamP) >
-                    sortOrder(color[permi[j]], pamP)) {
-
-                    sample tmp;
-                    
-                    tmp = permi[i]; permi[i] = permi[j]; permi[j] = tmp;
-                }
-            }
-        }
-    }
-    for (i = 0; i < cmapSize; ++i)
-        perm[permi[i]] = i;
+    pm_message("sorting colormap");
+
+    if (mapPamP->depth < 3)
+        qsort(tuplefreq, colors, sizeof(tuplefreq[0]), sortCompareGray);
+    else
+        qsort(tuplefreq, colors, sizeof(tuplefreq[0]), sortCompareColor); 
+
 }
 
 
@@ -1660,7 +1703,7 @@ colormapFromFile(char               const filespec[],
 
     pm_message("computing other colormap ...");
     
-    *tupletableP =
+    *tupletableP = 
         pnm_computetuplefreqtable(mapPamP, colors, maxcolors, &colorCount);
 
     *colorCountP = colorCount;
@@ -1671,6 +1714,60 @@ colormapFromFile(char               const filespec[],
 
 
 static void
+readAndValidateColormapFromFile(char           const filename[],
+                                unsigned int   const maxcolors,
+                                tupletable *   const tuplefreqP, 
+                                struct pam *   const mapPamP,
+                                unsigned int * const colorCountP,
+                                unsigned int   const nInputComp,
+                                sample         const inputMaxval) {
+/*----------------------------------------------------------------------------
+   Read the colormap from a separate colormap file named filename[],
+   and make sure it's consistent with an image with 'nInputComp'
+   color components (e.g. 3 for RGB) and a maxval of 'inputMaxval'.
+-----------------------------------------------------------------------------*/
+    colormapFromFile(filename, maxcolors, tuplefreqP, mapPamP, colorCountP);
+
+    if (mapPamP->depth != nInputComp)
+        pm_error("Depth of map file (%u) does not match number of "
+                 "color components in input file (%u)",
+                 mapPamP->depth, nInputComp);
+    if (mapPamP->maxval != inputMaxval)
+        pm_error("Maxval of map file (%lu) does not match maxval of "
+                 "input file (%lu)", mapPamP->maxval, inputMaxval);
+}
+
+
+
+static void
+computeColormapBw(struct pam *   const pamP,
+                  struct pam *   const mapPamP,
+                  unsigned int * const colorCountP,
+                  tupletable   * const tuplefreqP) {
+/*----------------------------------------------------------------------------
+  Shortcut for black and white (e.g. PBM).  We know that there are
+  only two colors.  Users who know that only one color is present in
+  the image should specify -sort at the command line.  Example:
+
+   $ pbmmake -w 600 400 | pamtogif -sort > canvas.gif
+-----------------------------------------------------------------------------*/
+    tupletable const colormap = pnm_alloctupletable(pamP, 2);
+    
+    *mapPamP = *pamP;
+    mapPamP->depth = 1;
+
+    colormap[0]->value = 1;
+    colormap[0]->tuple[0] = PAM_BLACK;
+    colormap[1]->value = 1;
+    colormap[1]->tuple[0] = PAM_BW_WHITE;
+    
+    *tuplefreqP  = colormap;
+    *colorCountP = 2;
+}
+  
+    
+
+static void
 computeColormapFromInput(struct pam *   const pamP,
                          unsigned int   const maxcolors,
                          unsigned int   const nInputComp,
@@ -1700,7 +1797,8 @@ computeLibnetpbmColormap(struct pam *   const pamP,
                          tuple *        const color,
                          tuplehash *    const tuplehashP,
                          struct pam *   const mapPamP,
-                         unsigned int * const colorCountP) {
+                         unsigned int * const colorCountP,
+                         bool           const sort) {
 /*----------------------------------------------------------------------------
    Compute a colormap, libnetpbm style, for the image described by
    'pamP', which is positioned to the raster.
@@ -1719,6 +1817,9 @@ computeLibnetpbmColormap(struct pam *   const pamP,
    too many, allow one slot for a fake transparency color if 'haveAlpha'
    is true.  If there are too many, issue an error message and abort the
    program.
+
+   'sort' means to sort the colormap by red intensity, then by green
+   intensity, then by blue intensity, as opposed to arbitrary order.
 -----------------------------------------------------------------------------*/
     unsigned int const maxcolors = haveAlpha ? MAXCMAPSIZE - 1 : MAXCMAPSIZE;
         /* The most colors we can tolerate in the image.  If we have
@@ -1732,19 +1833,14 @@ computeLibnetpbmColormap(struct pam *   const pamP,
     tupletable tuplefreq;
     unsigned int colorCount;
 
-    if (mapfile) {
-        /* Read the colormap from a separate colormap file. */
-        colormapFromFile(mapfile, maxcolors, &tuplefreq, mapPamP,
-                         &colorCount);
-
-        if (mapPamP->depth != nInputComp)
-            pm_error("Depth of map file (%u) does not match number of "
-                     "color components in input file (%u)",
-                      mapPamP->depth, pamP->depth);
-        if (mapPamP->maxval != pamP->maxval)
-            pm_error("Maxval of map file (%lu) does not match maxval of "
-                     "input file (%lu)", mapPamP->maxval, pamP->maxval);
-    } else
+    if (mapfile)
+        readAndValidateColormapFromFile(mapfile, maxcolors, &tuplefreq,
+                                        mapPamP, &colorCount,
+                                        nInputComp, pamP->maxval);
+    else if (nInputComp == 1 && pamP->maxval == 1 && !sort &&
+             pamP->height * pamP->width > 1)
+        computeColormapBw(pamP, mapPamP, &colorCount, &tuplefreq);
+    else
         computeColormapFromInput(pamP, maxcolors, nInputComp, 
                                  mapPamP, &colorCount, &tuplefreq);
     
@@ -1753,6 +1849,9 @@ computeLibnetpbmColormap(struct pam *   const pamP,
 
     pm_message("%u colors found", colorCount);
 
+    if (sort)
+        sortTupletable(mapPamP, colorCount, tuplefreq);
+
     for (i = 0; i < colorCount; ++i) {
         color[i] = pnm_allocpamtuple(mapPamP);
         pnm_assigntuple(mapPamP, color[i], tuplefreq[i]->tuple);
@@ -1812,7 +1911,7 @@ main(int argc, char *argv[]) {
     
     computeLibnetpbmColormap(&pam, !!pamAlphaPlane(&pam), cmdline.mapfile, 
                              cmap.color, &cmap.tuplehash,
-                             &cmap.pam, &cmap.cmapSize);
+                             &cmap.pam, &cmap.cmapSize, cmdline.sort);
     
     assert(cmap.pam.maxval == pam.maxval);
 
@@ -1822,9 +1921,8 @@ main(int argc, char *argv[]) {
         */
         addToColormap(&cmap, cmdline.alphacolor, &fakeTransparent);
     }
-    sortColormap(cmdline.sort, &cmap);
 
-    bitsPerPixel = pm_maxvaltobits(cmap.cmapSize-1);
+    bitsPerPixel = cmap.cmapSize == 1 ? 1 : nSignificantBits(cmap.cmapSize-1);
 
     computeTransparent(cmdline.transparent,
                        !!pamAlphaPlane(&pam), fakeTransparent, &cmap);
@@ -1832,7 +1930,7 @@ main(int argc, char *argv[]) {
     /* All set, let's do it. */
     gifEncode(&pam, stdout, rasterPos,
               cmdline.interlace, 0, bitsPerPixel, &cmap, cmdline.comment,
-              !cmdline.nolzw);
+              cmdline.aspect, !cmdline.nolzw);
     
     destroyCmap(&cmap);
 
@@ -1856,6 +1954,10 @@ main(int argc, char *argv[]) {
   JPEG Group's djpeg on 2001.09.29.  In 2006.12 the output subroutines
   were rewritten; now no uncompressed output subroutines are derived from
   the Independent JPEG Group's source code.
+  
+  2007.01  Changed sort routine to qsort.  (afu)
+  2007.03  Implemented variable hash table size, PBM color table
+           shortcut and "-aspect" command line option.   (afu)
 
  
   Copyright (C) 1989 by Jef Poskanzer.
@@ -1871,3 +1973,4 @@ main(int argc, char *argv[]) {
   CompuServe Incorporated.  GIF(sm) is a Service Mark property of
   CompuServe Incorporated.
 ============================================================================*/
+
diff --git a/converter/other/pamtoxvmini.c b/converter/other/pamtoxvmini.c
index 3207b0d5..e1aa9b52 100644
--- a/converter/other/pamtoxvmini.c
+++ b/converter/other/pamtoxvmini.c
@@ -67,7 +67,6 @@ makeXvPalette(xvPalette * const xvPaletteP) {
             }
         }
     }
-
 }
 
 
@@ -237,7 +236,7 @@ main(int    argc,
     struct pam pam;
     xvPalette xvPalette;
  
-    ppm_init(&argc, argv);
+    pnm_init(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
 
diff --git a/converter/other/pnmtojpeg.c b/converter/other/pnmtojpeg.c
index 5a4c5511..eb57bcc5 100644
--- a/converter/other/pnmtojpeg.c
+++ b/converter/other/pnmtojpeg.c
@@ -1012,10 +1012,9 @@ convert_scanlines(struct jpeg_compress_struct * const cinfo_p,
   }
 
   pnm_freerow(pnm_buffer);
-  /* Dont' worry about the compressor input buffer; it gets freed 
+  /* Don't worry about the compressor input buffer; it gets freed 
      automatically
   */
-
 }
 
 
diff --git a/converter/other/pnmtops.c b/converter/other/pnmtops.c
index 9f6de526..85d5d36d 100644
--- a/converter/other/pnmtops.c
+++ b/converter/other/pnmtops.c
@@ -496,7 +496,7 @@ destroyBmepsOutputEncoder(struct bmepsoe * const bmepsoeP) {
 static void
 outputBmepsSample(struct bmepsoe * const bmepsoeP,
                   unsigned int     const sampleValue,
-          unsigned int     const bitsPerSample) {
+                  unsigned int     const bitsPerSample) {
 
     if (bitsPerSample == 8)
         oe_byte_add(bmepsoeP->oeP, sampleValue);
@@ -1300,7 +1300,7 @@ main(int argc, char * argv[]) {
     strfree(name);
 
     pm_close(ifp);
-    
+
     return 0;
 }
 
diff --git a/converter/other/xwdtopnm.c b/converter/other/xwdtopnm.c
index 28c38cfc..1d8e1b64 100644
--- a/converter/other/xwdtopnm.c
+++ b/converter/other/xwdtopnm.c
@@ -14,6 +14,12 @@
 
    The file X11/XWDFile.h from the X Window System is an authority for the
    format of an XWD file.  Netpbm uses its own declaration, though.
+
+   It has been a real challenge trying to reverse engineer the XWD
+   format.  This program is almost always broken as people find XWD images
+   with which it does not work and we update the program in response.
+
+   We consider an XWD file correct if Xwud displays it properly.
 */
 
 
@@ -32,11 +38,18 @@
 #include "x10wd.h"
 #include "x11wd.h"
 
+struct compMask {
+    unsigned long red;
+    unsigned long grn;
+    unsigned long blu;
+};
+
+
 struct cmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
-    char *input_filename;
+    const char * inputFilename;
     unsigned int verbose;
     unsigned int debug;
     unsigned int headerdump;
@@ -119,12 +132,12 @@ parseCommandLine(int argc, char ** argv,
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
     if (argc - 1 == 0)
-        cmdlineP->input_filename = NULL;  /* he wants stdin */
+        cmdlineP->inputFilename = NULL;  /* he wants stdin */
     else if (argc - 1 == 1) {
         if (STREQ(argv[1], "-"))
-            cmdlineP->input_filename = NULL;  /* he wants stdin */
+            cmdlineP->inputFilename = NULL;  /* he wants stdin */
         else 
-            cmdlineP->input_filename = strdup(argv[1]);
+            cmdlineP->inputFilename = strdup(argv[1]);
     } else 
         pm_error("Too many arguments.  The only argument accepted\n"
                  "is the input file specification");
@@ -137,16 +150,14 @@ processX10Header(X10WDFileHeader *  const h10P,
                  FILE *             const file,
                  int *              const colsP, 
                  int *              const rowsP, 
-                 int *              const padrightP, 
+                 unsigned int *     const padrightP, 
                  xelval *           const maxvalP, 
                  enum visualclass * const visualclassP, 
                  int *              const formatP, 
                  xel **             const colorsP, 
                  int *              const bits_per_pixelP, 
                  int *              const bits_per_itemP, 
-                 unsigned long *    const red_maskP, 
-                 unsigned long *    const green_maskP, 
-                 unsigned long *    const blue_maskP,
+                 struct compMask *  const compMaskP,
                  enum byteorder *   const byte_orderP,
                  enum byteorder *   const bit_orderP) {
 
@@ -215,7 +226,7 @@ processX10Header(X10WDFileHeader *  const h10P,
         PNM_ASSIGN1( (*colorsP)[0], 0 );
         PNM_ASSIGN1( (*colorsP)[1], *maxvalP );
         *padrightP =
-            ( ( h10P->pixmap_width + 15 ) / 16 ) * 16 - h10P->pixmap_width;
+            (((h10P->pixmap_width + 15) / 16) * 16 - h10P->pixmap_width) * 8;
         *bits_per_itemP = 16;
         *bits_per_pixelP = 1;
     } else if ( h10P->window_ncolors == 0 ) { 
@@ -227,7 +238,7 @@ processX10Header(X10WDFileHeader *  const h10P,
         for ( i = 0; i <= *maxvalP; ++i )
             PNM_ASSIGN1( (*colorsP)[i], i );
         *padrightP =
-            ( ( h10P->pixmap_width + 15 ) / 16 ) * 16 - h10P->pixmap_width;
+            (((h10P->pixmap_width + 15) / 16) * 16 - h10P->pixmap_width) * 8;
         *bits_per_itemP = 16;
         *bits_per_pixelP = 1;
     } else {
@@ -245,7 +256,7 @@ processX10Header(X10WDFileHeader *  const h10P,
                     x10colors[i].blue);
         }
 
-        *padrightP = h10P->pixmap_width & 1;
+        *padrightP = (h10P->pixmap_width & 1) * 8;
         *bits_per_itemP = 8;
         *bits_per_pixelP = 8;
     }
@@ -426,21 +437,65 @@ dumpX11Header(X11WDFileHeader * const h11P) {
 
 
 
+static unsigned long
+reverseBits(unsigned long arg,
+            unsigned int nSigBits) {
+
+    unsigned long input;
+    unsigned long output;
+    unsigned int i;
+
+    for (i = 0, input = arg, output = 0; i < nSigBits; ++i) {
+        output <<= 1;
+
+        output |= (input & 0x1);
+
+        input >>= 1;
+    }
+    return output;
+}
+
+
+
+static void
+computeComponentMasks(X11WDFileHeader * const h11P,
+                      struct compMask * const compMaskP) {
+/*----------------------------------------------------------------------------
+   You'd think the component (red, green, blue) masks in the header
+   would just be right.  But we've seen a direct color image which has
+   BGR layout even though the masks say RGB.  It also says bit order
+   is LSB first, even though the pixels within the items are arranged
+   MSB first.  So we're guessing that LSB first bit order in that
+   particular case means the bits within each the pixel are backwards.
+   So we reverse the masks to compensate.
+-----------------------------------------------------------------------------*/
+    if (h11P->visual_class == DirectColor &&
+        h11P->bits_per_pixel == 24 && h11P->bitmap_bit_order == LSBFirst) {
+        compMaskP->red = reverseBits(h11P->red_mask, 24);
+        compMaskP->grn = reverseBits(h11P->green_mask, 24);
+        compMaskP->blu = reverseBits(h11P->blue_mask, 24);
+    } else {
+        compMaskP->red = h11P->red_mask;
+        compMaskP->grn = h11P->green_mask;
+        compMaskP->blu = h11P->blue_mask;
+    }
+}
+
+
+
 static void
 processX11Header(X11WDFileHeader *  const h11P, 
                  FILE *             const file,
                  int *              const colsP, 
                  int *              const rowsP, 
-                 int *              const padrightP, 
+                 unsigned int *     const padrightP, 
                  xelval *           const maxvalP, 
                  enum visualclass * const visualclassP, 
                  int *              const formatP, 
                  xel **             const colorsP, 
                  int *              const bits_per_pixelP, 
                  int *              const bits_per_itemP, 
-                 unsigned long *    const red_maskP, 
-                 unsigned long *    const green_maskP, 
-                 unsigned long *    const blue_maskP,
+                 struct compMask *  const compMaskP,
                  enum byteorder *   const byte_orderP,
                  enum byteorder *   const bit_orderP) {
 
@@ -460,34 +515,31 @@ processX11Header(X11WDFileHeader *  const h11P,
             pm_error("couldn't read rest of X11 XWD file header");
 
     /* Check whether we can handle this dump. */
-    if ( h11FixedP->pixmap_depth > 24 )
-        pm_error( "can't handle X11 pixmap_depth > 24" );
-    if ( h11FixedP->bits_per_rgb > 24 )
-        pm_error( "can't handle X11 bits_per_rgb > 24" );
-    if ( h11FixedP->pixmap_format != ZPixmap && h11FixedP->pixmap_depth != 1 )
-        pm_error(
-            "can't handle X11 pixmap_format %d with depth != 1",
-            h11FixedP->pixmap_format );
-    if ( h11FixedP->bitmap_unit != 8 && h11FixedP->bitmap_unit != 16 &&
-         h11FixedP->bitmap_unit != 32 )
-        pm_error(
-            "X11 bitmap_unit (%d) is non-standard - can't handle",
-            h11FixedP->bitmap_unit );
+    if (h11FixedP->pixmap_depth > 24)
+        pm_error( "can't handle X11 pixmap_depth > 24");
+    if (h11FixedP->bits_per_rgb > 24)
+        pm_error("can't handle X11 bits_per_rgb > 24");
+    if (h11FixedP->pixmap_format != ZPixmap && h11FixedP->pixmap_depth != 1)
+        pm_error("can't handle X11 pixmap_format %d with depth != 1",
+                 h11FixedP->pixmap_format);
+    if (h11FixedP->bitmap_unit != 8 && h11FixedP->bitmap_unit != 16 &&
+        h11FixedP->bitmap_unit != 32)
+        pm_error("X11 bitmap_unit (%d) is non-standard - can't handle",
+                 h11FixedP->bitmap_unit);
     /* The following check was added in 10.19 (November 2003) */
-    if ( h11FixedP->bitmap_pad != 8 && h11FixedP->bitmap_pad != 16 &&
-         h11FixedP->bitmap_pad != 32 )
-        pm_error(
-            "X11 bitmap_pad (%d) is non-standard - can't handle",
-            h11FixedP->bitmap_unit );
-
-    if ( h11FixedP->ncolors > 0 ) {
-        readX11Colormap( file, h11FixedP->ncolors, byte_swap, &x11colors );
-        grayscale = colormapAllGray( x11colors, h11FixedP->ncolors );
+    if (h11FixedP->bitmap_pad != 8 && h11FixedP->bitmap_pad != 16 &&
+        h11FixedP->bitmap_pad != 32)
+        pm_error("X11 bitmap_pad (%d) is non-standard - can't handle",
+                 h11FixedP->bitmap_unit);
+
+    if (h11FixedP->ncolors > 0) {
+        readX11Colormap(file, h11FixedP->ncolors, byte_swap, &x11colors);
+        grayscale = colormapAllGray(x11colors, h11FixedP->ncolors);
     } else
         grayscale = TRUE;
 
     *visualclassP = (enum visualclass) h11FixedP->visual_class;
-    if ( *visualclassP == DirectColor ) {
+    if (*visualclassP == DirectColor) {
         unsigned int i;
         *formatP = PPM_TYPE;
         *maxvalP = 65535;
@@ -497,12 +549,12 @@ processX11Header(X11WDFileHeader *  const h11P,
           is composed of 3 separate indices.
         */
 
-        *colorsP = pnm_allocrow( h11FixedP->ncolors );
-        for ( i = 0; i < h11FixedP->ncolors; ++i )
+        *colorsP = pnm_allocrow(h11FixedP->ncolors);
+        for (i = 0; i < h11FixedP->ncolors; ++i)
             PPM_ASSIGN(
                 (*colorsP)[i], x11colors[i].red, x11colors[i].green,
                 x11colors[i].blue);
-    } else if ( *visualclassP == TrueColor ) {
+    } else if (*visualclassP == TrueColor) {
         *formatP = PPM_TYPE;
 
         *maxvalP = pm_lcm(pm_bitstomaxval(one_bits(h11FixedP->red_mask)),
@@ -510,31 +562,30 @@ processX11Header(X11WDFileHeader *  const h11P,
                           pm_bitstomaxval(one_bits(h11FixedP->blue_mask)),
                           PPM_OVERALLMAXVAL
             );
-    }
-    else if ( *visualclassP == StaticGray && h11FixedP->bits_per_pixel == 1 ) {
+    } else if (*visualclassP == StaticGray && h11FixedP->bits_per_pixel == 1) {
         *formatP = PBM_TYPE;
         *maxvalP = 1;
         *colorsP = pnm_allocrow( 2 );
-        PNM_ASSIGN1( (*colorsP)[0], *maxvalP );
-        PNM_ASSIGN1( (*colorsP)[1], 0 );
-    } else if ( *visualclassP == StaticGray ) {
+        PNM_ASSIGN1((*colorsP)[0], *maxvalP);
+        PNM_ASSIGN1((*colorsP)[1], 0);
+    } else if (*visualclassP == StaticGray) {
         unsigned int i;
         *formatP = PGM_TYPE;
-        *maxvalP = ( 1 << h11FixedP->bits_per_pixel ) - 1;
-        *colorsP = pnm_allocrow( *maxvalP + 1 );
-        for ( i = 0; i <= *maxvalP; ++i )
-            PNM_ASSIGN1( (*colorsP)[i], i );
+        *maxvalP = (1 << h11FixedP->bits_per_pixel) - 1;
+        *colorsP = pnm_allocrow(*maxvalP + 1);
+        for (i = 0; i <= *maxvalP; ++i)
+            PNM_ASSIGN1((*colorsP)[i], i);
     } else {
-        *colorsP = pnm_allocrow( h11FixedP->ncolors );
-        if ( grayscale ) {
+        *colorsP = pnm_allocrow(h11FixedP->ncolors);
+        if (grayscale) {
             unsigned int i;
             *formatP = PGM_TYPE;
-            for ( i = 0; i < h11FixedP->ncolors; ++i )
-                PNM_ASSIGN1( (*colorsP)[i], x11colors[i].red );
+            for (i = 0; i < h11FixedP->ncolors; ++i)
+                PNM_ASSIGN1((*colorsP)[i], x11colors[i].red);
         } else {
             unsigned int i;
             *formatP = PPM_TYPE;
-            for ( i = 0; i < h11FixedP->ncolors; ++i )
+            for (i = 0; i < h11FixedP->ncolors; ++i)
                 PPM_ASSIGN(
                     (*colorsP)[i], x11colors[i].red, x11colors[i].green,
                     x11colors[i].blue);
@@ -545,13 +596,14 @@ processX11Header(X11WDFileHeader *  const h11P,
     *colsP = h11FixedP->pixmap_width;
     *rowsP = h11FixedP->pixmap_height;
     *padrightP =
-        h11FixedP->bytes_per_line * 8 / h11FixedP->bits_per_pixel -
-        h11FixedP->pixmap_width;
+        h11FixedP->bytes_per_line * 8 -
+        h11FixedP->pixmap_width * h11FixedP->bits_per_pixel;
+
     /* According to X11/XWDFile.h, the item size is 'bitmap_pad' for some
        images and 'bitmap_unit' for others.  This is strange, so there may
        be some subtlety of their definitions that we're missing.
 
-       See comments in getpix() about what an item is.
+       See comments in pixelReader_getpix() about what an item is.
 
        Ben Kelley in January 2002 had a 32 bits-per-pixel xwd file
        from a truecolor 32 bit window on a Hummingbird Exceed X server
@@ -561,22 +613,38 @@ processX11Header(X11WDFileHeader *  const h11P,
        bit-per-pixel direct color window that had bitmap_unit = 32 and
        bitmap_pad = 8.  This was made by Xwd in Red Hat Xfree86 4.3.0-2.
 
+       In March 2007, Darren Frith presented an xwd file like this:
+       Header says direct color, bits_per_pixel = 24, bitmap_unit =
+       32, bitmap_pad = 8, byte order and bit order LSB first.  The
+       bytes in each item are in fact MSB first and the pixels spread
+       across the items MSB first.  The raster is consecutive 24 bit
+       pixel units, but each row is padded on the right with enough
+       bits to make the total line size 32 x width.  Really strange.
+       The header says the bits within each pixel are one byte red,
+       one byte green, one byte blue.  But they are actually blue,
+       green, red.  Xwud, ImageMagick, and Gimp render this image
+       correctly, so it's not broken.  Created by Xwd of X.org 7.1.1.
+
        Before Netpbm 9.23 (January 2002), we used bitmap_unit as the
        item size always.  Then, until 10.19 (November 2003), we used
        bitmap_pad when pixmap_depth > 1 and pixmap_format == ZPixmap.
        We still don't see any logic in these fields at all, but we
        figure whichever one is greater (assuming both are meaningful)
-       has to be the item size.  
-    */
-    *bits_per_itemP = MAX(h11FixedP->bitmap_pad, h11FixedP->bitmap_unit);
-
+       has to be the item size.  */
+    *bits_per_itemP  = MAX(h11FixedP->bitmap_pad, h11FixedP->bitmap_unit);
     *bits_per_pixelP = h11FixedP->bits_per_pixel;
 
-    *byte_orderP = (enum byteorder) h11FixedP->byte_order;
-    *bit_orderP = (enum byteorder) h11FixedP->bitmap_bit_order;
-    *red_maskP = h11FixedP->red_mask;
-    *green_maskP = h11FixedP->green_mask;
-    *blue_maskP = h11FixedP->blue_mask;
+    if (*visualclassP == DirectColor) {
+        /* Strange, but we've seen a Direct Color 24/32 image that
+           says LSBFirst and it's a lie.  And Xwud renders it correctly.
+        */
+        *byte_orderP = MSBFirst;
+        *bit_orderP = MSBFirst;
+    } else {
+        *byte_orderP = (enum byteorder) h11FixedP->byte_order;
+        *bit_orderP  = (enum byteorder) h11FixedP->bitmap_bit_order;
+    }
+    computeComponentMasks(h11FixedP, compMaskP);
 
     free(h11FixedP);
 } 
@@ -587,16 +655,14 @@ static void
 getinit(FILE *             const ifP, 
         int *              const colsP, 
         int *              const rowsP, 
-        int *              const padrightP, 
+        unsigned int *     const padrightP, 
         xelval *           const maxvalP, 
         enum visualclass * const visualclassP, 
         int *              const formatP, 
         xel **             const colorsP,
         int *              const bits_per_pixelP, 
         int *              const bits_per_itemP, 
-        unsigned long *    const red_maskP, 
-        unsigned long *    const green_maskP,
-        unsigned long *    const blue_maskP,
+        struct compMask *  const compMaskP,
         enum byteorder *   const byte_orderP,
         enum byteorder *   const bit_orderP,
         bool               const headerDump) {
@@ -606,10 +672,10 @@ getinit(FILE *             const ifP,
 
    Return various fields from the header.
 
-   Return as *padrightP the number of additional pixels of padding are
+   Return as *padrightP the number of additional bits of padding are
    at the end of each line of input.  This says the input stream
-   contains *colsP pixels of image data plus *padrightP pixels of
-   padding.
+   contains *colsP pixels of image data (at *bits_per_pixelP bits each)
+   plus *padrightP bits of padding.
 -----------------------------------------------------------------------------*/
     /* Assume X11 headers are larger than X10 ones. */
     unsigned char header[sizeof(X11WDFileHeader)];
@@ -643,8 +709,7 @@ getinit(FILE *             const ifP,
         processX10Header(h10P, ifP, colsP, rowsP, padrightP, maxvalP, 
                          visualclassP, formatP, 
                          colorsP, bits_per_pixelP, bits_per_itemP, 
-                         red_maskP, green_maskP, blue_maskP, 
-                         byte_orderP, bit_orderP);
+                         compMaskP, byte_orderP, bit_orderP);
     } else if (h11P->file_version == X11WD_FILE_VERSION ||
                pm_bs_long(h11P->file_version) == X11WD_FILE_VERSION) {
         
@@ -665,8 +730,7 @@ getinit(FILE *             const ifP,
         processX11Header(h11P, ifP, colsP, rowsP, padrightP, maxvalP, 
                          visualclassP, formatP, 
                          colorsP, bits_per_pixelP, bits_per_itemP, 
-                         red_maskP, green_maskP, blue_maskP, 
-                         byte_orderP, bit_orderP);
+                         compMaskP, byte_orderP, bit_orderP);
     } else
         pm_error("unknown XWD file version: %u", h11P->file_version);
 }
@@ -719,13 +783,10 @@ getinit(FILE *             const ifP,
    one pixel at a time from it.
 
    It consists of a structure of type 'pixelReader' and the
-   getpix() and pixelReaderInit() subroutines.
+   pixelReader_*() subroutines.
 -----------------------------------------------------------------------------*/
 
 typedef struct {
-    /* This structure contains the state of the getpix() reader as it
-       reads across a row in the input image.
-       */
     FILE * fileP;
     unsigned long itemBuffer;
         /* The item buffer.  This contains what's left of the item
@@ -770,12 +831,12 @@ typedef struct {
 
 
 static void
-pixelReaderInit(pixelReader *  const pixelReaderP,
-                FILE *         const fileP,
-                int            const bitsPerPixel,
-                int            const bitsPerItem, 
-                enum byteorder const byteOrder,
-                enum byteorder const bitOrder) {
+pixelReader_init(pixelReader *  const pixelReaderP,
+                 FILE *         const fileP,
+                 int            const bitsPerPixel,
+                 int            const bitsPerItem, 
+                 enum byteorder const byteOrder,
+                 enum byteorder const bitOrder) {
     
     pixelReaderP->fileP           = fileP;
     pixelReaderP->bitsPerPixel    = bitsPerPixel;
@@ -789,6 +850,33 @@ pixelReaderInit(pixelReader *  const pixelReaderP,
 
 
 static void
+pixelReader_term(pixelReader * const pixelReaderP) {
+
+    uint remainingByteCount;
+
+    if (pixelReaderP->nBitsLeft > 0)
+        pm_message("Warning: %u unused bits left in the pixel reader "
+                   "buffer after full image converted.  XWD file may be "
+                   "corrupted or Xwdtopnm may have misinterpreted it",
+                   pixelReaderP->nBitsLeft);
+
+
+    pm_drain(pixelReaderP->fileP, 4096, &remainingByteCount);
+
+    if (remainingByteCount >= 4096)
+        pm_message("Warning: at least 4K additional bytes in XWD input stream "
+                   "after full image converted.  XWD file may be corrupted "
+                   "or Xwdtopnm may have misinterpreted it.");
+    else if (remainingByteCount > 0)
+        pm_message("Warning: %u additional bytes in XWD input stream "
+                   "after full image converted.  XWD file may be corrupted "
+                   "or Xwdtopnm may have misinterpreted it.",
+                   remainingByteCount);
+}
+
+
+
+static void
 readItem(pixelReader * const rdrP) {
 /*----------------------------------------------------------------------------
    Read one item from the XWD raster associated with pixel reader *rdrP.
@@ -861,55 +949,16 @@ static unsigned long const lsbmask[] = {
 
 
 static unsigned long
-getpix(pixelReader * const rdrP) {
+pixelReader_getbits(pixelReader * const rdrP,
+                    unsigned int  const nBits) {
 /*----------------------------------------------------------------------------
-   Get a pixel from the input image.
-
-   A pixel is a bit string.  It may be either an rgb triplet or an index
-   into the colormap (or even an rgb triplet of indices into the colormaps!).
-   We don't care -- it's just a bit string.
-
-   We return an integer.  It's the integer that the pixel represents as
-   pure binary cipher, with the first bit the most significant bit.
-   
-   The basic unit of storage in the input file is an "item."  An item
-   can be 1, 2, or 4 bytes, and 'bits_per_item' tells us which.  Each
-   item can have its bytes stored in forward or reverse order, and
-   'byte_order' tells us which.
-
-   Each item can contain one or more pixels, and may contain
-   fractional pixels.  'bits_per_pixel' tells us how many bits each
-   pixel has, and 'bits_per_pixel' is always less than or equal to
-   'bits_per_item', but not necessarily a factor of it.  Within an item,
-   after taking care of the endianness of its storage format, the pixels
-   may be arranged from left to right or right to left.  'bit_order' tells
-   us which.
-
-   But it's not that simple.  Sometimes dummy pixels are added to the
-   right edge of the image in order to make an integral number of
-   items in each row of the raster.  getpix() doesn't know anything
-   about that, though -- it gets the dummy pixels the same as any
-   other pixel.  (This program is written as if it is always whole
-   pixels that get added for padding, but I wonder.  When pixels are 3
-   or 4 bytes and items are 4 bytes, sub-pixel padding would make
-   sense.  But then, maybe in formats with 3 or 4 bytes pixels,
-   there's never padding.  That seems to be the case so far...).  The
-   XWD header has a field that tells how many bytes there are per XWD
-   raster line, so that's the final word on how much padding there is.
-   
-   The most difficult part of getting the pixel is the ones that span
-   items.  We detect when an item has part, but not all, of the next
-   pixel (after the one we return) in it and store the fragment as
-   "carryover" bits for use in the next call to this function.
-
-   All this state information (carryover bits, etc.) is kept in 
-   *row_controlP.
-
+  Get the next 'nBits' bits from the stream, and return the last 32
+  of them.
 -----------------------------------------------------------------------------*/
     unsigned long pixel;
         /* Accumulator for the value we ultimately return.  We shift in
            bits from the right end.  The number of bits presently in the
-           accumulator is rdrP->bitsPerPixel - bitsStillNeeded .
+           accumulator is rdrP->bitsPerPixel - nBitsStillNeeded .
         */
     
     unsigned int nBitsStillNeeded;
@@ -919,10 +968,8 @@ getpix(pixelReader * const rdrP) {
            it -- additional bits will shift in from the right.
         */
 
-    assert(rdrP->bitsPerPixel <= 32);
-
     pixel = 0;
-    nBitsStillNeeded = rdrP->bitsPerPixel;
+    nBitsStillNeeded = nBits;
 
     while (nBitsStillNeeded > 0) {
         if (rdrP->nBitsLeft == 0)
@@ -964,18 +1011,65 @@ getpix(pixelReader * const rdrP) {
 
 
 
+static unsigned long
+pixelReader_getpix(pixelReader * const rdrP) {
+/*----------------------------------------------------------------------------
+   Get a pixel from the input image.
+
+   A pixel is a bit string.  It may be either an rgb triplet or an index
+   into the colormap (or even an rgb triplet of indices into the colormaps!).
+   We don't care -- it's just a bit string.
+
+   We return an integer.  It's the integer that the pixel represents as
+   pure binary cipher, with the first bit the most significant bit.
+   
+   The basic unit of storage in the input file is an "item."  An item
+   can be 1, 2, or 4 bytes, and 'bits_per_item' tells us which.  Each
+   item can have its bytes stored in forward or reverse order, and
+   'byte_order' tells us which.  We have seen a Direct Color 24 bpp/32 bpi
+   image which said 'byte_order' == LSBFirst, but the byte order is
+   nonetheless MSB first.
+
+   Each item can contain one or more pixels, and may contain
+   fractional pixels.  'bits_per_pixel' tells us how many bits each
+   pixel has, and 'bits_per_pixel' is always less than or equal to
+   'bits_per_item', but not necessarily a factor of it.  Within an item,
+   after taking care of the endianness of its storage format, the pixels
+   may be arranged from left to right or right to left.  'bit_order' tells
+   us which.  We have also seen images in which the pixels are arranged
+   from left to right within the items, but the RGB components within
+   each pixel are right to left and 'bit_order' is LSBFirst.
+
+   But it's not that simple.  Sometimes dummy bits are added to the
+   right edge of the image in order to make an integral number of
+   items in each row of the raster.  And we've even seen images where
+   there are a ridiculous number of padding bits on the right so as to
+   make the number of items per line equal the number of pixels per
+   line, even though items are 32 bits and pixels are 24 bits!  The
+   XWD header has a field that tells how many bytes there are per XWD
+   raster line, so that's the final word on how much padding there is.
+
+   We maintain a 32 bit buffer to decouple reading of whole items from
+   the file and reading of an arbitrary number of bits from the
+   pixelReader.
+-----------------------------------------------------------------------------*/
+    assert(rdrP->bitsPerPixel <= 32);
+
+    return pixelReader_getbits(rdrP, rdrP->bitsPerPixel);
+}
+
+
+
 static void
 reportInfo(int              const cols, 
            int              const rows, 
-           int              const padright, 
+           unsigned int     const padright, 
            xelval           const maxval, 
            enum visualclass const visualclass,
            int              const format, 
            int              const bits_per_pixel,
            int              const bits_per_item, 
-           int              const red_mask, 
-           int              const green_mask, 
-           int              const blue_mask,
+           struct compMask  const compMask,
            enum byteorder   const byte_order, 
            enum byteorder   const bit_order) {
     
@@ -1003,15 +1097,15 @@ reportInfo(int              const cols,
     }
     pm_message("%d rows of %d columns with maxval %d",
                rows, cols, maxval);
-    pm_message("padright=%d.  visualclass = %s.  format=%d (%c%c)",
+    pm_message("padright=%u bits.  visualclass = %s.  format=%d (%c%c)",
                padright, visualclass_name, 
                format, format/256, format%256);
     pm_message("bits_per_pixel=%d; bits_per_item=%d",
                bits_per_pixel, bits_per_item);
     pm_message("byte_order=%s; bit_order=%s",
                byte_order_name, bit_order_name);
-    pm_message("red_mask=0x%.8x; green_mask=0x%.8x; blue_mask=0x%.8x",
-               red_mask, green_mask, blue_mask);
+    pm_message("component mask: red=0x%.8lx; grn=0x%.8lx; blu=0x%.8lx",
+               compMask.red, compMask.grn, compMask.blu);
 }
 
 
@@ -1024,36 +1118,34 @@ convertRowSimpleIndex(pixelReader *  const pixelReaderP,
     
     unsigned int col;
     for (col = 0; col < cols; ++col)
-        xelrow[col] = colors[getpix(pixelReaderP)];
+        xelrow[col] = colors[pixelReader_getpix(pixelReaderP)];
 }
 
 
 
 static void
-convertRowDirect(pixelReader *  const pixelReaderP,
-                 int            const cols,
-                 const xel *    const colors,
-                 unsigned long  const red_mask,
-                 unsigned long  const grn_mask,
-                 unsigned long  const blu_mask,
-                 xel *          const xelrow) {
+convertRowDirect(pixelReader *   const pixelReaderP,
+                 int             const cols,
+                 const xel *     const colors,
+                 struct compMask const compMask,
+                 xel *           const xelrow) {
         
     unsigned int col;
 
     for (col = 0; col < cols; ++col) {
         unsigned long pixel;
             /* This is a triplet of indices into the color map, packed
-               into this bit string according to red_mask, etc.
+               into this bit string according to compMask
             */
         unsigned int red_index, grn_index, blu_index;
             /* These are indices into the color map, unpacked from 'pixel'.
              */
             
-        pixel = getpix(pixelReaderP);
+        pixel = pixelReader_getpix(pixelReaderP);
 
-        red_index = (pixel & red_mask) >> zero_bits(red_mask);
-        grn_index = (pixel & grn_mask) >> zero_bits(grn_mask); 
-        blu_index = (pixel & blu_mask) >> zero_bits(blu_mask);
+        red_index = (pixel & compMask.red) >> zero_bits(compMask.red);
+        grn_index = (pixel & compMask.grn) >> zero_bits(compMask.grn); 
+        blu_index = (pixel & compMask.blu) >> zero_bits(compMask.blu);
 
         PPM_ASSIGN(xelrow[col],
                    PPM_GETR(colors[red_index]),
@@ -1066,39 +1158,37 @@ convertRowDirect(pixelReader *  const pixelReaderP,
 
 
 static void
-convertRowTrueColor(pixelReader *  const pixelReaderP,
-                    int                  const cols,
-                    pixval               const maxval,
-                    const xel *          const colors,
-                    unsigned long        const red_mask,
-                    unsigned long        const grn_mask,
-                    unsigned long        const blu_mask,
-                    xel *                const xelrow) {
+convertRowTrueColor(pixelReader *   const pixelReaderP,
+                    int             const cols,
+                    pixval          const maxval,
+                    const xel *     const colors,
+                    struct compMask const compMask,
+                    xel *           const xelrow) {
 
     unsigned int col;
     unsigned int red_shift, grn_shift, blu_shift;
     unsigned int red_maxval, grn_maxval, blu_maxval;
 
-    red_shift = zero_bits(red_mask);
-    grn_shift = zero_bits(grn_mask);
-    blu_shift = zero_bits(blu_mask);
+    red_shift = zero_bits(compMask.red);
+    grn_shift = zero_bits(compMask.grn);
+    blu_shift = zero_bits(compMask.blu);
 
-    red_maxval = red_mask >> red_shift;
-    grn_maxval = grn_mask >> grn_shift;
-    blu_maxval = blu_mask >> blu_shift;
+    red_maxval = compMask.red >> red_shift;
+    grn_maxval = compMask.grn >> grn_shift;
+    blu_maxval = compMask.blu >> blu_shift;
 
     for (col = 0; col < cols; ++col) {
         unsigned long pixel;
 
-        pixel = getpix(pixelReaderP);
+        pixel = pixelReader_getpix(pixelReaderP);
 
         /* The parsing of 'pixel' used to be done with hardcoded layout
            parameters.  See comments at end of this file.
         */
         PPM_ASSIGN(xelrow[col],
-                   ((pixel & red_mask) >> red_shift) * maxval / red_maxval,
-                   ((pixel & grn_mask) >> grn_shift) * maxval / grn_maxval,
-                   ((pixel & blu_mask) >> blu_shift) * maxval / blu_maxval
+                   ((pixel & compMask.red) >> red_shift) * maxval / red_maxval,
+                   ((pixel & compMask.grn) >> grn_shift) * maxval / grn_maxval,
+                   ((pixel & compMask.blu) >> blu_shift) * maxval / blu_maxval
             );
 
     }
@@ -1109,13 +1199,11 @@ convertRowTrueColor(pixelReader *  const pixelReaderP,
 static void
 convertRow(pixelReader *    const pixelReaderP,
            FILE *           const ofP,
-           int              const padright, 
+           unsigned int     const padright, 
            int              const cols, 
            xelval           const maxval,
            int              const format, 
-           unsigned long    const red_mask, 
-           unsigned long    const green_mask, 
-           unsigned long    const blue_mask, 
+           struct compMask  const compMask,
            const xel*       const colors, 
            enum visualclass const visualclass) {
 /*----------------------------------------------------------------------------
@@ -1125,7 +1213,7 @@ convertRow(pixelReader *    const pixelReaderP,
    The row is 'cols' pixels.
 
    After reading the 'cols' pixels, we read and discard an additional
-   'padright' pixels from the input stream, so as to read the entire
+   'padright' bits from the input stream, so as to read the entire
    input line.
 -----------------------------------------------------------------------------*/
     xel* xelrow;
@@ -1139,25 +1227,19 @@ convertRow(pixelReader *    const pixelReaderP,
         convertRowSimpleIndex(pixelReaderP, cols, colors, xelrow);
         break;
     case DirectColor: 
-        convertRowDirect(pixelReaderP, cols, colors,
-                         red_mask, green_mask, blue_mask,
-                         xelrow);
+        convertRowDirect(pixelReaderP, cols, colors, compMask, xelrow);
         
         break;
     case TrueColor: 
         convertRowTrueColor(pixelReaderP, cols, maxval, colors,
-                            red_mask, green_mask, blue_mask,
-                            xelrow);
+                            compMask, xelrow);
         break;
             
     default:
         pm_error("unknown visual class");
     }
-    {
-        unsigned int col;
-        for (col = 0; col < padright; ++col)
-            getpix(pixelReaderP);
-    }
+    pixelReader_getbits(pixelReaderP, padright);
+    
     pnm_writepnmrow(ofP, xelrow, cols, maxval, format, 0);
     pnm_freerow(xelrow);
 }
@@ -1192,15 +1274,17 @@ main(int argc, char *argv[]) {
 
     struct cmdlineInfo cmdline;
     FILE * ifP;
-    int rows, cols, format, padright;
+    int rows, cols, format;
+    unsigned int padright;
+        /* Number of bits of padding on the right of each row */
     unsigned int row;
-    int bits_per_pixel;
-    int bits_per_item;
-    unsigned long red_mask, green_mask, blue_mask;
+    int bitsPerPixel;
+    int bitsPerItem;
+    struct compMask compMask;
     xelval maxval;
     enum visualclass visualclass;
-    enum byteorder byte_order, bit_order;
-    xel *colors;  /* the color map */
+    enum byteorder byteOrder, bitOrder;
+    xel * colors;  /* the color map */
     pixelReader pixelReader;
 
     pnm_init(&argc, argv);
@@ -1210,24 +1294,23 @@ main(int argc, char *argv[]) {
     debug = cmdline.debug;
     verbose = cmdline.verbose;
 
-    if (cmdline.input_filename != NULL) 
-        ifP = pm_openr(cmdline.input_filename);
+    if (cmdline.inputFilename != NULL) 
+        ifP = pm_openr(cmdline.inputFilename);
     else
         ifP = stdin;
 
     getinit(ifP, &cols, &rows, &padright, &maxval, &visualclass, &format, 
-            &colors, &bits_per_pixel, &bits_per_item, 
-            &red_mask, &green_mask, &blue_mask, &byte_order, &bit_order,
+            &colors, &bitsPerPixel, &bitsPerItem, 
+            &compMask, &byteOrder, &bitOrder,
             cmdline.headerdump);
     
     if (verbose) 
         reportInfo(cols, rows, padright, maxval, visualclass,
-                   format, bits_per_pixel, bits_per_item,
-                   red_mask, green_mask, blue_mask, 
-                   byte_order, bit_order);
+                   format, bitsPerPixel, bitsPerItem, compMask,
+                   byteOrder, bitOrder);
 
-    pixelReaderInit(&pixelReader, ifP, bits_per_pixel, bits_per_item,
-                    byte_order, bit_order);
+    pixelReader_init(&pixelReader, ifP, bitsPerPixel, bitsPerItem,
+                     byteOrder, bitOrder);
 
     pnm_writepnminit(stdout, cols, rows, maxval, format, 0);
 
@@ -1235,9 +1318,11 @@ main(int argc, char *argv[]) {
 
     for (row = 0; row < rows; ++row) {
         convertRow(&pixelReader, stdout,
-                   padright, cols, maxval, format,
-                   red_mask, green_mask, blue_mask, colors, visualclass);
+                   padright, cols, maxval, format, compMask,
+                   colors, visualclass);
     }
+
+    pixelReader_term(&pixelReader);
     
     pm_close(ifP);
     pm_close(stdout);
@@ -1246,6 +1331,7 @@ main(int argc, char *argv[]) {
 }
 
 
+
 /*
    This used to be the way we parsed a direct/true color pixel.  I'm 
    keeping it here in case we find out some application needs it this way.
diff --git a/converter/pgm/pgmtopgm.c b/converter/pgm/pgmtopgm.c
index c65e98e0..250bb4dc 100644
--- a/converter/pgm/pgmtopgm.c
+++ b/converter/pgm/pgmtopgm.c
@@ -19,7 +19,7 @@ main(int argc, char *argv[]) {
     int rows, cols;
     gray maxval;
     int row;
-    gray* grayrow;
+    gray * grayrow;
     
     pgm_init(&argc, argv);
     
@@ -32,7 +32,7 @@ main(int argc, char *argv[]) {
 
     grayrow = pgm_allocrow(cols);
 
-    for (row = 0; row < rows; row++) {
+    for (row = 0; row < rows; ++row) {
         pgm_readpgmrow(stdin, grayrow, cols, maxval, format);
         pgm_writepgmrow(stdout, grayrow, cols, maxval, 0);
     }
diff --git a/converter/ppm/picttoppm.c b/converter/ppm/picttoppm.c
index 92eb04a4..62fb410a 100644
--- a/converter/ppm/picttoppm.c
+++ b/converter/ppm/picttoppm.c
@@ -94,16 +94,16 @@ struct rgbPlanes {
     word * blu;
 };
 
+struct canvas {
+    struct rgbPlanes planes;
+};
+
 typedef void (*transfer_func) (struct RGBColor* src, struct RGBColor* dst);
 
 static const char* stage;
 static struct Rect picFrame;
-static word* red;
-static word* green;
-static word* blue;
 static word rowlen;
 static word collen;
-static longword planelen;
 static int verbose;
 static int fullres;
 static int recognize_comment;
@@ -142,10 +142,12 @@ static int ps_cent_x;
 static int ps_cent_y;
 static int ps_cent_set;
 
+typedef void (drawFn)(struct canvas *, int);
+
 struct opdef {
     const char* name;
     int len;
-    void (*impl) (int);
+    drawFn * impl;
     const char* description;
 };
 
@@ -178,13 +180,26 @@ struct raster {
 };
 
 
+
 static void
 allocateRaster(struct raster * const rasterP,
                unsigned int    const width,
                unsigned int    const height,
                unsigned int    const bitsPerPixel) {
-
-    unsigned int const allocWidth = (width + 15)/16 * 16;
+/*----------------------------------------------------------------------------
+   Allocate storage for a raster that can contain a 'width' x 'height'
+   pixel rectangle read from a PICT image with 'bitsPerPixel' bits
+   per pixel.
+
+   Make the space large enough to round the number of pixels up to a
+   multiple of 16, because we've seen many images in which the PICT raster
+   does contain that much padding on the right.  I don't know why; I could
+   understand a multiple of 8, since in 1 bpp image, the smallest unit
+   expressable in PICT is 8 pixels.  But why 16?  The images we saw came
+   from Adobe Illustrator 10 in March 2007, supplied by
+   Guillermo Gómez Valcárcel.
+-----------------------------------------------------------------------------*/
+    unsigned int const allocWidth = ROUNDUP(width, 16);
 
     if (width > UINT_MAX/4 - 16)
         pm_error("Width %u pixels too large for arithmetic", width);
@@ -591,15 +606,23 @@ picComment(word const type,
 
 
 
+static drawFn ShortComment;
+
 static void
-ShortComment(int const version) {
+ShortComment(struct canvas * const canvasP,
+             int             const version) {
+
     picComment(read_word(), 0);
 }
 
 
 
+static drawFn LongComment;
+
 static void
-LongComment(int const version) {
+LongComment(struct canvas * const canvasP,
+            int             const version) {
+
     word type;
 
     type = read_word();
@@ -608,8 +631,12 @@ LongComment(int const version) {
 
 
 
+static drawFn skip_poly_or_region;
+
 static void
-skip_poly_or_region(int const version) {
+skip_poly_or_region(struct canvas * const canvasP,
+                    int             const version) {
+
     stage = "skipping polygon or region";
     skip(read_word() - 2);
 }
@@ -687,10 +714,11 @@ load_fontdir(const char * const dirfile) {
 
 static void
 read_rect(struct Rect * const r) {
-    r->top = read_word();
-    r->left = read_word();
+
+    r->top    = read_word();
+    r->left   = read_word();
     r->bottom = read_word();
-    r->right = read_word();
+    r->right  = read_word();
 }
 
 
@@ -1424,6 +1452,7 @@ static void
 generalBlit(struct Rect       const srcRect, 
             struct Rect       const srcBounds, 
             struct raster     const srcplane,
+            struct rgbPlanes  const planes,
             int               const pixSize, 
             struct Rect       const dstRect, 
             struct Rect       const dstBounds, 
@@ -1464,9 +1493,9 @@ generalBlit(struct Rect       const srcRect,
 
     dstoff = (clipdst.top - dstBounds.top) * dstwid +
         (clipdst.left - dstBounds.left);
-    dst.red = red + dstoff;
-    dst.grn = green + dstoff;
-    dst.blu = blue + dstoff;
+    dst.red = planes.red + dstoff;
+    dst.grn = planes.grn + dstoff;
+    dst.blu = planes.blu + dstoff;
 
     /* get rid of Text mask mode bit, if (erroneously) set */
     if ((mode & ~64) == 0)
@@ -1493,6 +1522,7 @@ static int
 blit(struct Rect       const srcRect, 
      struct Rect       const srcBounds, 
      struct raster     const srcplane,
+     struct canvas *   const canvasP,
      int               const pixSize, 
      struct Rect       const dstRect, 
      struct Rect       const dstBounds, 
@@ -1536,7 +1566,7 @@ blit(struct Rect       const srcRect,
 
             retval = 0;
         } else {
-            generalBlit(srcRect, srcBounds, srcplane, pixSize,
+            generalBlit(srcRect, srcBounds, srcplane, canvasP->planes, pixSize,
                         dstRect, dstBounds, dstwid, color_map, mode,
                         clipsrc, clipdst);
 
@@ -1555,17 +1585,15 @@ blit(struct Rect       const srcRect,
  */
 
 static void 
-allocPlanes(struct rgbPlanes * const planesP) {
+allocPlanes(unsigned int       const width,
+            unsigned int       const height,
+            struct rgbPlanes * const planesP) {
 
-    struct rgbPlanes planes;
-
-    rowlen = picFrame.right - picFrame.left;
-    collen = picFrame.bottom - picFrame.top;
+    unsigned int const planelen = width * height;
 
-    clip_rect = picFrame;
+    struct rgbPlanes planes;
 
-    planelen = rowlen * collen;
-    MALLOCARRAY(planes.red,  planelen);
+    MALLOCARRAY(planes.red, planelen);
     MALLOCARRAY(planes.grn, planelen);
     MALLOCARRAY(planes.blu, planelen);
     if (planes.red == NULL || planes.grn == NULL || planes.blu == NULL)
@@ -1577,14 +1605,6 @@ allocPlanes(struct rgbPlanes * const planesP) {
     memset(planes.blu, 255, planelen * sizeof(word));
 
     *planesP = planes;
-
-    /* Until we wean this program off of global variables, we have to
-       set these:
-    */
-
-    red   = planes.red;
-    green = planes.grn;
-    blue  = planes.blu;
 }
 
 
@@ -1608,7 +1628,7 @@ compact(word const input) {
 
 
 static void
-do_blits(struct rgbPlanes * const planesP) {
+do_blits(struct canvas * const canvasP) {
 
     struct blit_info* bi;
     int srcwidth, dstwidth, srcheight, dstheight;
@@ -1706,10 +1726,15 @@ do_blits(struct rgbPlanes * const planesP) {
         rectscale(&picFrame, xscale, yscale);
     }
 
-    allocPlanes(planesP);
+    rowlen = picFrame.right  - picFrame.left;
+    collen = picFrame.bottom - picFrame.top;
+
+    allocPlanes(rowlen, collen, &canvasP->planes);
+
+    clip_rect = picFrame;
 
     for (bi = blit_list; bi; bi = bi->next) {
-        blit(bi->srcRect, bi->srcBounds, bi->srcplane,
+        blit(bi->srcRect, bi->srcBounds, bi->srcplane, canvasP,
              bi->pixSize,
              bi->dstRect, picFrame, rowlen,
              bi->color_map,
@@ -1720,7 +1745,8 @@ do_blits(struct rgbPlanes * const planesP) {
 
 
 static void
-outputPpm(struct rgbPlanes const planes) {
+outputPpm(FILE *           const ofP,
+          struct rgbPlanes const planes) {
 
     unsigned int width;
     unsigned int height;
@@ -1736,7 +1762,7 @@ outputPpm(struct rgbPlanes const planes) {
     width  = picFrame.right  - picFrame.left;
     height = picFrame.bottom - picFrame.top;
 
-    ppm_writeppminit(stdout, width, height, PPM_MAXMAXVAL, 0);
+    ppm_writeppminit(ofP, width, height, PPM_MAXMAXVAL, 0);
     pixelrow = ppm_allocrow(width);
     srcCursor = 0;
     for (row = 0; row < height; ++row) {
@@ -1749,9 +1775,8 @@ outputPpm(struct rgbPlanes const planes) {
                 );
             ++srcCursor;
         }
-        ppm_writeppmrow(stdout, pixelrow, width, PPM_MAXMAXVAL, 0);
+        ppm_writeppmrow(ofP, pixelrow, width, PPM_MAXMAXVAL, 0);
     }
-    pm_close(stdout);
 }
 
 
@@ -1777,8 +1802,12 @@ get_op(int const version) {
 
 
 
+static drawFn Clip;
+
 static void
-Clip(int const version) {
+Clip(struct canvas * const canvasP,
+     int             const version) {
+
     word len;
 
     len = read_word();
@@ -1795,8 +1824,12 @@ Clip(int const version) {
 
 
 
+static drawFn OpColor;
+
 static void
-OpColor(int const version) {
+OpColor(struct canvas * const canvasP,
+        int             const version) {
+
     op_color.red = read_word();
     op_color.grn = read_word();
     op_color.blu = read_word();
@@ -1810,17 +1843,17 @@ read_pixmap(struct pixMap * const p) {
     stage = "getting pixMap header";
 
     read_rect(&p->Bounds);
-    p->version = read_word();
-    p->packType = read_word();
-    p->packSize = read_long();
-    p->hRes = read_long();
-    p->vRes = read_long();
-    p->pixelType = read_word();
-    p->pixelSize = read_word();
-    p->cmpCount = read_word();
-    p->cmpSize = read_word();
+    p->version    = read_word();
+    p->packType   = read_word();
+    p->packSize   = read_long();
+    p->hRes       = read_long();
+    p->vRes       = read_long();
+    p->pixelType  = read_word();
+    p->pixelSize  = read_word();
+    p->cmpCount   = read_word();
+    p->cmpSize    = read_word();
     p->planeBytes = read_long();
-    p->pmTable = read_long();
+    p->pmTable    = read_long();
     p->pmReserved = read_long();
 
     if (verbose) {
@@ -1828,6 +1861,8 @@ read_pixmap(struct pixMap * const p) {
         pm_message("pixelSize: %d", p->pixelSize);
         pm_message("cmpCount:  %d", p->cmpCount);
         pm_message("cmpSize:   %d", p->cmpSize);
+        if (verbose)
+            dumpRect("Bounds:", p->Bounds);
     }
 
     if (p->pixelType != 0)
@@ -2103,6 +2138,9 @@ expandRun(unsigned char * const block,
    returned.
 -----------------------------------------------------------------------------*/
     unsigned int const pkpixsize = bitsPerPixel == 16 ? 2 : 1;
+        /* The repetition unit size, in bytes.  The run consists of this many
+           bytes of packed data repeated the specified number of times.
+        */
 
     if (1 + pkpixsize > blockLimit)
         pm_error("PICT run block runs off the end of its line.  "
@@ -2118,14 +2156,28 @@ expandRun(unsigned char * const block,
         assert(block[0] & 0x80);  /* It's a run */
 
         if (verbose > 1)
-            pm_message("Block: run of %u pixels or plane samples", runLength);
-        
+            pm_message("Block: run of %u packed %u-byte units",
+                       runLength, pkpixsize);
+
         unpackBuf(&block[1], pkpixsize, bitsPerPixel,
                   &bytePixels, &expandedByteCount);
+
+        /* I assume in a legal PICT the run never has padding for the
+           case that the run is at the right edge of a row and the
+           remaining columns in the row don't fill whole bytes.
+           E.g. if there are 25 columns left in the row and 1 bit per
+           pixel, we won't see a run of 4 bytes and have to ignore the
+           last 7 pixels.  Instead, we'll see a run of 3 bytes
+           followed by a non-run block for the remaining pixel.
+
+           That is what I saw in a test image.
+        */
         
-        if (expandedByteCount > expandedSize)
+        if (expandedByteCount * runLength > expandedSize)
             pm_error("Invalid PICT image.  It contains a row with more pixels "
-                     "than the width of the image");
+                     "than the width of the rectangle containing it, "
+                     "even padded up to a "
+                     "multiple of 16 pixels.  Use -verbose to see details.");
         
         outputCursor = 0;
         for (i = 0; i < runLength; ++i) {
@@ -2150,9 +2202,23 @@ copyPixelGroup(unsigned char * const block,
                unsigned int *  const rasterBytesGeneratedP) {
 /*----------------------------------------------------------------------------
    Copy a group of pixels (the data says, "take the following N pixels").
+
+   Copy them (unpacked) from block block[] to dest[].
+
+   block[] self-describes its length.  Return that length as
+   *blockLengthP.
+
+   block[] contains at most 'blockLimit' valid array elements, so if
+   the length information in block[] indicates the block is larger
+   than that, the block is corrupt.
+
+   Return the number of pixels placed in dest[] as *rasterBytesGeneratedP.
+
+   The output array dest[] has 'destSize' elements of space.  Ignore
+   any pixels on the right that won't fit in that.
 -----------------------------------------------------------------------------*/
-    unsigned int const pkpixsize = bitsPerPixel == 16 ? 2 : 1;
-    unsigned int const groupLen = block[0] + 1;
+    unsigned int const pkpixsize   = bitsPerPixel == 16 ? 2 : 1;
+    unsigned int const groupLen    = block[0] + 1;
     unsigned int const blockLength = 1 + groupLen * pkpixsize;
 
     if (blockLength > blockLimit)
@@ -2163,22 +2229,35 @@ copyPixelGroup(unsigned char * const block,
         unsigned int i;
         unsigned char * bytePixels;  /* Points to static storage */
         unsigned int bytePixelLen;
+        unsigned int rasterBytesGenerated;
         
         assert(blockLimit >= 1);  /* block[0] exists */
         assert((block[0] & 0x80) == 0);  /* It's not a run */
         
         if (verbose > 1)
-            pm_message("Block: %u individual pixels or plane samples",
-                       groupLen);
+            pm_message("Block: %u explicit packed %u-byte units",
+                       groupLen, pkpixsize);
         
         unpackBuf(&block[1], groupLen * pkpixsize, bitsPerPixel,
                   &bytePixels, &bytePixelLen);
-        
-        for (i = 0; i < MIN(bytePixelLen, destSize); ++i)
+
+        /* It is normal for the above to return more pixels than there
+           are left in the row, because of padding.  E.g. there is one
+           pixel left in the row, at one bit per pixel.  But a block
+           contains full bytes, so it must contain at least 8 pixels.
+           7 of them are padding, which we should ignore.
+
+           BUT: I saw an image in which the block had _two_ data bytes
+           (16 pixels) when only 1 pixel remained in the row.  I don't
+           understand why, but ignoring the 15 extra seemed to work.
+        */
+        rasterBytesGenerated = MIN(bytePixelLen, destSize);
+
+        for (i = 0; i < rasterBytesGenerated; ++i)
             dest[i] = bytePixels[i];
         
         *blockLengthP = blockLength;
-        *rasterBytesGeneratedP = MIN(bytePixelLen, destSize);
+        *rasterBytesGeneratedP = rasterBytesGenerated;
     }
 }
 
@@ -2222,12 +2301,57 @@ static unsigned int const maxPixelBytesPerBlock = 1024;
 
 
 static void
+interpretCompressedLine(unsigned char * const linebuf,
+                        unsigned int    const linelen,
+                        unsigned char * const rowRaster,
+                        unsigned int    const rowSize,
+                        unsigned int    const bitsPerPixel) {
+/*----------------------------------------------------------------------------
+   linebuf[] contains 'linelen' bytes from the PICT image that represents
+   one row of the image, in compressed format.  Return the
+   uncompressed pixels of that row as rowRaster[].
+
+   rowRaster[] has 'rowSize' bytes of space.  Caller ensures that
+   linebuf[] does not contain more pixels than that, unless the PICT
+   image from which it comes is corrupt.
+-----------------------------------------------------------------------------*/
+    unsigned int lineCursor;
+        /* Cursor into linebuf[] -- the compressed data */
+    unsigned int rasterCursor;
+        /* Cursor into rowRaster[] -- the uncompressed data */
+
+    for (lineCursor = 0, rasterCursor = 0; lineCursor < linelen; ) {
+        unsigned int blockLength, rasterBytesGenerated;
+        
+        assert(lineCursor <= linelen);
+            
+        if (verbose > 1)
+            pm_message("At Byte %u of line, Column %u of row",
+                       lineCursor, rasterCursor);
+
+        interpretOneRasterBlock(
+            &linebuf[lineCursor], linelen - lineCursor,
+            bitsPerPixel,
+            &rowRaster[rasterCursor], rowSize - rasterCursor,
+            &blockLength, &rasterBytesGenerated);
+        
+        lineCursor += blockLength;
+        rasterCursor += rasterBytesGenerated;
+        assert(rasterCursor <= rowSize);
+    }
+    if (verbose > 1)
+        pm_message("Got %u pixels for row", rasterCursor);
+}
+
+
+
+static void
 unpackCompressedBits(FILE *          const ifP,
                      struct raster   const raster,
                      unsigned int    const rowBytes,
                      unsigned int    const bitsPerPixel) {
 /*----------------------------------------------------------------------------
-   Read the raster of on file *ifP and place it in 'raster'.
+   Read the raster on file *ifP and place it in 'raster'.
 
    The data in the file is compressed with run length encoding and
    possibly packed multiple pixels per byte as well.
@@ -2240,6 +2364,9 @@ unpackCompressedBits(FILE *          const ifP,
    *boundsP describes the rectangle.
 -----------------------------------------------------------------------------*/
     unsigned int const llsize = rowBytes > 200 ? 2 : 1;
+        /* Width in bytes of the field at the beginning of a line that tells
+           how long (in bytes) the line is.
+        */
     unsigned int rowOfRect;
     unsigned char * linebuf;
     unsigned int linebufSize;
@@ -2253,8 +2380,6 @@ unpackCompressedBits(FILE *          const ifP,
         unsigned char * const rowRaster =
             &raster.bytes[rowOfRect * raster.rowSize];
         unsigned int linelen;
-        unsigned int lineCursor;
-        unsigned int rasterCursor;
 
         if (llsize == 2)
             linelen = read_word();
@@ -2262,7 +2387,7 @@ unpackCompressedBits(FILE *          const ifP,
             linelen = read_byte();
 
         if (verbose > 1)
-            pm_message("linelen: %u", linelen);
+            pm_message("Row %u: %u-byte line", rowOfRect, linelen);
 
         if (linelen > linebufSize) {
             linebufSize = linelen;
@@ -2272,23 +2397,8 @@ unpackCompressedBits(FILE *          const ifP,
         }
         readBytes(ifP, linelen, linebuf);
 
-        for (lineCursor = 0, rasterCursor = 0; lineCursor < linelen; ) {
-            unsigned int blockLength, rasterBytesGenerated;
-
-            assert(lineCursor <= linelen);
-            
-            interpretOneRasterBlock(
-                &linebuf[lineCursor], linelen - lineCursor,
-                bitsPerPixel,
-                &rowRaster[rasterCursor], raster.rowSize - rasterCursor,
-                &blockLength, &rasterBytesGenerated);
-
-            lineCursor += blockLength;
-            rasterCursor += rasterBytesGenerated;
-            assert(rasterCursor <= raster.rowSize);
-        }
-        if (verbose > 1)
-            pm_message("row %u: got %u", rowOfRect, rasterCursor);
+        interpretCompressedLine(linebuf, linelen, rowRaster, raster.rowSize,
+                                bitsPerPixel);
     }
     free(linebuf);
 }
@@ -2390,21 +2500,39 @@ read_pattern(void) {
 
 /* these 3 do nothing but skip over their data! */
 
+static drawFn BkPixPat;
+
 static void
-BkPixPat(int const version) {
+BkPixPat(struct canvas * const canvasP,
+         int             const version) {
+
     read_pattern();
 }
 
+
+
+static drawFn PnPixPat;
+
 static void
-PnPixPat(int const version) {
+PnPixPat(struct canvas * const canvasP,
+         int             const version) {
+
     read_pattern();
 }
 
+
+
+static drawFn FillPixPat;
+
 static void
-FillPixPat(int const version) {
+FillPixPat(struct canvas * const canvasP,
+           int             const version) {
+
     read_pattern();
 }
 
+
+
 static void
 read_8x8_pattern(struct Pattern * const pat) {
     unsigned char buf[8];
@@ -2428,29 +2556,45 @@ read_8x8_pattern(struct Pattern * const pat) {
 
 
 
+static drawFn BkPat;
+
 static void 
-BkPat(int const version) {
+BkPat(struct canvas * const canvasP,
+      int             const version) {
+
     read_8x8_pattern(&bkpat);
 }
 
 
 
+static drawFn PnPat;
+
 static void 
-PnPat(int const version) {
+PnPat(struct canvas * const canvasP,
+      int             const version) {
+
     read_8x8_pattern(&pen_pat);
 }
 
 
 
+static drawFn FillPat;
+
 static void 
-FillPat(int const version) {
+FillPat(struct canvas * const canvasP,
+        int             const version) {
+
     read_8x8_pattern(&fillpat);
 }
 
 
 
+static drawFn PnSize;
+
 static void 
-PnSize(int const version) {
+PnSize(struct canvas * const canvasP,
+       int             const version) {
+
     pen_height = read_word();
     pen_width = read_word();
     if (verbose)
@@ -2459,8 +2603,11 @@ PnSize(int const version) {
 
 
 
+static drawFn PnSize;
+
 static void 
-PnMode(int const version) {
+PnMode(struct canvas * const canvasP,
+       int             const version) {
 
     pen_mode = read_word();
 
@@ -2484,8 +2631,12 @@ read_rgb(struct RGBColor * const rgb) {
 
 
 
+static drawFn RGBFgCol;
+
 static void 
-RGBFgCol(int const v) {
+RGBFgCol(struct canvas * const canvasP,
+         int             const version) {
+
     read_rgb(&foreground);
     if (verbose)
         pm_message("foreground now [%d,%d,%d]", 
@@ -2494,8 +2645,12 @@ RGBFgCol(int const v) {
 
 
 
+static drawFn RGBBkCol;
+
 static void 
-RGBBkCol(int const v) {
+RGBBkCol(struct canvas * const canvasP,
+         int             const version) {
+
     read_rgb(&background);
     if (verbose)
         pm_message("background now [%d,%d,%d]", 
@@ -2507,34 +2662,37 @@ RGBBkCol(int const v) {
 #define PIXEL_INDEX(x,y) ((y) - picFrame.top) * rowlen + (x) - picFrame.left
 
 static void 
-draw_pixel(int                const x, 
-           int                const y, 
+draw_pixel(struct canvas *   const canvasP,
+           int               const x, 
+           int               const y, 
            struct RGBColor * const clr, 
-           transfer_func            trf) {
-
-    int i;
-    struct RGBColor dst;
+           transfer_func           trf) {
 
     if (x < clip_rect.left || x >= clip_rect.right ||
-        y < clip_rect.top || y >= clip_rect.bottom)
-    {
-        return;
-    }
+        y < clip_rect.top || y >= clip_rect.bottom) {
+    } else {
+        unsigned int const i = PIXEL_INDEX(x, y);
+
+        struct RGBColor dst;
+
+        dst.red = canvasP->planes.red[i];
+        dst.grn = canvasP->planes.grn[i];
+        dst.blu = canvasP->planes.blu[i];
+ 
+        (*trf)(clr, &dst);
 
-    i = PIXEL_INDEX(x, y);
-    dst.red = red[i];
-    dst.grn = green[i];
-    dst.blu = blue[i];
-    (*trf)(clr, &dst);
-    red[i] = dst.red;
-    green[i] = dst.grn;
-    blue[i] = dst.blu;
+        canvasP->planes.red[i] = dst.red;
+        canvasP->planes.grn[i] = dst.grn;
+        canvasP->planes.blu[i] = dst.blu;
+    }
 }
 
 
 
 static void 
-draw_pen_rect(struct Rect * const r) {
+draw_pen_rect(struct canvas * const canvasP,
+              struct Rect *   const r) {
+
     int const rowadd = rowlen - (r->right - r->left);
 
     int i;
@@ -2545,16 +2703,18 @@ draw_pen_rect(struct Rect * const r) {
     
     for (y = r->top; y < r->bottom; y++) {
         for (x = r->left; x < r->right; x++) {
-            dst.red = red[i];
-            dst.grn = green[i];
-            dst.blu = blue[i];
+            dst.red = canvasP->planes.red[i];
+            dst.grn = canvasP->planes.grn[i];
+            dst.blu = canvasP->planes.blu[i];
+
             if (pen_pat.pix[(x & 7) + (y & 7) * 8])
                 (*pen_trf)(&black, &dst);
             else
                 (*pen_trf)(&white, &dst);
-            red[i] = dst.red;
-            green[i] = dst.grn;
-            blue[i] = dst.blu;
+
+            canvasP->planes.red[i] = dst.red;
+            canvasP->planes.grn[i] = dst.grn;
+            canvasP->planes.blu[i] = dst.blu;
 
             i++;
         }
@@ -2565,8 +2725,10 @@ draw_pen_rect(struct Rect * const r) {
 
 
 static void 
-draw_pen(int const x, 
-         int const y) {
+draw_pen(struct canvas * const canvasP,
+         int             const x, 
+         int             const y) {
+
     struct Rect penrect;
 
     penrect.left = x;
@@ -2576,7 +2738,7 @@ draw_pen(int const x,
 
     rectinter(penrect, clip_rect, &penrect);
 
-    draw_pen_rect(&penrect);
+    draw_pen_rect(canvasP, &penrect);
 }
 
 /*
@@ -2593,10 +2755,12 @@ draw_pen(int const x,
  * Paul Heckbert    3 Sep 85
  */
 static void 
-scan_line(short const x1, 
-          short const y1, 
-          short const x2, 
-          short const y2) {
+scan_line(struct canvas * const canvasP,
+          short           const x1, 
+          short           const y1, 
+          short           const x2, 
+          short           const y2) {
+
     int d, x, y, ax, ay, sx, sy, dx, dy;
 
     if (!(pen_width == 0 && pen_height == 0)) {
@@ -2609,7 +2773,7 @@ scan_line(short const x1,
         if (ax>ay) {        /* x dominant */
             d = ay-(ax>>1);
             for (;;) {
-                draw_pen(x, y);
+                draw_pen(canvasP, x, y);
                 if (x==x2) return;
                 if ((x > rowlen) && (sx > 0)) return;
                 if (d>=0) {
@@ -2623,7 +2787,7 @@ scan_line(short const x1,
         else {          /* y dominant */
             d = ax-(ay>>1);
             for (;;) {
-                draw_pen(x, y);
+                draw_pen(canvasP, x, y);
                 if (y==y2) return;
                 if ((y > collen) && (sy > 0)) return;
                 if (d>=0) {
@@ -2639,70 +2803,88 @@ scan_line(short const x1,
 
 
 
+static drawFn Line;
+
 static void 
-Line(int const v) {
+Line(struct canvas * const canvasP,
+     int             const version) {
+
   struct Point p1;
   read_point(&p1);
   read_point(&current);
   if (verbose)
     pm_message("(%d,%d) to (%d, %d)",
            p1.x,p1.y,current.x,current.y);
-  scan_line(p1.x,p1.y,current.x,current.y);
+  scan_line(canvasP, p1.x,p1.y,current.x,current.y);
 }
 
 
 
+static drawFn LineFrom;
+
 static void 
-LineFrom(int const v) {
-  struct Point p1;
-  read_point(&p1);
-  if (verbose)
-    pm_message("(%d,%d) to (%d, %d)",
-           current.x,current.y,p1.x,p1.y);
+LineFrom(struct canvas * const canvasP,
+         int             const version) {
 
-  if (!fullres)
-      scan_line(current.x,current.y,p1.x,p1.y);
+    struct Point p1;
+    read_point(&p1);
+    if (verbose)
+        pm_message("(%d,%d) to (%d, %d)", current.x, current.y, p1.x, p1.y);
+
+    if (!fullres)
+        scan_line(canvasP, current.x, current.y, p1.x, p1.y);
 
-  current.x = p1.x;
-  current.y = p1.y;
+    current.x = p1.x;
+    current.y = p1.y;
 }
 
 
 
+static drawFn ShortLine;
+
 static void 
-ShortLine(int const v) {
-  struct Point p1;
-  read_point(&p1);
-  read_short_point(&current);
-  if (verbose)
-    pm_message("(%d,%d) delta (%d, %d)",
-           p1.x,p1.y,current.x,current.y);
-  current.x += p1.x;
-  current.y += p1.y;
+ShortLine(struct canvas * const canvasP,
+          int             const version) {
 
-  if (!fullres)
-      scan_line(p1.x,p1.y,current.x,current.y);
+    struct Point p1;
+    read_point(&p1);
+    read_short_point(&current);
+    if (verbose)
+        pm_message("(%d,%d) delta (%d, %d)", p1.x, p1.y, current.x, current.y);
+    current.x += p1.x;
+    current.y += p1.y;
+    
+    if (!fullres)
+        scan_line(canvasP, p1.x, p1.y, current.x, current.y);
 }
 
 
 
+static drawFn ShortLineFrom;
+
 static void 
-ShortLineFrom(int const v) {
-  struct Point p1;
-  read_short_point(&p1);
-  if (verbose)
-    pm_message("(%d,%d) delta (%d, %d)",
-               current.x,current.y,p1.x,p1.y);
-  p1.x += current.x;
-  p1.y += current.y;
-  if (!fullres)
-      scan_line(current.x,current.y,p1.x,p1.y);
-  current.x = p1.x;
-  current.y = p1.y;
+ShortLineFrom(struct canvas * const canvasP,
+              int             const version) {
+
+    struct Point p1;
+    read_short_point(&p1);
+    if (verbose)
+        pm_message("(%d,%d) delta (%d, %d)",
+                   current.x,current.y,p1.x,p1.y);
+    p1.x += current.x;
+    p1.y += current.y;
+    if (!fullres)
+        scan_line(canvasP, current.x, current.y, p1.x, p1.y);
+    current.x = p1.x;
+    current.y = p1.y;
 }
 
+
+
 static void 
-do_paintRect(struct Rect const prect) {
+do_paintRect(struct canvas * const canvasP,
+             struct Rect     const prect) {
+
     struct Rect rect;
   
     if (fullres)
@@ -2713,28 +2895,37 @@ do_paintRect(struct Rect const prect) {
 
     rectinter(clip_rect, prect, &rect);
 
-    draw_pen_rect(&rect);
+    draw_pen_rect(canvasP, &rect);
 }
 
 
 
+static drawFn paintRect;
+
 static void 
-paintRect(int const v) {
+paintRect(struct canvas * const canvasP,
+          int             const version) {
+
     read_rect(&cur_rect);
-    do_paintRect(cur_rect);
+    do_paintRect(canvasP, cur_rect);
 }
 
 
 
+static drawFn paintSameRect;
+
 static void 
-paintSameRect(int const v) {
-    do_paintRect(cur_rect);
+paintSameRect(struct canvas * const canvasP,
+              int             const version) {
+
+    do_paintRect(canvasP, cur_rect);
 }
 
 
 
 static void 
-do_frameRect(struct Rect const rect) {
+do_frameRect(struct canvas * const canvasP,
+             struct Rect     const rect) {
     int x, y;
 
     if (fullres)
@@ -2747,29 +2938,37 @@ do_frameRect(struct Rect const rect) {
         return;
 
     for (x = rect.left; x <= rect.right - pen_width; x += pen_width) {
-        draw_pen(x, rect.top);
-        draw_pen(x, rect.bottom - pen_height);
+        draw_pen(canvasP, x, rect.top);
+        draw_pen(canvasP, x, rect.bottom - pen_height);
     }
 
     for (y = rect.top; y <= rect.bottom - pen_height ; y += pen_height) {
-        draw_pen(rect.left, y);
-        draw_pen(rect.right - pen_width, y);
+        draw_pen(canvasP, rect.left, y);
+        draw_pen(canvasP, rect.right - pen_width, y);
     }
 }
 
 
 
+static drawFn frameRect;
+
 static void 
-frameRect(int const v) {
+frameRect(struct canvas * const canvasP,
+          int             const version) {
+
     read_rect(&cur_rect);
-    do_frameRect(cur_rect);
+    do_frameRect(canvasP, cur_rect);
 }
 
 
 
+static drawFn frameSameRect;
+
 static void 
-frameSameRect(int const v) {
-    do_frameRect(cur_rect);
+frameSameRect(struct canvas * const canvasP,
+              int             const version) {
+
+    do_frameRect(canvasP, cur_rect);
 }
 
 
@@ -2810,8 +3009,10 @@ poly_sort(int const sort_index, struct Point points[]) {
 /* Watch out for the lack of error checking in the next two functions ... */
 
 static void 
-scan_poly(int          const np, 
-          struct Point       pts[]) {
+scan_poly(struct canvas * const canvasP,
+          int             const np, 
+          struct Point          pts[]) {
+
   int dx,dy,dxabs,dyabs,i,scan_index,j,k,px,py;
   int sdx,sdy,x,y,toggle,old_sdy,sy0;
 
@@ -2864,7 +3065,7 @@ scan_poly(int          const np,
         scan_index++;
       }
       px += sdx;
-      draw_pen(px, py);
+      draw_pen(canvasP, px, py);
     }
       }
     else
@@ -2880,7 +3081,7 @@ scan_poly(int          const np,
         old_sdy = sdy;
         if (sdy != 0) scan_index--;
       }
-      draw_pen(px,py);
+      draw_pen(canvasP, px,py);
       coord[scan_index].x = px;
       coord[scan_index].y = py;
       scan_index++;
@@ -2900,7 +3101,7 @@ scan_poly(int          const np,
     if ((coord[i].y == coord[i+1].y) && (toggle == 0))
       {
     for (j = coord[i].x; j <= coord[i+1].x; j++)
-      draw_pen(j, coord[i].y);
+      draw_pen(canvasP, j, coord[i].y);
     toggle = 1;
       }
     else
@@ -2909,9 +3110,13 @@ scan_poly(int          const np,
 }
   
 
+
+static drawFn paintPoly;
   
 static void 
-paintPoly(int const v) {
+paintPoly(struct canvas * const canvasP,
+          int             const version) {
+
   struct Rect bb;
   struct Point pts[100];
   int i, np = (read_word() - 10) >> 2;
@@ -2922,13 +3127,17 @@ paintPoly(int const v) {
 
   /* scan convert poly ... */
   if (!fullres)
-      scan_poly(np, pts);
+      scan_poly(canvasP, np, pts);
 }
 
 
 
+static drawFn PnLocHFrac;
+
 static void 
-PnLocHFrac(int const version) {
+PnLocHFrac(struct canvas * const canvasP,
+           int             const version) {
+
     word frac = read_word();
 
     if (verbose)
@@ -2937,8 +3146,12 @@ PnLocHFrac(int const version) {
 
 
 
+static drawFn TxMode;
+
 static void 
-TxMode(int const version) {
+TxMode(struct canvas * const canvasP,
+       int             const version) {
+
     text_mode = read_word();
 
     if (text_mode >= 8 && text_mode < 15)
@@ -2953,8 +3166,12 @@ TxMode(int const version) {
 
 
 
+static drawFn TxFont;
+
 static void 
-TxFont(int const version) {
+TxFont(struct canvas * const canvasP,
+       int             const version) {
+
     text_font = read_word();
     if (verbose)
         pm_message("text font %s", const_name(font_name, text_font));
@@ -2962,8 +3179,12 @@ TxFont(int const version) {
 
 
 
+static drawFn TxFace;
+
 static void 
-TxFace(int const version) {
+TxFace(struct canvas * const canvasP,
+       int             const version) {
+
     text_face = read_byte();
     if (verbose)
         pm_message("text face %d", text_face);
@@ -2971,8 +3192,12 @@ TxFace(int const version) {
 
 
 
+static drawFn TxSize;
+
 static void 
-TxSize(int const version) {
+TxSize(struct canvas * const canvasP,
+       int             const version) {
+
     text_size = read_word();
     if (verbose)
         pm_message("text size %d", text_size);
@@ -3062,8 +3287,10 @@ rotate(int * const x,
 
 
 static void
-do_ps_text(word const tx, 
-           word const ty) {
+do_ps_text(struct canvas * const canvasP,
+           word            const tx, 
+           word            const ty) {
+
     int len, width, i, w, h, x, y, rx, ry, o;
     byte str[256], ch;
     struct glyph* glyph;
@@ -3114,9 +3341,9 @@ do_ps_text(word const tx,
                 {
                     o = PIXEL_INDEX(rx, ry);
                     if (glyph->bmap[h * glyph->width + w]) {
-                        red[o] = foreground.red;
-                        green[o] = foreground.grn;
-                        blue[o] = foreground.blu;
+                        canvasP->planes.red[o] = foreground.red;
+                        canvasP->planes.grn[o] = foreground.grn;
+                        canvasP->planes.blu[o] = foreground.blu;
                     }
                 }
             }
@@ -3129,8 +3356,10 @@ do_ps_text(word const tx,
 
 
 static void
-do_text(word const startx, 
-        word const starty) {
+do_text(struct canvas *  const canvasP,
+        word             const startx, 
+        word             const starty) {
+
     if (fullres)
         skip_text();
     else {
@@ -3138,7 +3367,7 @@ do_text(word const startx,
             tfont = pbm_defaultfont("bdf");
 
         if (ps_text)
-            do_ps_text(startx, starty);
+            do_ps_text(canvasP, startx, starty);
         else {
             int len;
             word x, y;
@@ -3158,7 +3387,8 @@ do_text(word const startx,
                             struct RGBColor * const colorP = 
                                 glyph->bmap[h * glyph->width + w] ?
                                 &black : &white;
-                            draw_pixel(x + w + glyph->x, dy, colorP, text_trf);
+                            draw_pixel(canvasP,
+                                       x + w + glyph->x, dy, colorP, text_trf);
                         }
                     }
                     x += glyph->xadd;
@@ -3172,34 +3402,50 @@ do_text(word const startx,
 
 
 
+static drawFn LongText;
+
 static void
-LongText(int const version) {
+LongText(struct canvas * const canvasP,
+         int             const version) {
+
     struct Point p;
 
     read_point(&p);
-    do_text(p.x, p.y);
+
+    do_text(canvasP, p.x, p.y);
 }
 
 
 
+static drawFn DHText;
+
 static void
-DHText(int const version) {
+DHText(struct canvas * const canvasP,
+       int             const version) {
+
     current.x += read_byte();
-    do_text(current.x, current.y);
+    do_text(canvasP, current.x, current.y);
 }
 
 
 
+static drawFn DVText;
+
 static void
-DVText(int const version) {
+DVText(struct canvas * const canvasP,
+       int             const version) {
+
     current.y += read_byte();
-    do_text(current.x, current.y);
+    do_text(canvasP, current.x, current.y);
 }
 
 
 
+static drawFn DHDVText;
+
 static void
-DHDVText(int const version) {
+DHDVText(struct canvas * const canvasP,
+         int             const version) {
     byte dh, dv;
 
     dh = read_byte();
@@ -3210,7 +3456,7 @@ DHDVText(int const version) {
 
     current.x += dh;
     current.y += dv;
-    do_text(current.x, current.y);
+    do_text(canvasP, current.x, current.y);
 }
 
 
@@ -3220,8 +3466,9 @@ DHDVText(int const version) {
  */
 
 static void
-directBits(unsigned int const pictVersion, 
-           bool         const skipRegion) {
+directBits(struct canvas * const canvasP,
+           unsigned int    const pictVersion, 
+           bool            const skipRegion) {
 
     struct pixMap   p;
     struct Rect     srcRect;
@@ -3261,11 +3508,11 @@ directBits(unsigned int const pictVersion,
         pm_message("transfer mode = %s", const_name(transfer_name, mode));
 
     if (skipRegion) 
-        skip_poly_or_region(pictVersion);
+        skip_poly_or_region(canvasP, pictVersion);
 
     unpackbits(ifp, &p.Bounds, 0, p.pixelSize, &raster);
 
-    blit(srcRect, p.Bounds, raster, p.pixelSize,
+    blit(srcRect, p.Bounds, raster, canvasP, p.pixelSize,
          dstRect, picFrame, rowlen, NULL, mode);
 
     freeRaster(raster);
@@ -3276,26 +3523,33 @@ directBits(unsigned int const pictVersion,
 #define SKIP_REGION_TRUE TRUE
 #define SKIP_REGION_FALSE FALSE
 
+static drawFn DirectBitsRect;
+
 static void
-DirectBitsRect(int const version) {
+DirectBitsRect(struct canvas * const canvasP,
+               int             const version) {
 
-    directBits(version, SKIP_REGION_FALSE);
+    directBits(canvasP, version, SKIP_REGION_FALSE);
 }
 
 
 
+static drawFn DirectBitsRgn;
+
 static void
-DirectBitsRgn(int const version) {
+DirectBitsRgn(struct canvas * const canvasP,
+              int             const version) {
 
-    directBits(version, SKIP_REGION_TRUE);
+    directBits(canvasP, version, SKIP_REGION_TRUE);
 }
 
 
 
 static void
-do_pixmap(int  const version, 
-          word const rowBytes, 
-          int  const is_region) {
+do_pixmap(struct canvas * const canvasP,
+          int             const version, 
+          word            const rowBytes, 
+          int             const is_region) {
 /*----------------------------------------------------------------------------
    Do a paletted image.
 -----------------------------------------------------------------------------*/
@@ -3331,13 +3585,13 @@ do_pixmap(int  const version,
         pm_message("transfer mode = %s", const_name(transfer_name, mode));
 
     if (is_region)
-        skip_poly_or_region(version);
+        skip_poly_or_region(canvasP, version);
 
     stage = "unpacking rectangle";
 
     unpackbits(ifp, &p.Bounds, rowBytes, p.pixelSize, &raster);
 
-    blit(srcRect, p.Bounds, raster, 8,
+    blit(srcRect, p.Bounds, raster, canvasP, 8,
          dstRect, picFrame, rowlen, color_table, mode);
 
     free(color_table);
@@ -3347,17 +3601,24 @@ do_pixmap(int  const version,
 
 
 static void
-do_bitmap(int const version, 
-          int const rowBytes, 
-          int const is_region) {
+do_bitmap(FILE *          const ifP,
+          struct canvas * const canvasP,
+          int             const version, 
+          int             const rowBytes, 
+          int             const is_region) {
 /*----------------------------------------------------------------------------
    Do a bitmap.  That's one bit per pixel, 0 is white, 1 is black.
+
+   Read the raster from file 'ifP'.
 -----------------------------------------------------------------------------*/
     struct Rect Bounds;
     struct Rect srcRect;
     struct Rect dstRect;
     word mode;
     struct raster raster;
+        /* This raster contains padding on the right to make a multiple
+           of 16 pixels per row.
+        */
     static struct RGBColor color_table[] = { 
         {65535L, 65535L, 65535L}, {0, 0, 0} };
 
@@ -3369,13 +3630,13 @@ do_bitmap(int const version,
         pm_message("transfer mode = %s", const_name(transfer_name, mode));
 
     if (is_region)
-        skip_poly_or_region(version);
+        skip_poly_or_region(canvasP, version);
 
     stage = "unpacking rectangle";
 
-    unpackbits(ifp, &Bounds, rowBytes, 1, &raster);
+    unpackbits(ifP, &Bounds, rowBytes, 1, &raster);
 
-    blit(srcRect, Bounds, raster, 8,
+    blit(srcRect, Bounds, raster, canvasP, 8,
          dstRect, picFrame, rowlen, color_table, mode);
 
     freeRaster(raster);
@@ -3383,8 +3644,11 @@ do_bitmap(int const version,
 
 
 
+static drawFn BitsRect;
+
 static void
-BitsRect(int const version) {
+BitsRect(struct canvas * const canvasP,
+         int             const version) {
 
     word rowBytesWord;
     bool pixMap;
@@ -3396,16 +3660,19 @@ BitsRect(int const version) {
     interpretRowBytesWord(rowBytesWord, &pixMap, &rowBytes);
 
     if (pixMap)
-        do_pixmap(version, rowBytes, 0);
+        do_pixmap(canvasP, version, rowBytes, 0);
     else
-        do_bitmap(version, rowBytes, 0);
+        do_bitmap(ifp, canvasP, version, rowBytes, 0);
 }
 
 
 
-static void
-BitsRegion(int const version) {
+static drawFn BitsRegion;
 
+static void
+BitsRegion(struct canvas * const canvasP,
+           int             const version) {
+    
     word rowBytesWord;
     bool pixMap;
     unsigned int rowBytes;
@@ -3416,9 +3683,9 @@ BitsRegion(int const version) {
     interpretRowBytesWord(rowBytesWord, &pixMap, &rowBytes);
 
     if (pixMap)
-        do_pixmap(version, rowBytes, 1);
+        do_pixmap(canvasP, version, rowBytes, 1);
     else
-        do_bitmap(version, rowBytes, 1);
+        do_bitmap(ifp, canvasP, version, rowBytes, 1);
 }
 
 
@@ -3601,14 +3868,15 @@ static struct opdef const optable[] = {
 
 
 static void
-interpret_pict(void) {
+interpret_pict(FILE * const ofP) {
+
     byte ch;
     word picSize;
     word opcode;
     word len;
     unsigned int version;
     int i;
-    struct rgbPlanes planes;
+    struct canvas canvas;
 
     for (i = 0; i < 64; i++)
         pen_pat.pix[i] = bkpat.pix[i] = fillpat.pix[i] = 1;
@@ -3634,8 +3902,14 @@ interpret_pict(void) {
             picFrame.bottom - picFrame.top);
     }
 
-    if (!fullres)
-        allocPlanes(&planes);
+    if (!fullres) {
+        rowlen = picFrame.right  - picFrame.left;
+        collen = picFrame.bottom - picFrame.top;
+
+        allocPlanes(rowlen, collen, &canvas.planes);
+
+        clip_rect = picFrame;
+    }
 
     while ((ch = read_byte()) == 0)
         ;
@@ -3672,7 +3946,7 @@ interpret_pict(void) {
             }
 
             if (optable[opcode].impl != NULL)
-                (*optable[opcode].impl)(version);
+                (*optable[opcode].impl)(&canvas, version);
             else if (optable[opcode].len >= 0)
                 skip(optable[opcode].len);
             else switch (optable[opcode].len) {
@@ -3721,11 +3995,11 @@ interpret_pict(void) {
     }
     
     if (fullres)
-        do_blits(&planes);
+        do_blits(&canvas);
 
-    outputPpm(planes);
+    outputPpm(ofP, canvas.planes);
 
-    freePlanes(planes);
+    freePlanes(canvas.planes);
 }
 
 
@@ -3782,7 +4056,9 @@ main(int argc, char * argv[]) {
         skip(512);
     }
 
-    interpret_pict();
+    interpret_pict(stdout);
+
+    pm_close(stdout);
 
     return 0;
 }
diff --git a/doc/HISTORY b/doc/HISTORY
index 5bb4d539..5e905ef6 100644
--- a/doc/HISTORY
+++ b/doc/HISTORY
@@ -4,62 +4,104 @@ Netpbm.
 CHANGE HISTORY 
 --------------
 
-not yet  BJH  Release 10.37.06
+07.03.30 BJH  Release 10.38.0
+    
+              Add pamfixtrunc.
 
-              ppmtogif: handle case that map file and input file are not
-              the same depth, as was the case with the original ppmtogif.
-              Run the input through pnmremap.
+              pamtogif: Add -aspect.  Thanks
+              Prophet of the Way <afu@wta.att.ne.jp>.
 
-              picttoppm: Fix wild memory reference in all use cases.
+              pamditherbw: Add -atkinson.
 
-              picttoppm: Fix for multi-pixel-per-byte image in which
-              it says the image has a row that is too long (because of
-              padding).
+              pammixinterlace: Add -filter and fir and ffmpeg filters.
+              Thanks Bruce Guenter <bruce@untroubled.org>.
 
-              configure: fix choice of default library suffix.
+              pammixinterlace: Add -adaptive.
+              Thanks Bruce Guenter <bruce@untroubled.org>.
 
-07.03.23 BJH  Release 10.37.05
+              pambackground: recognize mid-row background.
 
-              pnmremap: fix incorrect output with map file deeper than
-              input file.
+              ppm3d: Change default offset to zero columns.
 
-              Pnmtopclxl: fix -feeder, -outtray options.  Thanks
-              "Eric K. Olson" <olson@mauicomputing.com>.
+              ppm3d: Add -color option.
 
-07.03.09 BJH  Release 10.37.04
+              ppm3d: Add -offset option as alternative to offset argument.
 
-              pamtogif: fix crash with -mapfile.
+              jpegtopnm: Add -repair option.
 
-              libnetpbm: fix crash with PBM images < 8 columns on MMX/SSE
-              machine.
+              giftopnm: Add -repair option.
+
+              xwdtopnm: use pm_drain() to catch some format
+              misinterpretations.
 
-              pamtoxvmini: fix maxval != 255 bugs.
+              pamtogif: Speed up for monochrome images.  Thanks
+              Prophet of the Way <afu@wta.att.ne.jp>.
+
+              pamtogif: Speed up for small images by using smaller
+              hash table (so smaller memory footprint).  Thanks
+              Prophet of the Way <afu@wta.att.ne.jp>.
 
+              libnetpbm: add pm_drain().
+              
               libnetpbm: shhopt: reject signed number as value for
               OPT_UINT option.
 
-07.02.21 BJH  Release 10.37.03
+              libnetpbm: in the "no such option" error message, say what
+              the valid options are.
 
-              pamtoxvmini: fix crash.
+              libnetpbm: Embellish "bad magic number" error message.
 
-              pamtogif: fail properly if image to wide or high for GIF.
+              pnmremap: fix incorrect output with map file deeper than
+              input file.
+
+              xwdtopnm: fix right edge padding for 24 bit per pixel,
+              32 bit per item images.
 
-07.01.15 BJH  Release 10.37.02
+              xwdtopnm: update assumptions about format for direct color
+              images to match an image we found.
+
+              pnmtopclxl: fix -feeder, -outtray options.  Thanks
+              "Eric K. Olson" <olson@mauicomputing.com>.
+
+              picttoppm: Fix wild memory reference in all use cases.
+
+              picttoppm: Fix for multi-pixel-per-byte image in which
+              it says the image has a row that is too long (because of
+              padding).
+
+              pamtogif: fix crash with -mapfile.
+
+              ppmtogif: handle case that map file and input file are not
+              the same depth, as was the case with the original ppmtogif.
+              Run the input through pnmremap.
+
+              pamtoxvmini: fix bug: produces garbage when maxval is
+              not 255.
+
+              pamtoxvmini: fix crash.
 
               libnetpbm: fix buffer overrun with PBM images < 8 columns.
 
+              libnetpbm: fix crash with PBM images < 8 columns on MMX/SSE
+              machine.
+
+              pamtogif: fail properly if image to wide or high for GIF.
+
               ppmdraw: fix crash with use of freed storage.  Thanks
               John Walker <kelvin@fourmilab.ch>.
 
+              libnetpbm: fix crash with PAM read as PNM.
+
               installnetpbm: use 2-argument open() for old Perl.
     
-06.12.31 BJH  Release 10.37.01
+              configure: fix choice of default library suffix.
 
-              libnetpbm: fix crash with PAM read as PNM.
+              configure: default to 'none' for Svgalib if it doesn't appear
+              to be installed.
 
-06.12.31 BJH  Release 10.37.00
+06.12.31 BJH  Release 10.37.0
 
-              add pambackground.  But doesn't find mid-row background yet.
+              Add pambackground.  But doesn't find mid-row background yet.
 
               pnmcrop, pamtopnm: work on multi-image stream.
               Thanks Erik Auerswald <auerswal@unix-ag.uni-kl.de>.
@@ -138,7 +180,7 @@ not yet  BJH  Release 10.37.06
 
               Build: Fix some compile failures with Irix IDO compiler.
 
-              Build: fix Darwin build.
+              Build: fix Darwin (Mac OS X) build.
 
               Configure: fix default for X library location.
 
@@ -3033,41 +3075,41 @@ Minor bug fixes and compatibility fixes are not documented in this file.
 
 PBM
 
-libpbm1.c	strstr() added to libpbm1.c.
-libpbm5.c	BDF font support added.
-pbmtext		BDF font support added.
-pbmto4425	New filter.
-pbmtoln03	Command line parsing changed to Pbmplus standard.
+libpbm1.c       strstr() added to libpbm1.c.
+libpbm5.c       BDF font functions added.
+pbmtext         Ability to use BDF fonts added.
+pbmto4425       New filter.
+pbmtoln03       Command line parsing changed to Pbmplus standard.
 
 
 PGM
 
-pgmnoise	New filter.
+pgmnoise        New filter.
 
 
 PPM
 
-picttoppm	Updated
-ppm3d		New facility.
-ppmchange	New filter.
-ppmdim		New filter.
-ppmflash	New filter.
-ppmmix		New filter.
-ppmntsc		New filter.
-ppmqvga		Option parsing changed to Pbmplus standard.
-ppmshift	New filter.
-ppmspread	New filter.
-ppmtoxpm	Prototypes added.
-xpmtoppm	Prototypes added.
-ilbmtoppm	Updated.
-ppmtoilbm	Updated.
+picttoppm       Updated
+ppm3d           New facility.
+ppmchange       New filter.
+ppmdim          New filter.
+ppmflash        New filter.
+ppmmix          New filter.
+ppmntsc         New filter.
+ppmqvga         Option parsing changed to Pbmplus standard.
+ppmshift        New filter.
+ppmspread       New filter.
+ppmtoxpm        Prototypes added.
+xpmtoppm        Prototypes added.
+ilbmtoppm       Updated.
+ppmtoilbm       Updated.
 
 
 PNM
 
-pnmtoddif	New filter.
-pnmhistmap	New facility.
-pnmtops		New option (-nocenter) added.
+pnmtoddif       New filter.
+pnmhistmap      New facility.
+pnmtops         New option (-nocenter) added.
 
 
 Functional changes to Netpbm since 7 December 1993.
@@ -3075,25 +3117,25 @@ Minor bug fixes and compatibility fixes are not documented in this file.
 
 PGM
 
-asciitopgm	New filter.
-fitstopgm	Replaced by fitstopnm.
-pgmtofits	Replaced by pnmtofits.
-pgmtopbm	Upgraded.
-pgmkernel	New filter.
+asciitopgm      New filter.
+fitstopgm       Replaced by fitstopnm.
+pgmtofits       Replaced by pnmtofits.
+pgmtopbm        Upgraded.
+pgmkernel       New filter.
 
 PPM
 
-ppmchange	Upgraded.
-xvminitoppm	New filter.
+ppmchange       Upgraded.
+xvminitoppm     New filter.
 
 PNM
 
-pnmalias	New filter.
-pnmtofits	Replacement for pgmtofits.
-fitstopnm	Replacement for fitstopgm.
-pnmtosgi	New filter.
-sgitopnm	New filter.
-pstopnm		New filter.
+pnmalias        New filter.
+pnmtofits       Replacement for pgmtofits.
+fitstopnm       Replacement for fitstopgm.
+pnmtosgi        New filter.
+sgitopnm        New filter.
+pstopnm         New filter.
 
 
 
@@ -3104,126 +3146,126 @@ The following is new in Netpbm (compared to Pbmplus):
 
 PBM
 
-pbmtext		BDF font support added.
+pbmtext         Ability to use BDF fonts added.
 
-pbmto4425	Display on an AT&T 4425 Ascii terminal.
+pbmto4425       Display on an AT&T 4425 Ascii terminal.
 
-pbmtoascii	A new improved version.
+pbmtoascii      A new improved version.
 
-pbmtoln03	Convert to DEC LN03+.
+pbmtoln03       Convert to DEC LN03+.
 
-pbmtolps	Fast PostScript creator.
+pbmtolps        Fast PostScript creator.
 
-pbmtopk		Conversion to/from a packed (PK) format font.
+pbmtopk         Conversion to/from a packed (PK) format font.
 pktopbm
 
-pbmclean	Flip isolated pixels.
+pbmclean        Flip isolated pixels.
 
-pbmpscale	Enlarge pbm image with edge smoothing.
+pbmpscale       Enlarge pbm image with edge smoothing.
 
 
 PGM
 
-asciitopgm	Convert an ascii image into pgm.
+asciitopgm      Convert an ascii image into pgm.
 
-pbmtopgm	Convert pbm to pgm by averaging areas.
+pbmtopgm        Convert pbm to pgm by averaging areas.
 
-rawtopgm	Handles input files without specification of the file size,
-		assuming the input image is quadratic. It also supports a
-		-tb (top bottom flip) option.
+rawtopgm        Handles input files without specification of the file size,
+                assuming the input image is quadratic. It also has a
+                -tb (top bottom flip) option.
 
-bioradtopgm	Conversion utility for files created by Biorad confocal
-		microscopes.
+bioradtopgm     Conversion utility for files created by Biorad confocal
+                microscopes.
 
-spottopgm	Convert SPOT satellite images to pgm.
+spottopgm       Convert SPOT satellite images to pgm.
 
-pgmkernel	Generate a convolution kernel.
+pgmkernel       Generate a convolution kernel.
 
-pgmnoise	Create a pgm file with random pixels.
+pgmnoise        Create a pgm file with random pixels.
 
 
 PPM
 
-bmptoppm	Conversion to/from windows bitmap format.
+bmptoppm        Conversion to/from windows bitmap format.
 ppmtobmp
 
-ppmtogif	Updated version.
-giftoppm	Removed (see giftopnm).
+ppmtogif        Updated version.
+giftoppm        Removed (see giftopnm).
 
-ppmtoilbm	Updated version.
+ppmtoilbm       Updated version.
 ilbmtoppm
 
-picttoppm	Updated version.
+picttoppm       Updated version.
 ppmtopict
 
-ppmtoxpm	Updated version, which supports xpm version 3.
+ppmtoxpm        Updated version, which understands xpm version 3.
 xpmtoppm
 
-ppmtomap	Extract all colors from a ppm file.
+ppmtomap        Extract all colors from a ppm file.
 
-ppmtomitsu	Convert to Mitsubishi S340-10 printer format.
+ppmtomitsu      Convert to Mitsubishi S340-10 printer format.
 
-xvminitoppm	Convert an XV thumbnail picture to ppm.
+xvminitoppm     Convert an XV thumbnail picture to ppm.
 
-ppmtoyuvsplit	Conversion to/from YUV triplets. (MPEG / JPEG).
+ppmtoyuvsplit   Conversion to/from YUV triplets. (MPEG / JPEG).
 yuvsplittoppm
 
-ppm3d		Create a red/blue stereo image.
+ppm3d           Create a red/blue stereo image.
 
-ppmbrighten	Change image saturation and value on an HSV map.
+ppmbrighten     Change image saturation and value on an HSV map.
 
-ppmchange	Change all pixels of one color to another in a portable pixmap
+ppmchange       Change all pixels of one color to another in a portable pixmap
 
-ppmdim		Dim a ppm file down to total blackness.
+ppmdim          Dim a ppm file down to total blackness.
 
-ppmdist		Simplistic grayscale assignment for machine generated
-		color images.
+ppmdist         Simplistic grayscale assignment for machine generated
+                color images.
 
-ppmflash	Brighten a picture up to complete white-out
+ppmflash        Brighten a picture up to complete white-out
 
-ppmmix		Blend together two portable pixmaps.
+ppmmix          Blend together two portable pixmaps.
 
-ppmnorm		Normalize the contrast in a portable pixmap.
+ppmnorm         Normalize the contrast in a portable pixmap.
 
-ppmntsc		Make a portable pixmap look like taken from an American TV.
+ppmntsc         Make a portable pixmap look like taken from an American TV.
 
-ppmqvga		Eight plane quantization.
+ppmqvga         Eight plane quantization.
 
-ppmshift	Shift lines of a portable pixmap left or right by a random amount.
+ppmshift        Shift lines of a portable pixmap left or right by a random amount.
 
-ppmspread	Displace a portable pixmap's pixels by a random amount.
+ppmspread       Displace a portable pixmap's pixels by a random amount.
 
-ppmtopjxl	Convert a ppm file into an HP PaintJet XL PCL file.
+ppmtopjxl       Convert a ppm file into an HP PaintJet XL PCL file.
 
 
 PNM
 
-pnmtops		New option (-nocenter) added.
+pnmtops         New option (-nocenter) added.
 
-pnmtofits	Replacement for pgmtofits/fitstopgm
+pnmtofits       Replacement for pgmtofits/fitstopgm
 fitstopnm
 
-pnmtosgi	Conversion to/from sgi image format.
+pnmtosgi        Conversion to/from sgi image format.
 sgitopnm
 
-pnmtosir	Conversion to/from Solitaire image recorder format.
+pnmtosir        Conversion to/from Solitaire image recorder format.
 sirtopnm
 
-giftopnm	Replaces giftoppm. Examines the input image and produces
-		a pbm, pgm, or ppm output.
+giftopnm        Replaces giftoppm. Examines the input image and produces
+                a pbm, pgm, or ppm output.
 
-pstopnm		Convert PostScript to pnm. Requires Ghostscript.
+pstopnm         Convert PostScript to pnm. Requires Ghostscript.
 
-zeisstopnm	Conversion utility for files created by Zeiss confocal
-		microscopes (the old standard).
+zeisstopnm      Conversion utility for files created by Zeiss confocal
+                microscopes (the old standard).
 
-pnmalias	Anti aliasing filter.
+pnmalias        Anti aliasing filter.
 
-pnmcomp		Composite two portable anymaps together.
+pnmcomp         Composite two portable anymaps together.
 
-pnmcrop		New options added.
+pnmcrop         New options added.
 
-pnmpad		Add borders to anymap.
+pnmpad          Add borders to anymap.
 
 
 LIBTIFF
@@ -3307,7 +3349,7 @@ Changes during the extended beta test period, starting on 15jan91:
     Added a -pseudodepth flag to pnmtoxwd.
     Updated tifftopnm for libtiff 2.4.
     Added many option flags to pnmtotiff.  (J.T. Conklin)
-    Added support for X11R5's new color specifiers rgb: and rgbi:.
+    Added recognition of X11R5's new color specifiers rgb: and rgbi:.
     Added pgmtexture.  (James Darrell McCauley)
     Added ppmtopj, pjtoppm, and ppmdither.  (Christos Zoulas)
     Added ppmtotga.  (Mark Shand)
@@ -3315,7 +3357,7 @@ Changes during the extended beta test period, starting on 15jan91:
     Added pbmtoatk and atktopbm.  (Bill Janssen)
     Added ppmtoyuv and yuvtoppm.  (Marc Boucher)
     Fixes to picttoppm.  (George Phillips)
-    Added 24-bit support to ilbmtoppm.  (Mark Thompson)
+    Added recognition of 24-bit images to ilbmtoppm.  (Mark Thompson)
 
 Changes since the X.V11R4 / comp.sources.misc distribution of 22nov89:
 
@@ -3356,7 +3398,7 @@ Changes since the X.V11R4 / comp.sources.misc distribution of 22nov89:
     Added -expand flag to pbmmask.
     Speedup to pnmflip - don't buffer if possible.
     Added color-name-to-value routine to ppm - uses X11's rgb.txt if present.
-    Updated Imakefile support to reflect X.V11R4.
+    Updated Imakefile function to reflect X.V11R4.
     Removed picttopbm.
     Improved pnmcut argument syntax so that negative coords work like pnmpaste.
     Added "magic" file, for use with the "file" program.
@@ -3380,7 +3422,7 @@ Changes since the X.V11R4 / comp.sources.misc distribution of 22nov89:
     Added -map flag to ppmquant - user-specifiable colormap.  Also, the
       Floyd-Steinberg error diffusion finally works right.
     Added -map flag to pgmtoppm.
-    Added DirectColor support to xwdtopnm and pnmtoxwd.
+    Added DirectColor capability to xwdtopnm and pnmtoxwd.
     Speedup to pgmtolj from Arthur David Olson: avoid sending whitespace.
     Fix to pbmtogo from Bo Thide': 2D compression now works.
 
@@ -3464,7 +3506,7 @@ Changes since the comp.sources.misc distribution of 31oct88:
 
 Changes since the X.V11R3 distribution of 31aug88:
 
-    The cbm format has been revised to support run-length encoding.
+    The cbm format has been revised to include run-length encoding.
     Pbmtops now does run-length encoding.
 
 Major changes since the X.V11R2 distribution of 28mar88:
diff --git a/editor/pambackground.c b/editor/pambackground.c
index 92e1fce2..8911adf0 100644
--- a/editor/pambackground.c
+++ b/editor/pambackground.c
@@ -111,17 +111,14 @@ selectBackground(struct pam * const pamP,
 
     tuple bg;  /* Reference to one of ul, ur, ll, lr */
 
-    if (pnm_tupleequal(pamP, ul, ur)) {
-        if (pnm_tupleequal(pamP, ll, ul))
-            bg = ul;
-        else if (pnm_tupleequal(pamP, lr, ul))
-            bg = ul;
-    } else if (pnm_tupleequal(pamP, ll, lr)) {
-        if (pnm_tupleequal(pamP, ul, ll))
-            bg = ll;
-        else if (pnm_tupleequal(pamP, ur, ll))
-            bg = ll;
-    } else {
+    if (pnm_tupleequal(pamP, ul, ur) &&
+        (pnm_tupleequal(pamP, ul, ll) ||
+         pnm_tupleequal(pamP, ul, lr)))
+        bg = ul;
+    else if (pnm_tupleequal(pamP, ll, lr) &&
+             pnm_tupleequal(pamP, lr, ul))
+        bg = ll;
+    else {
         /* No 3 corners are same color; look for 2 corners */
         if (pnm_tupleequal(pamP, ul, ur))  /* top edge */
             bg = ul;
@@ -144,9 +141,9 @@ selectBackground(struct pam * const pamP,
 
 
 static void
-computeBackground(struct pam * const pamP,
-                  bool         const verbose,
-                  tuple *      const bgColorP) {
+determineBackgroundColor(struct pam * const pamP,
+                         bool         const verbose,
+                         tuple *      const bgColorP) {
 /*----------------------------------------------------------------------------
    Determine what color is the background color of the image in the
    file represented by *pamP.
@@ -195,49 +192,276 @@ computeBackground(struct pam * const pamP,
 }
 
 
+static unsigned char const PT_UNKNOWN = 0;
+static unsigned char const PT_BG      = 1;
+static unsigned char const PT_FG      = 2;
+
+
+static unsigned char **
+newPi(unsigned int const width,
+      unsigned int const height) {
+
+    unsigned char ** pi;
+    unsigned int row;
+
+    MALLOCARRAY(pi, height);
+    if (pi == NULL)
+        pm_error("Out of memory allocating %u row pointers", height);
+    for (row = 0; row < height; ++row) {
+        MALLOCARRAY(pi[row], width);
+        if (pi[row] == NULL)
+            pm_error("Out of memory allocating row %u", row);
+    }
+    return pi;
+}
+
+
 
 static void
-computeOutputRow(struct pam * const inpamP,
-                 tuple *      const inputTuplerow,
-                 tuple        const backgroundColor,
-                 struct pam * const outpamP,
-                 tuple *      const outputTuplerow,
-                 tuple        const foreground,
-                 tuple        const background) {
+destroyPi(const unsigned char *const * const pi,
+          unsigned int                 const height) {
 
+    unsigned int row;
+
+    for (row = 0; row < height; ++row)
+        free((void*)pi[row]);
+
+    free((void*)pi);
+}
+
+
+
+static void
+initPi(unsigned char **   const pi,
+       const struct pam * const pamP,
+       tuple              const backgroundColor) {
+/*----------------------------------------------------------------------------
+  Set the initial info about every pixel in pi[][].
+
+  Read through the image in the file described by *inpamP and set each
+  pixel which is not of background color, and therefore obviously
+  foreground, to type PT_FG.  Set every other pixel to PT_UNKNOWN.
+-----------------------------------------------------------------------------*/
+    tuple * tuplerow;
+    unsigned int row;
+
+    tuplerow  = pnm_allocpamrow(pamP);
+
+    for (row = 0; row < pamP->height; ++row) {
+        unsigned int col;
+
+        pnm_readpamrow(pamP, tuplerow);
+
+        for (col = 0; col < pamP->width; ++col) {
+            pi[row][col] =
+                pnm_tupleequal(pamP, tuplerow[col], backgroundColor) ?
+                PT_UNKNOWN : PT_FG;
+        }
+    }
+    pnm_freepamrow(tuplerow);
+}
+
+
+
+static void
+setEdges(unsigned char ** const pi,
+         unsigned int     const width,
+         unsigned int     const height) {
+/*----------------------------------------------------------------------------
+  Do the four edges.
+
+  Anything of background color in an edge is background.  Pixel type
+  PT_UNKNOWN implies background color, so we change all PT_UNKNOWN pixels
+  to PT_BG.
+-----------------------------------------------------------------------------*/
+    unsigned int row, col;
+
+    for (col = 0; col < width; ++col) {
+        if (pi[0][col] == PT_UNKNOWN)
+            pi[0][col] = PT_BG;
+
+        if (pi[height-1][col] == PT_UNKNOWN)
+            pi[height-1][col] = PT_BG;
+    }
+    for (row = 0; row < height; ++row) {
+        if (pi[row][0] == PT_UNKNOWN)
+            pi[row][0] = PT_BG;
+
+        if (pi[row][width-1] == PT_UNKNOWN)
+            pi[row][width-1] = PT_BG;
+    }
+}
+
+
+
+static void
+expandBackgroundHoriz(unsigned char ** const pi,
+                      unsigned int     const width,
+                      unsigned int     const height,
+                      bool *           const expandedP) {
+/*----------------------------------------------------------------------------
+   In every row, expand the background rightward from any known background
+   pixel through all consecutive unknown pixels.
+
+   Then do the same thing leftward.
+
+   Iff we managed to expand the background at all, return *expandedP == TRUE.
+-----------------------------------------------------------------------------*/
+    unsigned int row;
+    bool expanded;
+
+    for (row = 1, expanded = FALSE; row < height-1; ++row) {
+        int col;
+
+        for (col = 1; col < width - 1; ++col) {
+            if (pi[row][col] == PT_UNKNOWN && pi[row][col-1] == PT_BG) {
+                expanded = TRUE;
+                /* Set the whole consecutive row of unknown to background */
+                for (; pi[row][col] == PT_UNKNOWN && col < width - 1; ++col)
+                    pi[row][col] = PT_BG;
+            }
+        }
+
+        for (col = width-2; col > 0; --col) {
+            if (pi[row][col] == PT_UNKNOWN && pi[row][col+1] == PT_BG) {
+                expanded = TRUE;
+                /* Set the whole consecutive row of unknown to background */
+                for (; pi[row][col] == PT_UNKNOWN && col > 0; --col)
+                    pi[row][col] = PT_BG;
+            }
+        }
+    }
+    *expandedP = expanded;
+}
+
+
+
+static void
+expandBackgroundVert(unsigned char ** const pi,
+                     unsigned int     const width,
+                     unsigned int     const height,
+                     bool *           const expandedP) {
+/*----------------------------------------------------------------------------
+   In every column, expand the background downward from any known background
+   pixel through all consecutive unknown pixels.
+
+   Then do the same thing upward.
+
+   Iff we managed to expand the background at all, return *expandedP == TRUE.
+-----------------------------------------------------------------------------*/
     unsigned int col;
-    unsigned int firstForegroundCol;
-        // Column number of first column, going from the left, that is
-        // not the background color.
-    unsigned int endForegroundCol;
-        // Column number of the last column, going from the right,
-        // that is the background color.
-
-    col = 0;
-    while (col < inpamP->width &&
-           pnm_tupleequal(inpamP, inputTuplerow[col], backgroundColor))
-        ++col;
-
-    firstForegroundCol = col;
-
-    col = inpamP->width;
-    while (col > firstForegroundCol && 
-           pnm_tupleequal(inpamP, inputTuplerow[col-1], backgroundColor))
-        --col;
-
-    endForegroundCol = col;
-
-    // If the row is all background, 'firstForegroundCol' and
-    // 'endForegroundCol' are both one past the right edge.
-            
-    for (col = 0; col < firstForegroundCol; ++col)
-        outputTuplerow[col] = background;
-
-    for (col = firstForegroundCol; col < endForegroundCol; ++col)
-        outputTuplerow[col] = foreground;
-
-    for (col = endForegroundCol; col < outpamP->width; ++col)
-        outputTuplerow[col] = background;
+    bool expanded;
+
+    for (col = 1, expanded = FALSE; col < height-1; ++col) {
+        int row;
+
+        for (row = 1; row < height - 1; ++row) {
+            if (pi[row][col] == PT_UNKNOWN && pi[row-1][col] == PT_BG) {
+                expanded = TRUE;
+                /* Set the whole consecutive col of unknown to background */
+                for (; pi[row][col] == PT_UNKNOWN && row < height - 1; ++row)
+                    pi[row][col] = PT_BG;
+            }
+        }
+
+        for (row = height-2; row > 0; --row) {
+            if (pi[row][col] == PT_UNKNOWN && pi[row+1][col] == PT_BG) {
+                expanded = TRUE;
+                /* Set the whole consecutive col of unknown to background */
+                for (; pi[row][col] == PT_UNKNOWN && row > 0; --row)
+                    pi[row][col] = PT_BG;
+            }
+        }
+    }
+    *expandedP = expanded;
+}
+
+
+
+static void
+findBackgroundPixels(struct pam *                   const inpamP,
+                     tuple                          const backgroundColor,
+                     bool                           const verbose,
+                     const unsigned char * const ** const piP) {
+/*----------------------------------------------------------------------------
+   Figure out which pixels of the image are background.  Read the
+   image from the input file described by *inpamP (positioned now to
+   the start of the raster) and generate the matrix *piP telling which
+   pixels are background and which are foreground, given that the
+   background color is 'backgroundColor'.
+
+   Note that it isn't as simple as finding which pixels are of color
+   'backgroundColor'.  They have to be part of a contiguous region that
+   touches one of the 4 edges of the image.
+
+   In the matrix we return, there is one element for each pixel in the
+   image, and it has value PT_BG or PT_FG.
+-----------------------------------------------------------------------------*/
+    unsigned char ** pi;
+    bool backgroundComplete;
+    unsigned int passes;
+
+    pi = newPi(inpamP->width, inpamP->height);
+
+    initPi(pi, inpamP, backgroundColor);
+
+    setEdges(pi, inpamP->width, inpamP->height);
+
+    backgroundComplete = FALSE;
+    passes = 0;
+    
+    while (!backgroundComplete) {
+        bool expandedHoriz, expandedVert;
+
+        expandBackgroundHoriz(pi, inpamP->width, inpamP->height,
+                              &expandedHoriz);
+    
+        expandBackgroundVert(pi, inpamP->width, inpamP->height,
+                             &expandedVert);
+
+        backgroundComplete = !expandedHoriz && !expandedVert;
+
+        ++passes;
+    }
+
+    if (verbose)
+        pm_message("Background found in %u passes", passes);
+
+    *piP = (const unsigned char * const *)pi;
+}
+
+                     
+
+static void
+writeOutput(const struct pam *            const inpamP,
+            const unsigned char * const * const pi) {
+
+    tuple black, white;
+    tuple * outputTuplerow;
+        /* This is not a normal tuplerow; it is just pointers to either
+           'black' or 'white'
+        */
+    unsigned int row;
+    struct pam outpam;
+
+    initOutpam(inpamP, &outpam);
+
+    allocateOutputPointerRow(outpam.width, &outputTuplerow);
+    pnm_createBlackTuple(&outpam, &black);
+    createWhiteTuple(&outpam, &white);
+
+    pnm_writepaminit(&outpam);
+
+    for (row = 0; row < outpam.height; ++row) {
+        unsigned int col;
+        for (col = 0; col < outpam.width; ++col)
+            outputTuplerow[col] = pi[row][col] == PT_BG ? white : black;
+
+        pnm_writepamrow(&outpam, outputTuplerow);
+    }
+    pnm_freepamtuple(white);
+    pnm_freepamtuple(black);
+    free(outputTuplerow);
 }
 
 
@@ -247,15 +471,10 @@ main(int argc, char *argv[]) {
 
     struct cmdlineInfo cmdline;
     struct pam inpam;
-    struct pam outpam;
     FILE * ifP;
-    tuple * inputTuplerow;
-    tuple * outputTuplerow;
-        // Not a regular tuple row -- just pointer array
-    unsigned int row;
     pm_filepos rasterpos;
-    tuple black, white;
     tuple backgroundColor;
+    const unsigned char * const * pi;
     
     pnm_init(&argc, argv);
 
@@ -267,36 +486,19 @@ main(int argc, char *argv[]) {
 
     pm_tell2(ifP, &rasterpos, sizeof(rasterpos));
 
-    computeBackground(&inpam, cmdline.verbose, &backgroundColor);
-
-    initOutpam(&inpam, &outpam);
-
-    inputTuplerow  = pnm_allocpamrow(&inpam);
-
-    allocateOutputPointerRow(outpam.width, &outputTuplerow);
-    pnm_createBlackTuple(&outpam, &black);
-    createWhiteTuple(&outpam, &white);
-
-    pnm_writepaminit(&outpam);
+    determineBackgroundColor(&inpam, cmdline.verbose, &backgroundColor);
 
     pm_seek2(ifP, &rasterpos, sizeof(rasterpos));
 
-    for (row = 0; row < outpam.height; ++row) {
-        pnm_readpamrow(&inpam, inputTuplerow);
+    findBackgroundPixels(&inpam, backgroundColor, cmdline.verbose, &pi);
 
-        computeOutputRow(&inpam, inputTuplerow, backgroundColor,
-                         &outpam, outputTuplerow, black, white);
+    writeOutput(&inpam, pi);
 
-        pnm_writepamrow(&outpam, outputTuplerow);
-    }
+    destroyPi(pi, inpam.height);
 
     pm_close(ifP);
 
-    pnm_freepamrow(inputTuplerow);
-    free(outputTuplerow);
     pnm_freepamtuple(backgroundColor);
-    pnm_freepamtuple(white);
-    pnm_freepamtuple(black);
     
     return 0;
 }
diff --git a/editor/pamcut.c b/editor/pamcut.c
index d5de45fb..c6fc0cc1 100644
--- a/editor/pamcut.c
+++ b/editor/pamcut.c
@@ -94,8 +94,10 @@ parseCommandLine(int argc, char ** const argv,
         pm_error("-height may not be negative.");
 
     if ((argc-1) != 0 && (argc-1) != 1 && (argc-1) != 4 && (argc-1) != 5)
-        pm_error("Wrong number of arguments.  "
-                 "Must be 0, 1, 4, or 5 arguments.");
+        pm_error("Wrong number of arguments: %u.  The only argument in "
+                 "the preferred syntax is an optional input file name.  "
+                 "In older syntax, there are also forms with 4 and 5 "
+                 "arguments.", argc-1);
 
     switch (argc-1) {
     case 0:
diff --git a/editor/pamditherbw.c b/editor/pamditherbw.c
index 931b475f..49e78a4e 100644
--- a/editor/pamditherbw.c
+++ b/editor/pamditherbw.c
@@ -19,7 +19,12 @@
 #include "shhopt.h"
 #include "pm_gamma.h"
 
-enum halftone {QT_FS, QT_THRESH, QT_DITHER8, QT_CLUSTER, QT_HILBERT};
+enum halftone {QT_FS,
+               QT_ATKINSON,
+               QT_THRESH,
+               QT_DITHER8,
+               QT_CLUSTER,
+               QT_HILBERT};
 
 enum ditherType {DT_REGULAR, DT_CLUSTER};
 
@@ -53,7 +58,7 @@ parseCommandLine(int argc, char ** argv,
     optStruct3 opt;
 
     unsigned int option_def_index;
-    unsigned int floydOpt, hilbertOpt, thresholdOpt, dither8Opt,
+    unsigned int floydOpt, atkinsonOpt, hilbertOpt, thresholdOpt, dither8Opt,
         cluster3Opt, cluster4Opt, cluster8Opt;
     unsigned int valueSpec, clumpSpec;
 
@@ -62,6 +67,7 @@ parseCommandLine(int argc, char ** argv,
     option_def_index = 0;   /* incremented by OPTENTRY */
     OPTENT3(0, "floyd",     OPT_FLAG,  NULL, &floydOpt,     0);
     OPTENT3(0, "fs",        OPT_FLAG,  NULL, &floydOpt,     0);
+    OPTENT3(0, "atkinson",  OPT_FLAG,  NULL, &atkinsonOpt,     0);
     OPTENT3(0, "threshold", OPT_FLAG,  NULL, &thresholdOpt, 0);
     OPTENT3(0, "hilbert",   OPT_FLAG,  NULL, &hilbertOpt,   0);
     OPTENT3(0, "dither8",   OPT_FLAG,  NULL, &dither8Opt,   0);
@@ -84,15 +90,17 @@ parseCommandLine(int argc, char ** argv,
     optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
-    if (floydOpt + thresholdOpt + hilbertOpt + dither8Opt + 
+    if (floydOpt + atkinsonOpt + thresholdOpt + hilbertOpt + dither8Opt + 
         cluster3Opt + cluster4Opt + cluster8Opt == 0)
         cmdlineP->halftone = QT_FS;
-    else if (floydOpt + thresholdOpt + dither8Opt + 
+    else if (floydOpt + atkinsonOpt + thresholdOpt + dither8Opt + 
         cluster3Opt + cluster4Opt + cluster8Opt > 1)
-        pm_error("No cannot specify more than one halftoning type");
+        pm_error("Cannot specify more than one halftoning type");
     else {
         if (floydOpt)
             cmdlineP->halftone = QT_FS;
+        else if (atkinsonOpt)
+            cmdlineP->halftone = QT_ATKINSON;
         else if (thresholdOpt)
             cmdlineP->halftone = QT_THRESH;
         else if (hilbertOpt) {
@@ -389,8 +397,15 @@ struct converter {
 
 struct fsState {
     float * thiserr;
+        /* thiserr[N] is the power from previous pixels to include in 
+           future column N of the current row.
+        */
     float * nexterr;
+        /* nexterr[N] is the power from previous pixels to include in 
+           future column N of the next row.
+        */
     bool fs_forward;
+        /* We're going forward (left to right) through the current row */
     samplen threshval;
         /* The power value we consider to be half white */
 };
@@ -426,32 +441,32 @@ fsConvertRow(struct converter * const converterP,
 
         sum = pm_ungamma709(grayrow[col][0]) + thiserr[col + 1];
         if (sum >= stateP->threshval) {
-            /* We've accumulated enough light to justify a white output
-               pixel.
+            /* We've accumulated enough light (power) to justify a
+               white output pixel.
             */
             bitrow[col][0] = PAM_BW_WHITE;
-            /* Remove from sum the power of the white output pixel */
+            /* Remove from sum the power of this white output pixel */
             sum -= 2*stateP->threshval;
         } else
             bitrow[col][0] = PAM_BLACK;
         
-        /* Forward the power from current input pixel and the power
-           forwarded from previous input pixels to the current pixel,
-           to future output pixels, but subtract out any power we put
-           into the current output pixel.  
+        /* Forward to future output pixels the power from current
+           input pixel and the power forwarded from previous input
+           pixels to the current pixel, less any power we put into the
+           current output pixel.
         */
         if (stateP->fs_forward) {
             thiserr[col + 2] += (sum * 7) / 16;
             nexterr[col    ] += (sum * 3) / 16;
             nexterr[col + 1] += (sum * 5) / 16;
-            nexterr[col + 2] += (sum    ) / 16;
+            nexterr[col + 2] += (sum * 1) / 16;
             
             ++col;
         } else {
             thiserr[col    ] += (sum * 7) / 16;
             nexterr[col + 2] += (sum * 3) / 16;
             nexterr[col + 1] += (sum * 5) / 16;
-            nexterr[col    ] += (sum    ) / 16;
+            nexterr[col    ] += (sum * 1) / 16;
             
             --col;
         }
@@ -466,7 +481,13 @@ fsConvertRow(struct converter * const converterP,
 
 static void
 fsDestroy(struct converter * const converterP) {
-    free(converterP->stateP);
+
+    struct fsState * const stateP = converterP->stateP;
+
+    free(stateP->thiserr);
+    free(stateP->nexterr);
+
+    free(stateP);
 }
 
 
@@ -507,6 +528,144 @@ createFsConverter(struct pam * const graypamP,
 
 
 
+struct atkinsonState {
+    float * error[3];
+        /* error[R][C] is the power from previous pixels to include
+           in column C of the Rth row down from the current row
+           (0th row is the current row).
+
+           No error propagates down more than two rows.
+
+           For R == 0, C is a column we haven't done yet.
+        */
+    samplen threshval;
+        /* The power value we consider to be half white */
+};
+
+
+static void
+moveAtkinsonErrorWindowDown(struct converter * const converterP) {
+                            
+    struct atkinsonState * const stateP = converterP->stateP;
+
+    float * const oldError0 = stateP->error[0];
+
+    unsigned int relRow;
+    unsigned int col;
+
+    for (relRow = 0; relRow < 2; ++relRow)
+        stateP->error[relRow] = stateP->error[relRow+1];
+
+    for (col = 0; col < converterP->cols + 2; ++col)
+        oldError0[col] = 0.0;
+
+    stateP->error[2] = oldError0;
+}
+
+
+
+static void
+atkinsonConvertRow(struct converter * const converterP,
+                   unsigned int       const row,
+                   tuplen                   grayrow[],
+                   tuple                    bitrow[]) {
+
+    /* See http://www.tinrocket.com/projects/programming/graphics/00158/
+       for a description of the Atkinson algorithm
+    */
+
+    struct atkinsonState * const stateP = converterP->stateP;
+
+    samplen ** const error = stateP->error;
+
+    unsigned int col;
+
+    for (col = 0; col < converterP->cols; ++col) {
+        samplen sum;
+
+        sum = pm_ungamma709(grayrow[col][0]) + error[0][col + 1];
+        if (sum >= stateP->threshval) {
+            /* We've accumulated enough light (power) to justify a
+               white output pixel.
+            */
+            bitrow[col][0] = PAM_BW_WHITE;
+            /* Remove from sum the power of this white output pixel */
+            sum -= 2*stateP->threshval;
+        } else
+            bitrow[col][0] = PAM_BLACK;
+        
+        /* Forward to future output pixels 3/4 of the power from current
+           input pixel and the power forwarded from previous input
+           pixels to the current pixel, less any power we put into the
+           current output pixel.
+        */
+        error[0][col+1] += sum/8;
+        error[0][col+2] += sum/8;
+        if (col > 0)
+            error[1][col-1] += sum/8;
+        error[1][col  ] += sum/8;
+        error[1][col+1] += sum/8;
+        error[2][col  ] += sum/8;
+    }
+    
+    moveAtkinsonErrorWindowDown(converterP);
+}
+
+
+
+static void
+atkinsonDestroy(struct converter * const converterP) {
+
+    struct atkinsonState * const stateP = converterP->stateP;
+
+    unsigned int relRow;
+
+    for (relRow = 0; relRow < 3; ++relRow)
+        free(stateP->error[relRow]);
+
+    free(stateP);
+}
+
+
+
+static struct converter
+createAtkinsonConverter(struct pam * const graypamP,
+                        float        const threshFraction) {
+
+    struct atkinsonState * stateP;
+    struct converter converter;
+    unsigned int relRow;
+    
+    converter.cols       = graypamP->width;
+    converter.convertRow = &atkinsonConvertRow;
+    converter.destroy    = &atkinsonDestroy;
+
+    MALLOCVAR_NOFAIL(stateP);
+
+    for (relRow = 0; relRow < 3; ++relRow)
+        MALLOCARRAY_NOFAIL(stateP->error[relRow], graypamP->width + 2);
+
+    srand(pm_randseed());
+
+    {
+        /* (random errors in [-1/8 .. 1/8]) */
+        unsigned int col;
+        for (col = 0; col < graypamP->width + 2; ++col) {
+            stateP->error[0][col] = ((float)rand()/RAND_MAX - 0.5) / 4;
+            stateP->error[1][col] = 0.0;
+            stateP->error[2][col] = 0.0;
+        }
+    }
+
+    stateP->threshval  = threshFraction;
+
+    converter.stateP = stateP;
+
+    return converter;
+}
+
+
+
 struct threshState {
     samplen threshval;
 };
@@ -703,6 +862,9 @@ main(int argc, char *argv[]) {
         case QT_FS:
             converter = createFsConverter(&graypam, cmdline.threshval);
             break;
+        case QT_ATKINSON:
+            converter = createAtkinsonConverter(&graypam, cmdline.threshval);
+            break;
         case QT_THRESH:
             converter = createThreshConverter(&graypam, cmdline.threshval);
             break;
diff --git a/editor/pamflip.c b/editor/pamflip.c
index 59b60b56..0c2aabb4 100644
--- a/editor/pamflip.c
+++ b/editor/pamflip.c
@@ -171,7 +171,7 @@ parseCommandLine(int argc, char ** const argv,
    Note that the file spec array we return is stored in the storage that
    was passed to us as the argv array.
 -----------------------------------------------------------------------------*/
-    optEntry *option_def = malloc(100*sizeof(optEntry));
+    optEntry * option_def;
         /* Instructions to OptParseOptions3 on how to parse our options.
          */
     optStruct3 opt;
@@ -183,6 +183,8 @@ parseCommandLine(int argc, char ** const argv,
     unsigned int memsizeOpt;
     const char *xformOpt;
 
+    MALLOCARRAY(option_def, 100);
+
     option_def_index = 0;   /* incremented by OPTENTRY */
     OPTENT3(0, "lr",        OPT_FLAG,    NULL, &lr,      0);
     OPTENT3(0, "leftright", OPT_FLAG,    NULL, &lr,      0);
@@ -860,7 +862,7 @@ main(int argc, char * argv[]) {
     struct cmdlineInfo cmdline;
     struct pam inpam;
     struct pam outpam;
-    FILE* ifP;
+    FILE * ifP;
     struct xformMatrix xform;
 
     pnm_init(&argc, argv);
diff --git a/editor/pammixinterlace.c b/editor/pammixinterlace.c
index 1421c7a2..579a8092 100644
--- a/editor/pammixinterlace.c
+++ b/editor/pammixinterlace.c
@@ -3,7 +3,7 @@
 *******************************************************************************
   De-interlace an image by merging adjacent rows.
    
-  Copyright (C) 2005 Bruce Guenter, FutureQuest, Inc.
+  Copyright (C) 2007 Bruce Guenter, FutureQuest, Inc.
 
   Permission to use, copy, modify, and distribute this software and its
   documentation for any purpose and without fee is hereby granted,
@@ -14,32 +14,184 @@
 
 ******************************************************************************/
 
-#include "pam.h"
-#include "shhopt.h"
+#include <string.h>
+
 #include "mallocvar.h"
+#include "nstring.h"
+#include "shhopt.h"
+#include "pam.h"
+
+
+
+static sample
+clamp(sample const val,
+      sample const maxval) {
+
+    return val < 0 ? 0 : val > maxval ? maxval : val;
+}
+
+
+
+static bool
+distant(long const above,
+        long const mid,
+        long const below) {
+
+    return abs(mid - (above + below) / 2) > abs(above - below);
+}
+
+
+
+static void
+filterLinearBlend(tuple *      const outputrow,
+                  tuple **     const tuplerowWindow,
+                  unsigned int const width,
+                  unsigned int const depth,
+                  bool         const adaptive,
+                  sample       const maxval) {
+
+    unsigned int col;
+
+    for (col = 0; col < width; ++col) {
+        unsigned int plane;
+
+        for (plane = 0; plane < depth; ++plane) {
+            long const above = tuplerowWindow[0][col][plane];
+            long const mid   = tuplerowWindow[1][col][plane];
+            long const below = tuplerowWindow[2][col][plane];
+
+            sample out;
+
+            if (!adaptive || distant(above, mid, below))
+                out = (above + mid * 2 + below) / 4;
+            else
+                out = mid;
+            
+            outputrow[col][plane] = out;
+        }
+    }
+}
+
+
+
+static void
+filterFfmpeg(tuple *      const outputrow,
+             tuple **     const tuplerowWindow,
+             unsigned int const width,
+             unsigned int const depth,
+             bool         const adaptive,
+             sample       const maxval) {
+
+    unsigned int col;
+    
+    for (col = 0; col < width; ++col) {
+        unsigned int plane;
+        
+        for (plane = 0; plane < depth; ++plane) {
+            long const above = tuplerowWindow[1][col][plane];
+            long const mid   = tuplerowWindow[2][col][plane];
+            long const below = tuplerowWindow[3][col][plane];
+
+            sample out;
+            
+            if (!adaptive || distant(above, mid, below)) {
+                long const a = (- (long)tuplerowWindow[0][col][plane]
+                                + above * 4
+                                + mid * 2
+                                + below * 4
+                                - (long)tuplerowWindow[4][col][plane]) / 8;
+                out = clamp(a, maxval);
+            } else
+                out = mid;
+
+            outputrow[col][plane] = out;
+        }
+    }
+}
+
+
+
+static void
+filterFIR(tuple *      const outputrow,
+          tuple **     const tuplerowWindow,
+          unsigned int const width,
+          unsigned int const depth,
+          bool         const adaptive,
+          sample       const maxval) {
+
+    unsigned int col;
+
+    for (col = 0; col < width; ++col) {
+        unsigned int plane;
+
+        for (plane = 0; plane < depth; ++plane) {
+
+            long const above = tuplerowWindow[1][col][plane];
+            long const mid   = tuplerowWindow[2][col][plane];
+            long const below = tuplerowWindow[3][col][plane];
+
+            sample out;
+
+            if (!adaptive || distant(above, mid, below)) {
+                long const a = (- (long)tuplerowWindow[0][col][plane]
+                                + above * 2
+                                + mid * 6
+                                + below * 2
+                                - (long)tuplerowWindow[4][col][plane]) / 8;
+                out = clamp(a, maxval);
+            } else
+                out = mid;
+            
+            outputrow[col][plane] = out;
+        }
+    }
+}
+
+
+
+struct filter {
+    const char * name;          /* The command-line name of the filter */
+    unsigned int rows;   /* The number of rows the filter operates on */
+    void (*filter)(tuple *, tuple **,
+                   unsigned int width, unsigned int depth,
+                   bool adaptive, sample maxval);
+};
+
+static struct filter filters[] = {
+    { "fir", 5, filterFIR }, /* FIR is cleanest and default filter */
+    { "ffmpeg", 5, filterFfmpeg },
+    { "linear", 3, filterLinearBlend },
+};
 
 struct cmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
-    const char *inputFilespec;  /* Filespecs of input files */
+    const char * inputFileName;  /* Names of input files */
+    struct filter * filterP;
+    unsigned int adaptive;
 };
 
 
+
 static void
 parseCommandLine(int argc, 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.
 -----------------------------------------------------------------------------*/
     optStruct3 opt;  /* set by OPTENT3 */
-    optEntry *option_def;
+    optEntry * option_def;
     unsigned int option_def_index;
+    const char * filterName;
+    unsigned int filterSpec;
 
     MALLOCARRAY_NOFAIL(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "filter",   OPT_STRING, &filterName, &filterSpec, 0);
+    OPTENT3(0, "adaptive", OPT_FLAG, NULL, &cmdlineP->adaptive, 0);
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
@@ -48,10 +200,25 @@ parseCommandLine(int argc, char ** argv,
     optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
     /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
+    if (!filterSpec)
+        cmdlineP->filterP = &filters[0];
+    else {
+        unsigned int i;
+        cmdlineP->filterP = NULL;
+        for (i = 0; i < sizeof filters / sizeof(struct filter); ++i) {
+            if (STRCASEEQ(filterName, filters[i].name)) {
+                cmdlineP->filterP = &filters[i];
+                break;
+            }
+        }
+        if (!cmdlineP->filterP)
+            pm_error("The filter name '%s' is not known.", filterName);
+    }
+    
     if (argc-1 < 1)
-        cmdlineP->inputFilespec = "-";
+        cmdlineP->inputFileName = "-";
     else if (argc-1 == 1)
-        cmdlineP->inputFilespec = argv[1];
+        cmdlineP->inputFileName = argv[1];
     else
         pm_error("You specified too many arguments (%d).  The only "
                  "argument is the optional input file specification.",
@@ -62,40 +229,44 @@ parseCommandLine(int argc, char ** argv,
 
 static void
 allocateRowWindowBuffer(struct pam * const pamP,
-                        tuple **     const tuplerow) {
+                        tuple **     const tuplerowWindow,
+                        unsigned int const rows) {
 
     unsigned int row;
 
-    for (row = 0; row < 3; ++row)
-        tuplerow[row] = pnm_allocpamrow(pamP);
+    for (row = 0; row < rows; ++row)
+        tuplerowWindow[row] = pnm_allocpamrow(pamP);
 }
 
 
 
 static void
-freeRowWindowBuffer(tuple ** const tuplerow) {
+freeRowWindowBuffer(tuple **     const tuplerowWindow,
+                    unsigned int const rows) {
 
     unsigned int row;
 
-    for (row = 0; row < 3; ++row)
-        pnm_freepamrow(tuplerow[row]);
-
+    for (row = 0; row < rows; ++row)
+        pnm_freepamrow(tuplerowWindow[row]);
 }
 
 
 
 static void
-slideWindowDown(tuple ** const tuplerow) {
+slideWindowDown(tuple **     const tuplerowWindow,
+                unsigned int const rows) {
 /*----------------------------------------------------------------------------
-  Slide the 3-line tuple row window tuplerow[] down one row by moving
+  Slide the rows-line tuple row window tuplerowWindow[] down one by moving
   pointers.
-
-  tuplerow[2] ends up an uninitialized buffer.
 -----------------------------------------------------------------------------*/
-    tuple * const oldrow0 = tuplerow[0];
-    tuplerow[0] = tuplerow[1];
-    tuplerow[1] = tuplerow[2];
-    tuplerow[2] = oldrow0;
+    tuple * const oldrow0 = tuplerowWindow[0];
+
+    unsigned int i;
+
+    for (i = 0; i < rows-1; ++i)
+        tuplerowWindow[i] = tuplerowWindow[i+1];
+
+    tuplerowWindow[i] = oldrow0;
 }
 
 
@@ -107,14 +278,17 @@ main(int argc, char *argv[]) {
     struct cmdlineInfo cmdline;
     struct pam inpam;  
     struct pam outpam;
-    tuple * tuplerow[3];
+    tuple * tuplerowWindow[5];
     tuple * outputrow;
+    unsigned int rows;
     
-    pnm_init( &argc, argv );
+    pnm_init(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
 
-    ifP = pm_openr(cmdline.inputFilespec);
+    rows = cmdline.filterP->rows;
+
+    ifP = pm_openr(cmdline.inputFileName);
     
     pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
 
@@ -123,48 +297,43 @@ main(int argc, char *argv[]) {
 
     pnm_writepaminit(&outpam);
 
-    allocateRowWindowBuffer(&inpam, tuplerow);
+    allocateRowWindowBuffer(&inpam, tuplerowWindow, rows);
     outputrow = pnm_allocpamrow(&outpam);
 
-    if (inpam.height < 3) {
+    if (inpam.height < rows) {
         unsigned int row;
-        pm_message("WARNING: Image height less than 3.  No mixing done.");
+        pm_message("WARNING: Image height less than %d.  No mixing done.",
+                   rows);
         for (row = 0; row < inpam.height; ++row) {
-            pnm_readpamrow(&inpam, tuplerow[0]);
-            pnm_writepamrow(&outpam, tuplerow[0]);
+            pnm_readpamrow(&inpam, tuplerowWindow[0]);
+            pnm_writepamrow(&outpam, tuplerowWindow[0]);
         }
     } else {
         unsigned int row;
 
-        pnm_readpamrow(&inpam, tuplerow[0]);
-        pnm_readpamrow(&inpam, tuplerow[1]);
-
-        /* Pass through first row */
-        pnm_writepamrow(&outpam, tuplerow[0]);
-
-        for (row = 2; row < inpam.height; ++row) {
-            unsigned int col;
-            pnm_readpamrow(&inpam, tuplerow[2]);
-            for (col = 0; col < inpam.width; ++col) {
-                unsigned int plane;
-
-                for (plane = 0; plane < inpam.depth; ++plane) {
-                    outputrow[col][plane] =
-                        (tuplerow[0][col][plane]
-                         + tuplerow[1][col][plane] * 2
-                         + tuplerow[2][col][plane]) / 4;
-                }
-            }
-            pnm_writepamrow(&outpam, outputrow);
-            
-            slideWindowDown(tuplerow);
-        }
-
-        /* Pass through last row */
-        pnm_writepamrow(&outpam, tuplerow[1]);
+    for (row = 0; row < rows-1; ++row)
+        pnm_readpamrow(&inpam, tuplerowWindow[row]);
+
+    /* Pass through first unfilterable rows */
+    for (row = 0; row < rows/2; ++row)
+        pnm_writepamrow(&outpam, tuplerowWindow[row]);
+
+    for (row = rows / 2 + 1; row < inpam.height - rows / 2 + 1; ++row) {
+        pnm_readpamrow(&inpam, tuplerowWindow[rows-1]);
+        cmdline.filterP->filter(outputrow, tuplerowWindow,
+                                inpam.width, inpam.depth,
+                                cmdline.adaptive, inpam.maxval);
+        pnm_writepamrow(&outpam, outputrow);
+        
+        slideWindowDown(tuplerowWindow, rows);
+    }
+    
+    /* Pass through last rows */
+    for (row = rows/2; row < rows-1; ++row)
+        pnm_writepamrow(&outpam, tuplerowWindow[row]);
     }
 
-    freeRowWindowBuffer(tuplerow);
+    freeRowWindowBuffer(tuplerowWindow, rows);
     pnm_freepamrow(outputrow);
     pm_close(inpam.file);
     pm_close(outpam.file);
diff --git a/editor/ppm3d.c b/editor/ppm3d.c
index c37ceeb1..6f317a0b 100644
--- a/editor/ppm3d.c
+++ b/editor/ppm3d.c
@@ -1,18 +1,99 @@
-/* ppmto3d.c - convert a portable pixmap to a portable graymap
-**
-** Copyright (C) 1989 by Jef Poskanzer.
-**
-** Permission to use, copy, modify, and distribute this software and its
-** documentation for any purpose and without fee is hereby granted, provided
-** that the above copyright notice appear in all copies and that both that
-** copyright notice and this permission notice appear in supporting
-** documentation.  This software is provided "as is" without express or
-** implied warranty.
-*/
+/*=============================================================================
+                                   ppmto3d
+===============================================================================
+  This program converts two PPM images into an anaglyph stereogram image PPM.
+  (for viewing with red/blue 3D glasses).
 
+=============================================================================*/
+
+#include <assert.h>
+
+#include "shhopt.h"
+#include "mallocvar.h"
 #include "ppm.h"
 #include "lum.h"
 
+
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * leftInputFileName;  /* '-' if stdin */
+    const char * rghtInputFileName;  /* '-' if stdin */
+    int offset;
+    unsigned int color;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.  
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+    unsigned int offsetSpec;
+    const char * offsetArg;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "color",   OPT_FLAG,   NULL,
+            &cmdlineP->color,   0);
+    OPTENT3(0, "offset",  OPT_INT,    &cmdlineP->offset,
+            &offsetSpec,  0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3( &argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+    
+    if (argc-1 < 2)
+        pm_error("You must specify at least two arguments: left and right "
+                 "input file names.  You specified %u", argc-1);
+    else {
+        cmdlineP->leftInputFileName = argv[1];
+        cmdlineP->rghtInputFileName = argv[2];
+
+        if (argc-1 > 2) {
+            offsetArg = argv[3];
+
+            if (argc-1 > 3)
+                pm_error("Program takes at most 3 arguments:  left and "
+                         "right input file names and offset.  "
+                         "You specified %u", argc-1);
+        } else
+            offsetArg = NULL;
+    }
+
+    if (offsetArg && offsetSpec)
+        pm_error("You cannot specify both -offset and the offset "
+                 "argument (i.e. with -offset, there is at most "
+                 "two arguments: left and right input file names");
+    else if (!offsetArg && !offsetSpec)
+        cmdlineP->offset = 0;
+    else if (offsetArg)
+        cmdlineP->offset = atoi(offsetArg);
+}
+
+
+
 static void
 computeGrayscaleRow(const pixel * const inputRow,
                     gray *        const outputRow,
@@ -34,105 +115,193 @@ computeGrayscaleRow(const pixel * const inputRow,
 
 
 
+static void
+compute3dRowMono(gray *       const lGrayrow,
+                 gray *       const rGrayrow,
+                 pixel *      const pixelrow,
+                 unsigned int const cols,
+                 int          const offset) {
+    
+    unsigned int col;
+    gray *  lgP;
+    gray *  rgP;
+    pixel * pP;
+
+    assert(abs(offset) <= cols);
+
+    for (col = 0, pP = pixelrow, lgP = lGrayrow, rgP = rGrayrow;
+         col < cols + offset;
+         ++col) {
+            
+        if ((int)col < offset/2)
+            ++lgP;
+        else if ((int)col < offset) {
+            PPM_ASSIGN(*pP, 0, *lgP, *lgP);
+            ++lgP;
+            ++pP;
+        } else if (col < cols) {
+            PPM_ASSIGN(*pP, *rgP, *lgP, *lgP);
+            ++lgP;
+            ++rgP;
+            ++pP;
+        } else if (col < cols + offset/2) {
+            PPM_ASSIGN(*pP, *rgP, 0, 0);
+            ++rgP;
+            ++pP;
+        } else {
+            assert(col < cols + offset);
+            ++rgP;
+        }
+    }
+}    
+
+
+
+static void
+compute3dRowColor(pixel *      const lPixelrow,
+                  pixel *      const rPixelrow,
+                  pixel *      const pixelrow,
+                  unsigned int const cols,
+                  unsigned int const offset) {
+    
+    unsigned int col;
+    pixel * lP;
+    pixel * rP;
+    pixel * pP;
+
+    assert(abs(offset) <= cols);
+
+    for (col = 0, pP = pixelrow, lP = lPixelrow, rP = rPixelrow;
+         col < cols + offset;
+         ++col) {
+            
+        if ((int)col < offset/2)
+            ++lP;
+        else if ((int)col < offset) {
+            PPM_ASSIGN(*pP, 0, PPM_GETG(*lP), PPM_GETB(*lP));
+            ++lP;
+            ++pP;
+        } else if (col < cols) {
+            PPM_ASSIGN(*pP, PPM_GETR(*rP), PPM_GETG(*lP), PPM_GETB(*lP));
+            ++lP;
+            ++rP;
+            ++pP;
+        } else if (col < cols + offset/2) {
+            PPM_ASSIGN(*pP, PPM_GETR(*rP), 0, 0);
+            ++rP;
+            ++pP;
+        } else {
+            assert(col < cols + offset);
+            ++rP;
+        }
+    }
+}    
+
+
+
+static void
+write3dRaster(FILE *       const ofP,
+              FILE *       const lIfP,
+              FILE *       const rIfP,
+              unsigned int const cols,
+              unsigned int const rows,
+              pixval       const maxval,
+              int          const lFormat,
+              int          const rFormat,
+              int          const offset,
+              bool         const color) {
+
+    pixel * lPixelrow;
+    gray *  lGrayrow;
+    pixel * rPixelrow;
+    gray *  rGrayrow;
+    pixel * pixelrow;
+
+    unsigned int row;
+
+    assert(abs(offset) < cols);
+
+    lPixelrow = ppm_allocrow (cols);
+    lGrayrow  = pgm_allocrow (cols);
+    rPixelrow = ppm_allocrow (cols);
+    rGrayrow  = pgm_allocrow (cols);
+    pixelrow  = ppm_allocrow (cols);
+
+    for (row = 0; row < rows; ++row) {
+        ppm_readppmrow(lIfP, lPixelrow, cols, maxval, lFormat);
+        ppm_readppmrow(rIfP, rPixelrow, cols, maxval, rFormat);
+
+        computeGrayscaleRow(lPixelrow, lGrayrow, maxval, cols);
+        computeGrayscaleRow(rPixelrow, rGrayrow, maxval, cols);
+
+        if (color)
+            compute3dRowColor(lPixelrow, rPixelrow, pixelrow, cols, offset);
+        else
+            compute3dRowMono(lGrayrow, rGrayrow, pixelrow, cols, offset);
+
+        ppm_writeppmrow(ofP, pixelrow, cols, maxval, 0);
+    }
+
+    ppm_freerow(pixelrow);
+    pgm_freerow(rGrayrow);
+    ppm_freerow(rPixelrow);
+    pgm_freerow(lGrayrow);
+    ppm_freerow(lPixelrow);
+}
+
+
+
 int
-main (int argc, char *argv[]) {
+main(int argc, char *argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE * lIfP;
+    FILE * rIfP;
 
-    int offset; 
-    int cols, rows, row;
-    pixel* pixelrow;
+    int cols, rows;
     pixval maxval;
 
-    FILE* Lifp;
-    pixel* Lpixelrow;
-    gray* Lgrayrow;
-    int Lrows, Lcols, Lformat;
-    pixval Lmaxval;
+    int lRows, lCols;
+    int lFormat;
+    pixval lMaxval;
    
-    FILE* Rifp;
-    pixel* Rpixelrow;
-    gray* Rgrayrow;
-    int Rrows, Rcols, Rformat;
-    pixval Rmaxval;
+    int rRows, rCols;
+    int rFormat;
+    pixval rMaxval;
    
-    ppm_init (&argc, argv);
+    ppm_init(&argc, argv);
 
-    if (argc-1 > 3 || argc-1 < 2) 
-        pm_error("Wrong number of arguments (%d).  Arguments are "
-                 "leftppmfile rightppmfile [horizontal_offset]", argc-1);
+    parseCommandLine(argc, argv, &cmdline);
 
-    Lifp = pm_openr (argv[1]);
-    Rifp = pm_openr (argv[2]);
+    lIfP = pm_openr(cmdline.leftInputFileName);
+    rIfP = pm_openr(cmdline.rghtInputFileName);
 
-    if (argc-1 >= 3) 
-        offset = atoi (argv[3]);
-    else
-        offset = 30;
-
-    ppm_readppminit (Lifp, &Lcols, &Lrows, &Lmaxval, &Lformat);
-    ppm_readppminit (Rifp, &Rcols, &Rrows, &Rmaxval, &Rformat);
+    ppm_readppminit(lIfP, &lCols, &lRows, &lMaxval, &lFormat);
+    ppm_readppminit(rIfP, &rCols, &rRows, &rMaxval, &rFormat);
     
-    if ((Lcols != Rcols) || (Lrows != Rrows) || 
-        (Lmaxval != Rmaxval) || 
-        (PPM_FORMAT_TYPE(Lformat) != PPM_FORMAT_TYPE(Rformat)))
+    if ((lCols != rCols) || (lRows != rRows) || 
+        (lMaxval != rMaxval) || 
+        (PPM_FORMAT_TYPE(lFormat) != PPM_FORMAT_TYPE(rFormat)))
         pm_error ("Pictures are not of same size and format");
     
-    cols = Lcols;
-    rows = Lrows;
-    maxval = Lmaxval;
+    cols   = lCols;
+    rows   = lRows;
+    maxval = lMaxval;
+
+    if (abs(cmdline.offset) >= cols)
+        pm_error("Magnitude of -offset (%u columns) is not less than "
+                 "width of images "
+                 "(%u columns)", abs(cmdline.offset), cols);
    
-    ppm_writeppminit (stdout, cols, rows, maxval, 0);
-    Lpixelrow = ppm_allocrow (cols);
-    Lgrayrow = pgm_allocrow (cols);
-    Rpixelrow = ppm_allocrow (cols);
-    Rgrayrow = pgm_allocrow (cols);
-    pixelrow = ppm_allocrow (cols);
+    ppm_writeppminit(stdout, cols, rows, maxval, 0);
 
-    for (row = 0; row < rows; ++row) {
-        ppm_readppmrow(Lifp, Lpixelrow, cols, maxval, Lformat);
-        ppm_readppmrow(Rifp, Rpixelrow, cols, maxval, Rformat);
-
-        computeGrayscaleRow(Lpixelrow, Lgrayrow, maxval, cols);
-        computeGrayscaleRow(Rpixelrow, Rgrayrow, maxval, cols);
-        {
-            int col;
-            gray* LgP;
-            gray* RgP;
-            pixel* pP;
-            for (col = 0, pP = pixelrow, LgP = Lgrayrow, RgP = Rgrayrow;
-                 col < cols + offset;
-                 ++col) {
-            
-                if (col < offset/2)
-                    ++LgP;
-                else if (col >= offset/2 && col < offset) {
-                    const pixval Blue = (pixval) (float) *LgP;
-                    const pixval Red = (pixval) 0;
-                    PPM_ASSIGN (*pP, Red, Blue, Blue);
-                    ++LgP;
-                    ++pP;
-                } else if (col >= offset && col < cols) {
-                    const pixval Red = (pixval) (float) *RgP;
-                    const pixval Blue = (pixval) (float) *LgP;
-                    PPM_ASSIGN (*pP, Red, Blue, Blue);
-                    ++LgP;
-                    ++RgP;
-                    ++pP;
-                } else if (col >= cols && col < cols + offset/2) {
-                    const pixval Blue = (pixval) 0;
-                    const pixval Red = (pixval) (float) *RgP;
-                    PPM_ASSIGN (*pP, Red, Blue, Blue);
-                    ++RgP;
-                    ++pP;
-                } else
-                    ++RgP;
-            }
-        }    
-        ppm_writeppmrow(stdout, pixelrow, cols, maxval, 0);
-    }
+    write3dRaster(stdout, lIfP, rIfP, cols, rows, maxval,
+                  lFormat, rFormat, cmdline.offset, cmdline.color);
 
-    pm_close(Lifp);
-    pm_close(Rifp);
+    pm_close(lIfP);
+    pm_close(rIfP);
     pm_close(stdout);
 
     return 0;
 }
+
diff --git a/editor/ppmbrighten.c b/editor/ppmbrighten.c
index 93649082..1dcdbd7f 100644
--- a/editor/ppmbrighten.c
+++ b/editor/ppmbrighten.c
@@ -21,7 +21,7 @@ struct cmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
-    const char *inputFilespec;  /* '-' if stdin */
+    const char * inputFilespec;  /* '-' if stdin */
     float saturation;
     float value;
     unsigned int normalize;
@@ -29,10 +29,9 @@ struct cmdlineInfo {
 
 
 
-
 static void
-parseCommandLine (int argc, char ** argv,
-                  struct cmdlineInfo *cmdlineP) {
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    parse program command line described in Unix standard form by argc
    and argv.  Return the information in the options as *cmdlineP.  
@@ -270,9 +269,9 @@ int
 main(int argc, char * argv[]) {
 
     struct cmdlineInfo cmdline;
-    FILE *ifP;
+    FILE * ifP;
     pixval minValue, maxValue;
-    pixel *pixelrow;
+    pixel * pixelrow;
     pixval maxval;
     int rows, cols, format, row;
 
diff --git a/lib/fileio.h b/lib/fileio.h
index 158da10a..586c8265 100644
--- a/lib/fileio.h
+++ b/lib/fileio.h
@@ -1,5 +1,7 @@
-#ifndef _NETPBM_FILEIO_H_
-#define _NETPBM_FILEIO_H_
+#ifndef FILEIO_H_INCLUDED
+#define FILEIO_H_INCLUDED
+
+#include <stdio.h>
 
 char
 pm_getc(FILE * const file);
diff --git a/lib/libpam.c b/lib/libpam.c
index f725a916..362c1159 100644
--- a/lib/libpam.c
+++ b/lib/libpam.c
@@ -687,10 +687,10 @@ pnm_readpaminitrestaspnm(FILE * const fileP,
 -----------------------------------------------------------------------------*/
     struct pam pam;
 
-    pam.size        = sizeof(struct pam);
-    pam.file        = fileP;
-    pam.len         = PAM_STRUCT_SIZE(tuple_type);
-    pam.format      = PAM_FORMAT;
+    pam.size   = sizeof(struct pam);
+    pam.file   = fileP;
+    pam.len    = PAM_STRUCT_SIZE(tuple_type);
+    pam.format = PAM_FORMAT;
 
     readpaminitrest(&pam);
 
@@ -792,7 +792,8 @@ pnm_readpaminit(FILE *       const file,
         break;
         
     default:
-        pm_error("bad magic number - not a PAM, PPM, PGM, or PBM file");
+        pm_error("bad magic number 0x%x - not a PAM, PPM, PGM, or PBM file",
+                 pamP->format);
     }
     
     pamP->bytes_per_sample = pnm_bytespersample(pamP->maxval);
diff --git a/lib/libpamread.c b/lib/libpamread.c
index 85701a90..c03d730c 100644
--- a/lib/libpamread.c
+++ b/lib/libpamread.c
@@ -16,9 +16,9 @@
 #include <limits.h>
 #include <assert.h>
 
-#include "pam.h"
 #include "fileio.h"
 #include "nstring.h"
+#include "pam.h"
 
 
 static void
diff --git a/lib/libpbm3.c b/lib/libpbm3.c
index 34922edd..2a811748 100644
--- a/lib/libpbm3.c
+++ b/lib/libpbm3.c
@@ -133,6 +133,14 @@ packBitsWithMmxSse(FILE *          const fileP,
 
 
 
+static unsigned int
+bitValue(unsigned char const byteValue) {
+
+    return byteValue == 0 ? 0 : 1;
+}
+
+
+
 static void
 packBitsGeneric(FILE *          const fileP,
                 const bit *     const bitrow,
@@ -140,7 +148,7 @@ packBitsGeneric(FILE *          const fileP,
                 unsigned int    const cols,
                 unsigned int *  const nextColP) {
 /*----------------------------------------------------------------------------
-   Pack the bits of bitrow[] into byts at 'packedBits'.  Going left to right,
+   Pack the bits of bitrow[] into bytes at 'packedBits'.  Going left to right,
    stop when there aren't enough bits left to fill a whole byte.  Return
    as *nextColP the number of the next column after the rightmost one we
    packed.
@@ -149,18 +157,16 @@ packBitsGeneric(FILE *          const fileP,
 -----------------------------------------------------------------------------*/
     unsigned int col;
 
-    #define iszero(x) ((x) == 0 ? 0 : 1)
-
     for (col = 0; col + 7 < cols; col += 8)
         packedBits[col/8] = (
-            iszero(bitrow[col+0]) << 7 |
-            iszero(bitrow[col+1]) << 6 |
-            iszero(bitrow[col+2]) << 5 |
-            iszero(bitrow[col+3]) << 4 |
-            iszero(bitrow[col+4]) << 3 |
-            iszero(bitrow[col+5]) << 2 |
-            iszero(bitrow[col+6]) << 1 |
-            iszero(bitrow[col+7]) << 0
+            bitValue(bitrow[col+0]) << 7 |
+            bitValue(bitrow[col+1]) << 6 |
+            bitValue(bitrow[col+2]) << 5 |
+            bitValue(bitrow[col+3]) << 4 |
+            bitValue(bitrow[col+4]) << 3 |
+            bitValue(bitrow[col+5]) << 2 |
+            bitValue(bitrow[col+6]) << 1 |
+            bitValue(bitrow[col+7]) << 0
             );
     *nextColP = col;
 }
diff --git a/lib/libpm.c b/lib/libpm.c
index f36b7a50..88b790e6 100644
--- a/lib/libpm.c
+++ b/lib/libpm.c
@@ -1662,3 +1662,32 @@ pm_check(FILE *               const file,
 
 
 
+void
+pm_drain(FILE * const fileP,
+         uint   const limit,
+         uint * const bytesReadP) {
+/*----------------------------------------------------------------------------
+  Read bytes from *fileP until EOF and return as *bytesReadP how many there
+  were.
+
+  But don't read any more than 'limit'.
+
+  This is a good thing to call after reading an input file to be sure you
+  didn't leave some input behind, which could mean you didn't properly
+  interpret the file.
+-----------------------------------------------------------------------------*/
+    uint bytesRead;
+    bool eof;
+
+    for (bytesRead = 0, eof = false; !eof && bytesRead < 4096;) {
+
+        int rc;
+
+        rc = fgetc(fileP);
+
+        eof = (rc == EOF);
+        if (!eof)
+            ++bytesRead;
+    }
+    *bytesReadP = bytesRead;
+}
diff --git a/lib/pm.h b/lib/pm.h
index 8265c9ea..232fc363 100644
--- a/lib/pm.h
+++ b/lib/pm.h
@@ -345,6 +345,11 @@ pm_check(FILE *               const file,
          pm_filepos           const need_raster_size,
          enum pm_check_code * const retval_p);
 
+void
+pm_drain(FILE *         const fileP,
+         unsigned int   const limit,
+         unsigned int * const bytesReadP);
+
 char *
 pm_arg0toprogname(const char arg0[]);
 
diff --git a/lib/util/pm_c_util.h b/lib/util/pm_c_util.h
index f21a2f82..db159284 100644
--- a/lib/util/pm_c_util.h
+++ b/lib/util/pm_c_util.h
@@ -15,6 +15,14 @@
 #define ROUND(X) (((X) >= 0) ? (int)((X)+0.5) : (int)((X)-0.5))
 #undef ROUNDU
 #define ROUNDU(X) ((unsigned int)((X)+0.5))
+
+/* ROUNDUP rounds up to a specified multiple.  E.g. ROUNDUP(22, 8) == 24 */
+
+#undef ROUNDUP
+#define ROUNDUP(X,M) (((X)+(M)-1)/(M)*(M))
+#undef ROUNDDN
+#define ROUNDDN(X,M) ((X)/(M)*(M))
+
 #undef SQR
 #define SQR(a) ((a)*(a))
 
diff --git a/lib/util/shhopt.c b/lib/util/shhopt.c
index 15058bb5..718186fa 100644
--- a/lib/util/shhopt.c
+++ b/lib/util/shhopt.c
@@ -89,9 +89,13 @@ optStructCount(const optEntry opt[])
     return ret;
 }
 
+
+
+static int
+optMatch(optEntry     const opt[],
+         const char * const s,
+         int          const lng) {
 /*------------------------------------------------------------------------
- |  NAME          optMatch
- |
  |  FUNCTION      Find a matching option.
  |
  |  INPUT         opt     array of possible options.
@@ -103,35 +107,39 @@ optStructCount(const optEntry opt[])
  |  DESCRIPTION   Short options are matched from the first character in
  |                the given string.
  */
-static int
-optMatch(const optEntry opt[], const char *s, int lng)
-{
-    int        nopt, q, matchlen = 0;
-    const char *p;
 
-    nopt = optStructCount(opt);
+    unsigned int const nopt = optStructCount(opt);
+
+    unsigned int q;
+    unsigned int matchlen;
+    const char * p;
+
+    matchlen = 0;  /* initial value */
+
     if (lng) {
         if ((p = strchr(s, '=')) != NULL)
             matchlen = p - s;
         else
             matchlen = strlen(s);
     }
-    for (q = 0; q < nopt; q++) {
+    for (q = 0; q < nopt; ++q) {
         if (lng) {
-            if (!opt[q].longName)
-                continue;
-            if (strncmp(s, opt[q].longName, matchlen) == 0)
-                return q;
+            if (opt[q].longName) {
+                if (strncmp(s, opt[q].longName, matchlen) == 0)
+                    return q;
+            }
         } else {
-            if (!opt[q].shortName)
-                continue;
-            if (*s == opt[q].shortName)
-                return q;
+            if (opt[q].shortName) {
+                if (s[0] == opt[q].shortName)
+                    return q;
+            }
         }
     }
     return -1;
 }
 
+
+
 /*------------------------------------------------------------------------
  |  NAME          optString
  |
@@ -422,7 +430,7 @@ optExecute(optEntry  const opt, char *arg, int lng)
     case OPT_UINT:
     case OPT_ULONG: {
         unsigned long tmp;
-        char *e;
+        char * tailPtr;
         
         if (arg == NULL)
             optFatal("internal error: optExecute() called with NULL argument "
@@ -431,9 +439,8 @@ optExecute(optEntry  const opt, char *arg, int lng)
         if (arg[0] == '-' || arg[1] == '+')
             optFatal("unsigned number '%s' has a sign ('%c')",
                      arg, arg[0]);
-
-        tmp = strtoul(arg, &e, 10);
-        if (*e)
+        tmp = strtoul(arg, &tailPtr, 10);
+        if (*tailPtr)
             optFatal("invalid number `%s'", arg);
         if (errno == ERANGE
             || (opt.type == OPT_UINT && tmp > UINT_MAX))
@@ -648,7 +655,15 @@ static void
 parse_short_option_token(char *argv[], const int argc, const int ai,
                          const optEntry opt_table[], 
                          int * const tokens_consumed_p) {
+/*----------------------------------------------------------------------------
+   Parse a cluster of short options, e.g. -walne .
+
+   The last option in the cluster might take an argument, and we parse
+   that as well.  e.g. -cf myfile or -cfmyfile .
 
+   argv[] and argc describe the whole program argument set.  'ai' is the
+   index of the argument that is the short option cluster.
+-----------------------------------------------------------------------------*/
     char *o;  /* A short option character */
     char *arg;
     int mi;   /* index into option table */
@@ -690,11 +705,60 @@ parse_short_option_token(char *argv[], const int argc, const int ai,
 
 
 static void
-parse_long_option(char *argv[], const int argc, const int ai,
-                  const int namepos,
-                  const optEntry opt_table[], 
-                  int * const tokens_consumed_p) {
+fatalUnrecognizedLongOption(const char * const optionName,
+                            optEntry     const optTable[]) {
+
+    unsigned int const nopt = optStructCount(optTable);
+
+    unsigned int q;
+
+    char optList[1024];
+
+    optList[0] = '\0';  /* initial value */
+
+    for (q = 0;
+         q < nopt && strlen(optList) + 1 <= sizeof(optList);
+         ++q) {
+
+        const optEntry * const optEntryP = &optTable[q];
+        const char * entry;
+
+        if (optEntryP->longName)
+            asprintfN(&entry, "-%s ", optEntryP->longName);
+        else
+            asprintfN(&entry, "-%c ", optEntryP->shortName);
+
+        strncat(optList, entry, sizeof(optList) - strlen(optList) - 1);
+
+        strfree(entry);
+
+        if (strlen(optList) + 1 == sizeof(optList)) {
+            /* Buffer is full.  Overwrite end of list with ellipsis */
+            strcpy(&optList[sizeof(optList) - 4], "...");
+        }
+    }
+    optFatal("unrecognized option '%s'.  Recognized options are: %s",
+             optionName, optList);
+}
+
+
+
+static void
+parse_long_option(char *   const argv[],
+                  int      const argc,
+                  int      const ai,
+                  int      const namepos,
+                  optEntry const opt_table[], 
+                  int *    const tokens_consumed_p) {
+/*----------------------------------------------------------------------------
+   Parse a long option, e.g. -verbose or --verbose.
+
+   The option might take an argument, and we parse
+   that as well.  e.g. -file=myfile or -file myfile .
 
+   argv[] and argc describe the whole program argument set.  'ai' is the
+   index of the argument that is the long option.
+-----------------------------------------------------------------------------*/
     char *equals_arg;
       /* The argument of an option, included in the same token, after a
          "=".  NULL if no "=" in the token.
@@ -708,8 +772,8 @@ parse_long_option(char *argv[], const int argc, const int ai,
     *tokens_consumed_p = 1;  /* initial assumption */
     /* find matching option */
     if ((mi = optMatch(opt_table, &argv[ai][namepos], 1)) < 0)
-        optFatal("unrecognized option `%s'", argv[ai]);
-            
+        fatalUnrecognizedLongOption(argv[ai], opt_table);
+
     /* possibly locate the argument to this option. */
     { 
         char *p;
@@ -732,7 +796,8 @@ parse_long_option(char *argv[], const int argc, const int ai,
         }
     } else {
         if (equals_arg)
-            optFatal("option `%s' doesn't allow an argument",
+            optFatal("option `%s' doesn't allow an argument, but you "
+                     "have specified it in the form name=value",
                      optString(opt_table[mi], 1));
         else 
             arg = NULL;
diff --git a/other/Makefile b/other/Makefile
index 139527e3..a38766fc 100644
--- a/other/Makefile
+++ b/other/Makefile
@@ -24,7 +24,7 @@ endif
 # build.
 
 PORTBINARIES = pamarith pambayer pamchannel pamdepth \
-	pamendian pamlookup pampick pamsplit \
+	pamendian pamfixtrunc pamlookup pampick pamsplit \
 	pamstack pamsummcol pnmcolormap \
 	ppmdcfont ppmddumpfont ppmdmkfont 
 
diff --git a/other/pamfixtrunc.c b/other/pamfixtrunc.c
new file mode 100644
index 00000000..7bf2435e
--- /dev/null
+++ b/other/pamfixtrunc.c
@@ -0,0 +1,175 @@
+/*============================================================================
+                             pamfixtrunc
+==============================================================================
+  Fix a Netpbm image that has been truncated, e.g. by I/O error.
+
+  By Bryan Henderson, January 2007.
+
+  Contributed to the public domain by its author.
+
+============================================================================*/
+
+#include <setjmp.h>
+
+#include "pam.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFilespec;  /* Filespec of input file */
+    unsigned int verbose;
+};
+
+
+
+static void
+parseCommandLine(int argc, char ** const argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    MALLOCARRAY(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+
+    OPTENT3(0, "verbose",   OPT_FLAG,    NULL, &cmdlineP->verbose,       0);
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We don't parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (argc-1 == 0) 
+        cmdlineP->inputFilespec = "-";
+    else if (argc-1 != 1)
+        pm_error("Program takes zero or one argument (filename).  You "
+                 "specified %d", argc-1);
+    else
+        cmdlineP->inputFilespec = argv[1];
+}
+
+
+
+static unsigned int readErrRow;
+static bool readErrVerbose;
+
+static pm_usererrormsgfn discardMsg;
+
+static void
+discardMsg(const char * const msg) {
+    if (readErrVerbose)
+        pm_message("Error reading row %u: %s", readErrRow, msg);
+}
+
+
+
+static void
+countRows(const struct pam * const inpamP,
+          bool               const verbose,
+          unsigned int *     const goodRowCountP) {
+
+    tuple * tuplerow;
+    unsigned int row;
+    jmp_buf jmpbuf;
+    int rc;
+    unsigned int goodRowCount;
+    
+    tuplerow = pnm_allocpamrow(inpamP);
+
+    pm_setusererrormsgfn(discardMsg);
+
+    rc = setjmp(jmpbuf);
+    if (rc == 0) {
+        pm_setjmpbuf(&jmpbuf);
+
+        readErrVerbose = verbose;
+        goodRowCount = 0;  /* initial value */
+        for (row = 0; row < inpamP->height; ++row) {
+            readErrRow = row;
+            pnm_readpamrow(inpamP, tuplerow);
+            /* The above does not return if it can't read the next row from
+               the file.  Instead, it longjmps out of this loop.
+            */
+            ++goodRowCount;
+        }
+    }
+    *goodRowCountP = goodRowCount;
+
+    pnm_freepamrow(tuplerow);
+}
+
+
+
+static void
+copyGoodRows(const struct pam * const inpamP,
+             FILE *             const ofP,
+             unsigned int       const goodRowCount) {
+
+    struct pam outpam;
+    tuple * tuplerow;
+    unsigned int row;
+
+    outpam = *inpamP;  /* initial value */
+
+    outpam.file = ofP;
+    outpam.height = goodRowCount;
+
+    tuplerow = pnm_allocpamrow(inpamP);
+
+    pnm_writepaminit(&outpam);
+
+    for (row = 0; row < outpam.height; ++row) {
+        pnm_readpamrow(inpamP, tuplerow);
+        pnm_writepamrow(&outpam, tuplerow);
+    }
+    
+    pnm_freepamrow(tuplerow);
+}
+
+
+
+int
+main(int argc, char * argv[]) {
+    struct cmdlineInfo cmdline;
+    struct pam inpam;
+    FILE * ifP;
+    pm_filepos rasterPos;
+    unsigned int goodRowCount;
+
+    pnm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr_seekable(cmdline.inputFilespec);
+
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    pm_tell2(ifP, &rasterPos, sizeof(rasterPos));
+
+    countRows(&inpam, cmdline.verbose, &goodRowCount);
+
+    pm_message("Copying %u good rows; %u bottom rows missing",
+               goodRowCount, inpam.height - goodRowCount);
+    
+    pm_seek2(ifP, &rasterPos, sizeof(rasterPos));
+
+    copyGoodRows(&inpam, stdout, goodRowCount);
+
+    pm_close(inpam.file);
+    
+    return 0;
+}
+
+