about summary refs log tree commit diff
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2006-09-21 18:00:16 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2006-09-21 18:00:16 +0000
commit3a2be6f9d910b862d4e5ea237d734315627dd661 (patch)
tree0525085036572441949db4493a31afa5c8813a91
parent3aa04aa72c6ec522395919d528dfce6a92b0de98 (diff)
downloadnetpbm-mirror-3a2be6f9d910b862d4e5ea237d734315627dd661.tar.gz
netpbm-mirror-3a2be6f9d910b862d4e5ea237d734315627dd661.tar.xz
netpbm-mirror-3a2be6f9d910b862d4e5ea237d734315627dd661.zip
Release 10.36.0
git-svn-id: http://svn.code.sf.net/p/netpbm/code/advanced@65 9d0c8265-081b-0410-96cb-a4ca84ce46f8
-rw-r--r--GNUmakefile3
-rw-r--r--Makefile.common24
-rw-r--r--Makefile.version5
-rw-r--r--converter/other/Makefile15
-rw-r--r--converter/other/cameratopam/Makefile3
-rw-r--r--converter/other/fiasco/Makefile2
-rw-r--r--converter/other/fiasco/codec/Makefile2
-rw-r--r--converter/other/fiasco/input/Makefile4
-rw-r--r--converter/other/fiasco/lib/Makefile2
-rw-r--r--converter/other/fiasco/output/Makefile7
-rw-r--r--converter/other/jbig/Makefile4
-rw-r--r--converter/other/jpeg2000/Makefile4
-rw-r--r--converter/other/jpeg2000/libjasper/Makefile.common4
-rw-r--r--converter/other/pamtosvg/pamtosvg.test6
-rw-r--r--converter/other/pnmtopalm/Makefile4
-rw-r--r--converter/other/pnmtopalm/gen_palm_colormap.c33
-rw-r--r--converter/pbm/pbmtonokia.c669
-rw-r--r--converter/ppm/ppmtobmp.c2
-rw-r--r--converter/ppm/ppmtompeg/Makefile5
-rw-r--r--converter/ppm/xvminitoppm.c4
-rw-r--r--doc/HISTORY44
-rw-r--r--doc/INSTALL3
-rw-r--r--editor/pamperspective.c10
-rw-r--r--editor/pamthreshold.c21
-rw-r--r--editor/pbmpscale.c230
-rw-r--r--editor/pnmcrop.c18
-rw-r--r--editor/pnmrotate.c4
-rw-r--r--generator/pamstereogram.test8
-rw-r--r--lib/Makefile6
-rw-r--r--lib/libpammap.c20
-rw-r--r--lib/libpamn.c284
-rw-r--r--lib/libpamread.c104
-rw-r--r--lib/libpamwrite.c29
-rw-r--r--lib/libpbm3.c113
-rw-r--r--lib/libpgm1.c186
-rw-r--r--lib/libpm.c292
-rw-r--r--lib/libpnm1.c125
-rw-r--r--lib/libpnm2.c102
-rw-r--r--lib/libppm1.c290
-rw-r--r--lib/libppmcmap.c313
-rw-r--r--lib/libppmcolor.c257
-rw-r--r--lib/libppmfuzzy.c89
-rw-r--r--lib/pm.h18
-rw-r--r--lib/ppm.h17
-rw-r--r--lib/util/Makefile6
-rw-r--r--lib/util/nstring.c26
-rw-r--r--lib/util/nstring.h9
-rw-r--r--other/Makefile3
-rw-r--r--other/pamx/Makefile3
-rw-r--r--urt/scanargs.c2
50 files changed, 2384 insertions, 1050 deletions
diff --git a/GNUmakefile b/GNUmakefile
index 56c6126c..420d558c 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -117,7 +117,8 @@ $(BUILDDIR)/inttypes_netpbm.h: $(TYPEGEN)
 # cross compiling.
 
 $(BUILDDIR)/pm_config.h: \
-  $(SRCDIR)/pm_config.in.h Makefile.config inttypes_netpbm.h $(ENDIANGEN)
+  $(SRCDIR)/pm_config.in.h Makefile.config inttypes_netpbm.h \
+  $(ENDIANGEN)
 	echo '/* pm_config.h GENERATED BY A MAKE RULE */' >$@ || $(DELETEIT)
 	echo '#ifndef PM_CONFIG_H' >>$@ || $(DELETEIT)
 	echo '#define PM_CONFIG_H' >>$@ || $(DELETEIT)
diff --git a/Makefile.common b/Makefile.common
index cc1f20e0..c31013c6 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -52,10 +52,14 @@
 #   front end, so takes linker options in a different format
 # LDFLAGS: linker options 
 # LIBS or LOADLIBES: names of libraries to be added to all links
-# INCLUDES: Compiler option string to establish the search path for include
-#   files when compiling things or computing dependencies (make dep).
-#   current directory, corresponding source tree directory, and ./importinc
-#   are implied, so should not be in here.
+# COMP_INCLUDES: Compiler option string to establish the search path for
+#   component-specific include files when compiling things or computing
+#   dependencies (make dep).  Header files from this part of the search
+#   path take precedence over general Netpbm header files and external
+#   library header files.
+# EXTERN_INCLUDES: Like COMP_INCLUDES, but for external libraries, e.g.
+#   libjpeg.  All header files from the Netpbm source tree take precedence
+#   over these.
 
 # In addition, there is CADD, which is extra C compilation flags and
 # is intended to be set on a make command line (e.g. 'make CADD=-g')
@@ -68,7 +72,10 @@
 
 include $(SRCDIR)/Makefile.version
 
-INCLUDES2 := $(INCLUDES) -I$(SRCDIR)/$(SUBDIR) -I. -I importinc
+NETPBM_INCLUDES := -I importinc -I$(SRCDIR)/$(SUBDIR)
+
+# -I. is needed when builddir != srcdir
+INCLUDES = -I. $(COMP_INCLUDES) $(NETPBM_INCLUDES) $(EXTERN_INCLUDES)
 
 ifeq ($(NETPBMLIBTYPE),unixstatic)
   NETPBMLIBFNAME = libnetpbm.$(STATICLIBSUFFIX)
@@ -194,7 +201,6 @@ config:
 $(OBJECTS): %.o: %.c importinc
 #############################################################################
 # Note that the user may have configured -I options into CFLAGS or CPPFLAGS.
-# -I. is needed when builddir != srcdir
 # Note about -o: There used to be systems that couldn't handle a space
 # between flag and value.  But we found a Solaris gcc on 2003.09.02 that
 # actually fails _without_ the space (it invokes Solaris 'as' with the
@@ -210,7 +216,7 @@ $(OBJECTS): %.o: %.c importinc
 # assertion and crash the program if it isn't really true.  You can add
 # -UNDEBUG (in any of various ways) to override this.
 #
-	$(CC) -c $(INCLUDES2) -DNDEBUG \
+	$(CC) -c $(INCLUDES) -DNDEBUG \
 	    $(CPPFLAGS) $(CFLAGS) $(CFLAGS_PERSONAL) $(CADD) -o $@ $<
 
 # libopt is a utility program used in the make file below.
@@ -314,7 +320,7 @@ $(PORTBINARIES) $(MATHBINARIES): %: %.o $(NETPBMLIB) $(LIBOPT)
 
 %.o2: %.c importinc
 # Note that the user may have configured -I options into CFLAGS.
-	$(CC) -c $(INCLUDES2) -DNDEBUG $(CFLAGS) \
+	$(CC) -c $(INCLUDES) -DNDEBUG $(CFLAGS) \
 	  "-Dmain=main_$*" \
           $(CFLAGS_MERGE) $(CFLAGS_PERSONAL) $(CADD) -o $@ $<
 
@@ -469,7 +475,7 @@ dep: $(SUBDIRS:%=%/dep) importinc
 # before the first make after a clean.
 
 ifneq ($(DEP_SOURCES)x,x)
-	$(CC) -MM -MG $(INCLUDES2) $(DEP_SOURCES) >Makefile.depend
+	$(CC) -MM -MG $(INCLUDES) $(DEP_SOURCES) >Makefile.depend
 endif
 
 # Note: if I stack all these subdirectory targets into one rule, I get
diff --git a/Makefile.version b/Makefile.version
index 4683751d..a524892c 100644
--- a/Makefile.version
+++ b/Makefile.version
@@ -1,3 +1,4 @@
 NETPBM_MAJOR_RELEASE = 10
-NETPBM_MINOR_RELEASE = 35
-NETPBM_POINT_RELEASE = 05
+NETPBM_MINOR_RELEASE = 36
+NETPBM_POINT_RELEASE = 0
+
diff --git a/converter/other/Makefile b/converter/other/Makefile
index 4585c45d..6cde424d 100644
--- a/converter/other/Makefile
+++ b/converter/other/Makefile
@@ -20,10 +20,9 @@ ifneq ($(BUILD_FIASCO), N)
   SUBDIRS += fiasco
 endif
 
-INCLUDES = -I$(SRCDIR)/util 
 ifneq ($(TIFFLIB),NONE)
   ifneq ($(TIFFHDR_DIR)x,x)
-    INCLUDES += -I$(TIFFHDR_DIR)
+    EXTERN_INCLUDES += -I$(TIFFHDR_DIR)
   endif
 endif
 
@@ -31,30 +30,30 @@ ifeq ($(shell libpng-config --version),)
   ifneq ($(PNGLIB),NONE)
     HAVE_PNGLIB = Y
     ifneq ($(PNGHDR_DIR)x,x)
-      INCLUDES += -I$(PNGHDR_DIR)
+      EXTERN_INCLUDES += -I$(PNGHDR_DIR)
     endif
     ifneq ($(ZHDR_DIR)x,x)
-      INCLUDES += -I$(ZHDR_DIR)
+      EXTERN_INCLUDES += -I$(ZHDR_DIR)
     endif
   endif
 else
   HAVE_PNGLIB = Y
-  INCLUDES += $(shell libpng-config --cflags)
+  EXTERN_INCLUDES += $(shell libpng-config --cflags)
 endif
 
 ifneq ($(JPEGLIB),NONE)
   ifneq ($(JPEGHDR_DIR)x,x)
-    INCLUDES += -I$(JPEGHDR_DIR)
+    EXTERN_INCLUDES += -I$(JPEGHDR_DIR)
   endif
 endif
 ifneq ($(URTLIB),NONE)
   ifneq ($(URTHDR_DIR)x,x)
-    INCLUDES += -I$(URTHDR_DIR)
+    EXTERN_INCLUDES += -I$(URTHDR_DIR)
   endif
 endif
 ifneq ($(XML2_LIBS),NONE)
   ifneq ($(XML2_CFLAGS),NONE)
-    INCLUDES += $(XML2_CFLAGS)
+    EXTERN_INCLUDES += $(XML2_CFLAGS)
   endif
 endif
 
diff --git a/converter/other/cameratopam/Makefile b/converter/other/cameratopam/Makefile
index c50c9176..178b4fff 100644
--- a/converter/other/cameratopam/Makefile
+++ b/converter/other/cameratopam/Makefile
@@ -5,9 +5,10 @@ endif
 SUBDIR = converter/other/cameratopam
 VPATH=.:$(SRCDIR)/$(SUBDIR)
 
+EXTERN_INCLUDES =
 ifneq ($(JPEGLIB),NONE)
   ifneq ($(JPEGHDR_DIR)x,x)
-    INCLUDES += -I$(JPEGHDR_DIR)
+    EXTERN_INCLUDES += -I$(JPEGHDR_DIR)
     CFLAGS += -DHAVE_JPEG
   endif
 endif
diff --git a/converter/other/fiasco/Makefile b/converter/other/fiasco/Makefile
index 0dd945ed..0330802c 100644
--- a/converter/other/fiasco/Makefile
+++ b/converter/other/fiasco/Makefile
@@ -7,7 +7,7 @@ VPATH=.:$(SRCDIR)/$(SUBDIR)
 
 include $(BUILDDIR)/Makefile.config
 
-INCLUDES = \
+COMP_INCLUDES = \
 	-I$(SRCDIR)/$(SUBDIR)/codec -I$(SRCDIR)/$(SUBDIR)/input \
 	-I$(SRCDIR)/$(SUBDIR)/output -I$(SRCDIR)/$(SUBDIR)/lib \
 
diff --git a/converter/other/fiasco/codec/Makefile b/converter/other/fiasco/codec/Makefile
index 9a9d502a..c5f94c28 100644
--- a/converter/other/fiasco/codec/Makefile
+++ b/converter/other/fiasco/codec/Makefile
@@ -8,7 +8,7 @@ VPATH=.:$(SRCDIR)/$(SUBDIR)
 
 include $(BUILDDIR)/Makefile.config
 
-INCLUDES = -I$(SRCDIR)/$(FIASCOSUBDIR) -I$(SRCDIR)/$(FIASCOSUBDIR)/lib \
+COMP_INCLUDES = -I$(SRCDIR)/$(FIASCOSUBDIR) -I$(SRCDIR)/$(FIASCOSUBDIR)/lib \
 	 -I$(SRCDIR)/$(FIASCOSUBDIR)/input -I$(SRCDIR)/$(FIASCOSUBDIR)/output 
 
 OBJECTS =  approx.o bintree.o coder.o coeff.o \
diff --git a/converter/other/fiasco/input/Makefile b/converter/other/fiasco/input/Makefile
index c01af772..6cd8e34b 100644
--- a/converter/other/fiasco/input/Makefile
+++ b/converter/other/fiasco/input/Makefile
@@ -13,8 +13,8 @@ OBJECTS =  basis.o matrices.o mc.o nd.o read.o tree.o weights.o
 
 MERGE_OBJECTS = $(OBJECTS)
 
-INCLUDES = -I$(SRCDIR)/$(FIASCOSUBDIR) -I$(SRCDIR)/$(FIASCOSUBDIR)/lib \
-	   -I$(SRCDIR)/$(FIASCOSUBDIR)/codec
+COMP_INCLUDES = -I$(SRCDIR)/$(FIASCOSUBDIR) -I$(SRCDIR)/$(FIASCOSUBDIR)/lib \
+	        -I$(SRCDIR)/$(FIASCOSUBDIR)/codec
 
 all: libfiasco_input.a
 
diff --git a/converter/other/fiasco/lib/Makefile b/converter/other/fiasco/lib/Makefile
index 99d7c1d7..bd129016 100644
--- a/converter/other/fiasco/lib/Makefile
+++ b/converter/other/fiasco/lib/Makefile
@@ -21,7 +21,7 @@ OBJECTS = \
 
 MERGE_OBJECTS = $(OBJECTS)
 
-INCLUDES = -I$(SRCDIR)/$(FIASCOSUBDIR)
+COMP_INCLUDES = -I$(SRCDIR)/$(FIASCOSUBDIR)
 
 all: libfiasco_lib.a
 
diff --git a/converter/other/fiasco/output/Makefile b/converter/other/fiasco/output/Makefile
index 3bdc4635..fc1d4155 100644
--- a/converter/other/fiasco/output/Makefile
+++ b/converter/other/fiasco/output/Makefile
@@ -12,9 +12,10 @@ OBJECTS =  matrices.o mc.o nd.o tree.o weights.o write.o
 
 MERGE_OBJECTS = $(OBJECTS)
 
-INCLUDES = -I$(SRCDIR)/$(FIASCOSUBDIR) \
-	   -I$(SRCDIR)/$(FIASCOSUBDIR)/lib \
-	   -I$(SRCDIR)/$(FIASCOSUBDIR)/codec 
+COMP_INCLUDES = \
+  -I$(SRCDIR)/$(FIASCOSUBDIR) \
+  -I$(SRCDIR)/$(FIASCOSUBDIR)/lib \
+  -I$(SRCDIR)/$(FIASCOSUBDIR)/codec 
 
 all: libfiasco_output.a
 
diff --git a/converter/other/jbig/Makefile b/converter/other/jbig/Makefile
index ca98ef29..4be21582 100644
--- a/converter/other/jbig/Makefile
+++ b/converter/other/jbig/Makefile
@@ -9,9 +9,9 @@ include $(BUILDDIR)/Makefile.config
 
 LIBJBIG_OBJECTS = jbig.o jbig_tab.o
 
-INCLUDES =
+EXTERN_INCLUDES =
 ifneq ($(JBIGHDR_DIR),NONE)
-  INCLUDES += -I$(JBIGHDR_DIR)
+  EXTERN_INCLUDES += -I$(JBIGHDR_DIR)
 endif
 
 ifneq ($(JBIGHDR_DIR),NONE)
diff --git a/converter/other/jpeg2000/Makefile b/converter/other/jpeg2000/Makefile
index 70cfafb7..c5e74361 100644
--- a/converter/other/jpeg2000/Makefile
+++ b/converter/other/jpeg2000/Makefile
@@ -9,9 +9,9 @@ SUBDIRS = libjasper
 
 include $(BUILDDIR)/Makefile.config
 
-INCLUDES =
+EXTERN_INCLUDES =
 ifneq ($(JASPERHDR_DIR),NONE)
-  INCLUDES += -I$(JASPERHDR_DIR)
+  EXTERN_INCLUDES += -I$(JASPERHDR_DIR)
 endif
 
 
diff --git a/converter/other/jpeg2000/libjasper/Makefile.common b/converter/other/jpeg2000/libjasper/Makefile.common
index 71a11832..84781769 100644
--- a/converter/other/jpeg2000/libjasper/Makefile.common
+++ b/converter/other/jpeg2000/libjasper/Makefile.common
@@ -17,10 +17,10 @@ $(SUBDIRS:%=%/partlist): %/partlist: $(CURDIR)/%
 	$(MAKE) -C $(dir $@) -f $(SRCDIR)/$(SUBDIR)/$(dir $@)Makefile \
 	    SRCDIR=$(SRCDIR) BUILDDIR=$(BUILDDIR) $(notdir $@) 
 
-INCLUDES = -I$(JASPERSRCDIR)/include -I$(JASPERSRCDIR)/importinc
-
 include $(SRCDIR)/Makefile.common
 
+INCLUDES = -I$(JASPERSRCDIR)/include -I$(JASPERSRCDIR)/importinc
+
 DEFS = -DHAVE_LIBM=1 -DSTDC_HEADERS=1 -DHAVE_FCNTL_H=1 -DHAVE_LIMITS_H=1 -DHAVE_UNISTD_H=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STDDEF_H=1 -DEXCLUDE_BMP_SUPPORT -DEXCLUDE_RAS_SUPPORT -DEXCLUDE_MIF_SUPPORT -DEXCLUDE_JPG_SUPPORT -DEXCLUDE_PGX_SUPPORT -DEXCLUDE_PNM_SUPPORT
 
 $(LIB_OBJECTS):%.o:%.c
diff --git a/converter/other/pamtosvg/pamtosvg.test b/converter/other/pamtosvg/pamtosvg.test
index df3a07d3..3217287e 100644
--- a/converter/other/pamtosvg/pamtosvg.test
+++ b/converter/other/pamtosvg/pamtosvg.test
@@ -1,6 +1,8 @@
+echo "Test 1.  Should print nothing"
 # This will print nothing if successful (diff will find no difference)
-ppmmake black 20 20 | ppmdraw -script="line 5 2 15 17" | pamtosvg | \
+ppmmake black 20 20 | ppmdraw -script="line 5 2 15 17" | ./pamtosvg | \
   diff testline.svg -
 
+echo "Test 2.  Should print nothing"
 # This will print nothing if successful (diff will find no difference)
-pamtosvg ../../../../testgrid.pbm | diff testgrid.svg -
+./pamtosvg ../../../testgrid.pbm | diff testgrid.svg -
diff --git a/converter/other/pnmtopalm/Makefile b/converter/other/pnmtopalm/Makefile
index 2a76297e..d7bf2829 100644
--- a/converter/other/pnmtopalm/Makefile
+++ b/converter/other/pnmtopalm/Makefile
@@ -24,8 +24,8 @@ $(BINARIES): %: %.o palmcolormap.o $(NETPBMLIB) $(LIBOPT)
 	$(LD) $(LDFLAGS) -o $@ $< palmcolormap.o $(LIBOPTS) \
 	  $(MATHLIB) $(LDLIBS) $(RPATH) $(LADD)
 
-gen_palm_colormap : $(SUBDIR)/gen_palm_colormap.c palmcolormap.o
-	$(CC) $(INCLUDES) $(CFLAGS) $(LDFLAGS) -o $@ $< palmcolormap.o \
+gen_palm_colormap : % : %.c palmcolormap.o
+	$(CC) -I importinc $(CFLAGS) $(LDFLAGS) -o $@ $< palmcolormap.o \
 	  $(LIBOPTS) $(MATHLIB) $(LDLIBS) $(LADD)
 
 
diff --git a/converter/other/pnmtopalm/gen_palm_colormap.c b/converter/other/pnmtopalm/gen_palm_colormap.c
index 4b65e631..b71854b2 100644
--- a/converter/other/pnmtopalm/gen_palm_colormap.c
+++ b/converter/other/pnmtopalm/gen_palm_colormap.c
@@ -7,18 +7,25 @@
 
 #include "palm.h"
 
-int main( int argc, char **argv ) {
-
-  int i;
-  Color_s current;
-  Colormap default_map = palmcolor_build_default_8bit_colormap ();
-
-  printf("P3\n%d 1\n255\n", default_map->ncolors);
-  for (i = 0;  i < default_map->ncolors;  i++) {
-    current = default_map->color_entries[i];
-    printf ("%d %d %d\n", (current & 0xFF0000) >> 16, (current & 0xFF00) >> 8, (current & 0xFF));
-    /* printf ("%x:  %d %d %d\n", (current & 0xFF000000) >> 24, (current & 0xFF0000) >> 16, (current & 0xFF00) >> 8, (current & 0xFF)); */
-  };
-  return 0;
+int
+main(int     argc,
+     char ** argv) {
+
+    Colormap const defaultMap = palmcolor_build_default_8bit_colormap();
+
+    unsigned int i;
+    
+    printf("P3\n%d 1\n255\n", defaultMap->ncolors);
+
+    for (i = 0; i < defaultMap->ncolors; ++i) {
+        Color_s const current = defaultMap->color_entries[i];
+
+        printf("%u %u %u\n",
+               (unsigned char)(current >> 16),
+               (unsigned char)(current >>  8),
+               (unsigned char)(current >>  0));
+    }
+
+    return 0;
 }
 
diff --git a/converter/pbm/pbmtonokia.c b/converter/pbm/pbmtonokia.c
index 214958c5..3c47eac3 100644
--- a/converter/pbm/pbmtonokia.c
+++ b/converter/pbm/pbmtonokia.c
@@ -1,231 +1,510 @@
-/* pbmtonokia.c - convert a portable bitmap to Nokia Smart Messaging
+/* pbmtonokia.c - convert a PBM image to Nokia Smart Messaging
    Formats (NOL, NGG, HEX)
 
-** Copyright (C)2001 OMS Open Media System GmbH, Tim Rühsen
-** <tim.ruehsen@openmediasystem.de>.
-**
-** 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.
+   Copyright information is at end of file.
+*/
 
-History
-  07.06.2001 Created
-  20.11.2001 Handle Picture Messages
-             new option -txt to embed text into Picture Messages
-             new option -net to specify operator network code for 
-                Nokia Operator Logos
+#define _BSD_SOURCE    /* Make sure strcasecmp() is in string.h */
+#include <string.h>
+#include <assert.h>
 
-Notes:
-  - limited to rows <= 255 and columns <= 255
-  - limited to b/w graphics, not animated
+#include "pm_c_util.h"
+#include "nstring.h"
+#include "mallocvar.h"
+#include "shhopt.h"
+#include "pbm.h"
 
-Testing:
-  Testing was done with SwissCom SMSC (Switzerland) and IC3S SMSC (Germany).
-  The data was send with EMI/UCP protocol over TCP/IP.
+enum outputFormat {
+    FMT_HEX_NOL,
+    FMT_HEX_NGG,
+    FMT_HEX_NPM,
+    FMT_NOL,
+    FMT_NGG,
+    FMT_NPM
+};
 
-  - 7.6.2001: tested with Nokia 3210: 72x14 Operator Logo
-  - 7.6.2001: tested with Nokia 6210: 72x14 Operator Logo and 
-              72x14 Group Graphic
 
-Todo:
-  - more testing
-  - sendsms compatibility ?
-  - are -fmt NOL and -fmt NGG working ok?  */
+struct cmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;  /* Filename of input files */
+    int outputFormat;
+    const char * networkCode;
+    const char * txt;  /* NULL means unspecified */
+};
 
-#define _BSD_SOURCE    /* Make sure strcasecmp() is in string.h */
-#include <string.h>
 
-#include "nstring.h"
-#include "pbm.h"
 
-#define FMT_HEX_NOL   1
-#define FMT_HEX_NGG   2
-#define FMT_HEX_NPM   3
-#define FMT_NOL       4
-#define FMT_NGG       5
-
-static void 
-usage(char *myname)
-{
-    pm_message("Copyright (C)2001 OMS GmbH");
-    pm_message("Contact: Tim Ruehsen <tim.ruehsen@openmediasystem.de>\n");
-    pm_usage("[options] [pbmfile]\n"
-             "  Options:\n"
-             "    -fmt <HEX_NOL|HEX_NGG|HEX_NPM|NOL|NGG>  "
-             "Output format (default=HEX_NOL)\n"
-             "    -net <network code>                     "
-             "Network code for NOL operator logos\n"
-             "    -txt <text message>                     "
-             "Text for NPM picture messages\n");
-
-    exit(1);
+static const char *
+uppercase(const char * const subject) {
+
+    char * buffer;
+
+    buffer = malloc(strlen(subject) + 1);
+
+    if (buffer == NULL)
+        pm_error("Out of memory allocating buffer for uppercasing a "
+                 "%u-character string", strlen(subject));
+    else {
+        unsigned int i;
+
+        i = 0;
+        while (subject[i]) {
+            buffer[i] = TOUPPER(subject[i]);
+            ++i;
+        }
+        buffer[i] = '\0';
+    }
+    return buffer;
 }
 
-int 
-main(int argc, char *argv[])
-{
-    FILE    *fp;
-    bit    **image;
-    unsigned int    c;
-    int    argpos, output=FMT_HEX_NOL, rows, cols, row, col, p, it, len;
-    char    header[32], *myname;
-    char    network_code[6+1];
-    char    *text=NULL;
-
-    if ((myname=strrchr(argv[0],'/'))!=NULL) myname++; else myname=argv[0];
 
-    pbm_init(&argc, argv);
 
-    strcpy(network_code, "62F210"); /* default is German D1 net */
-
-    for(argpos=1;argpos<argc;argpos++) {
-        if (argv[argpos][0]=='-') {
-            if (argv[argpos][1]=='-') {
-                if (argc>argpos+1 && ISDIGIT(argv[argpos+1][0]))
-                    {argpos++;break;}
-            } else if (STREQ(argv[argpos],"-fmt") && argc>argpos+1) {
-                ++argpos;
-                if (!strcasecmp(argv[argpos],"HEX_NOL")) output=FMT_HEX_NOL;
-                else if (!strcasecmp(argv[argpos],"HEX_NGG")) 
-                    output=FMT_HEX_NGG;
-                else if (!strcasecmp(argv[argpos],"HEX_NPM")) 
-                    output=FMT_HEX_NPM;
-                else if (!strcasecmp(argv[argpos],"NOL")) output=FMT_NOL;
-                else if (!strcasecmp(argv[argpos],"NGG")) output=FMT_NGG;
-                else usage(myname);
-            } else if (STREQ(argv[argpos],"-net") && argc>argpos+1) {
-                char * const network_code_arg=argv[++argpos];
-                unsigned int it;
-                len=strlen(network_code_arg);
-                if (len!=6) 
-                    pm_error("Network code must be 6 hex-digits long");
-                for (it=0;it<strlen(network_code_arg);it++) {
-                    if (!ISXDIGIT(network_code_arg[it])) 
-                        pm_error("Network code must contain hex-digits only");
-                    network_code[it]=TOUPPER(network_code_arg[it]);
-                }
-                network_code[it] = '\0';
-            } else if (STREQ(argv[argpos],"-txt") && argc>argpos+1) {
-                text=argv[++argpos];
+static void
+parseCommandLine(int argc, char ** argv,
+                 struct cmdlineInfo * const cmdlineP) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+    unsigned int fmtSpec, netSpec, txtSpec;
+    const char * fmtOpt;
+    const char * netOpt;
+
+    MALLOCARRAY_NOFAIL(option_def, 100);
+
+    option_def_index = 0;   /* incremented by OPTENT3 */
+    OPTENT3(0, "fmt",     OPT_STRING, &fmtOpt, 
+            &fmtSpec, 0);
+    OPTENT3(0, "net",     OPT_STRING, &netOpt,
+            &netSpec, 0);
+    OPTENT3(0, "txt",     OPT_STRING, &cmdlineP->txt,
+            &txtSpec, 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 (fmtSpec) {
+        if (STRCASEEQ(fmtOpt, "HEX_NOL"))
+            cmdlineP->outputFormat = FMT_HEX_NOL;
+        else if (STRCASEEQ(fmtOpt, "HEX_NGG"))
+            cmdlineP->outputFormat = FMT_HEX_NGG;
+        else if (STRCASEEQ(fmtOpt, "HEX_NPM"))
+            cmdlineP->outputFormat = FMT_HEX_NPM;
+        else if (STRCASEEQ(fmtOpt, "NOL"))
+            cmdlineP->outputFormat = FMT_NOL;
+        else if (STRCASEEQ(fmtOpt, "NGG"))
+            cmdlineP->outputFormat = FMT_NGG;
+        else if (STRCASEEQ(fmtOpt, "NPM"))
+            cmdlineP->outputFormat = FMT_NPM;
+        else
+            pm_error("-fmt option must be HEX_NGG, HEX_NOL, HEX_NPM, "
+                     "NGG, NOL or NPM.  You specified '%s'", fmtOpt);
+    } else
+        cmdlineP->outputFormat = FMT_HEX_NOL;
+
+    if (netSpec) {
+        if (strlen(netOpt) != 6)
+            pm_error("-net option must be 6 hex digits long.  "
+                     "You specified %u characters", strlen(netOpt));
+        else if (!strishex(netOpt))
+            pm_error("-net option must be hexadecimal.  You specified '%s'",
+                     netOpt);
+        else
+            cmdlineP->networkCode = uppercase(netOpt);
+    } else
+        cmdlineP->networkCode = strdup("62F210");  /* German D1 net */
+
+    if (!txtSpec)
+        cmdlineP->txt = NULL;
+    else if (strlen(cmdlineP->txt) > 120)
+        pm_error("Text message is longer (%u characters) than "
+                 "the 120 characters allowed by the format.",
+                 strlen(cmdlineP->txt));
+
+    if (argc-1 == 0) 
+        cmdlineP->inputFileName = "-";
+    else if (argc-1 != 1)
+        pm_error("Program takes zero or one argument (filename).  You "
+                 "specified %u", argc-1);
+    else
+        cmdlineP->inputFileName = argv[1];
+}
+
+
+
+static void
+freeCmdline(struct cmdlineInfo const cmdline) {
+
+    strfree(cmdline.networkCode);
+}
+
+
+
+static void
+convertToHexNol(bit **       const image,
+                unsigned int const cols,
+                unsigned int const rows,
+                const char * const networkCode,
+                FILE *       const ofP) {
+
+    unsigned int row;
+
+    /* header */
+    fprintf(ofP, "06050415820000%s00%02X%02X01", networkCode, cols, rows);
+    
+    /* image */
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        unsigned int p;
+        unsigned int c;
+
+        c = 0;
+
+        for (p = 0, col = 0; col < cols; ++col) {
+            if (image[row][col] == PBM_BLACK)
+                c |= 0x80 >> p;
+            if (++p == 8) {
+                fprintf(ofP, "%02X",c);
+                p = c = 0;
             }
-            else usage(myname);
-        } else break;
+        }
+        if (p > 0)
+            fprintf(ofP, "%02X", c);
     }
+}
 
-    if (argpos==argc) {
-        image = pbm_readpbm(stdin, &cols, &rows);
-    } else {
-        fp=pm_openr(argv[argpos]);
-        image = pbm_readpbm(fp, &cols, &rows);
-        pm_close(fp);
-    }
 
-    memset(header,0,sizeof(header));
 
-    switch (output) {
-    case FMT_HEX_NOL:
-        /* header */
-        printf("06050415820000%s00%02X%02X01",network_code,cols,rows);
-
-        /* image */
-        for (row=0;row<rows;row++) {
-            for (p=c=col=0;col<cols;col++) {
-                if (image[row][col]==PBM_BLACK) c|=0x80>>p;
-                if (++p==8) {
-                    printf("%02X",c);
-                    p=c=0;
-                }
+static void
+convertToHexNgg(bit **       const image,
+                unsigned int const cols,
+                unsigned int const rows,
+                FILE *       const ofP) {
+
+    unsigned int row;
+
+    /* header */
+    fprintf(ofP, "0605041583000000%02X%02X01", cols, rows);
+
+    /* image */
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        unsigned int p;
+        unsigned int c;
+
+        for (p = 0, c = 0, col = 0; col < cols; ++col) {
+            if (image[row][col] == PBM_BLACK)
+                c |= 0x80 >> p;
+            if (++p == 8) {
+                fprintf(ofP, "%02X", c);
+                p = c = 0;
             }
-            if (p) printf("%02X",c);
         }
-        break;
-    case FMT_HEX_NGG:
-        /* header */
-        printf("0605041583000000%02X%02X01",cols,rows);
-
-        /* image */
-        for (row=0;row<rows;row++) {
-            for (p=c=col=0;col<cols;col++) {
-                if (image[row][col]==PBM_BLACK) c|=0x80>>p;
-                if (++p==8) {
-                    printf("%02X",c);
-                    p=c=0;
-                }
+        if (p > 0)
+            fprintf(ofP, "%02X", c);
+    }
+}
+
+
+
+
+static void
+convertToHexNpm(bit **       const image,
+                unsigned int const cols,
+                unsigned int const rows,
+                const char * const text,
+                FILE *       const ofP) {
+
+    unsigned int row;
+    
+    /* header */
+    fprintf(ofP, "060504158A0000");
+
+    /* text */
+    if (text) {
+        size_t const len = strlen(text);
+
+        unsigned int it;
+
+        fprintf(ofP, "00%04X", len);
+
+        for (it = 0; it < len; ++it)
+            fprintf(ofP, "%02X", text[it]);
+    }
+
+    /* image */
+    fprintf(ofP, "02%04X00%02X%02X01", (cols * rows) / 8 + 4, cols, rows);
+
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        unsigned int p;
+        unsigned int c;
+
+        for (p = 0, c = 0, col = 0; col < cols; ++col) {
+            if (image[row][col] == PBM_BLACK)
+                c |= 0x80 >> p;
+            if (++p == 8) {
+                fprintf(ofP, "%02X", c);
+                p = c = 0;
             }
-            if (p) printf("%02X",c);
         }
-        break;
-    case FMT_HEX_NPM:
-        /* header */
-        printf("060504158A0000");
+        if (p > 0)
+            fprintf(ofP, "%02X", c);
+    }
+}
+
 
-        /* text */
-        if (text!=NULL) {
-            printf("00%04X",(len=strlen(text)));
-            for (it=0;it<len;it++) printf("%02X",text[it]);
+
+static void
+convertToNol(bit **       const image,
+             unsigned int const cols,
+             unsigned int const rows,
+             FILE *       const ofP) {
+
+    unsigned int row;
+    char header[32];
+    unsigned int it;
+    
+    /* header - this is a hack */
+
+    header[ 0] = 'N';
+    header[ 1] = 'O';
+    header[ 2] = 'L';
+    header[ 3] = 0;
+    header[ 4] = 1;
+    header[ 5] = 0;
+    header[ 6] = 4;
+    header[ 7] = 1;
+    header[ 8] = 1;
+    header[ 9] = 0;
+    header[10] = cols;
+    header[11] = 0;
+    header[12] = rows;
+    header[13] = 0;
+    header[14] = 1;
+    header[15] = 0;
+    header[16] = 1;
+    header[17] = 0;
+    header[18] = 0x53;
+    header[19] = 0;
+
+    fwrite(header, 20, 1, ofP);
+    
+    /* image */
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        unsigned int p;
+        unsigned int c;
+
+        for (p = 0, c = 0, col = 0; col < cols; ++col) {
+            char const output = image[row][col] == PBM_BLACK ? '1' : '0';
+
+            putc(output, ofP);
         }
+    }
 
-        /* image */
-        printf("02%04X00%02X%02X01",(cols*rows)/8+4,cols,rows);
-        for (row=0;row<rows;row++) {
-            for (p=c=col=0;col<cols;col++) {
-                if (image[row][col]==PBM_BLACK) c|=0x80>>p;
-                if (++p==8) {
-                    printf("%02X",c);
-                    p=c=0;
-                }
-            }
-            if (p) printf("%02X",c);
+    /* padding (to keep gnokii happy) */
+    for (it = 0; it < 8 - cols * rows % 8; ++it)
+        putc('0', ofP);
+}
+
+
+
+
+static void
+convertToNgg(bit **       const image,
+             unsigned int const cols,
+             unsigned int const rows,
+             FILE *       const ofP) {
+
+    unsigned int row;
+    char    header[32];
+    unsigned int it;
+
+    /* header - this is a hack */
+
+    header[ 0] = 'N';
+    header[ 1] = 'G';
+    header[ 2] = 'G';
+    header[ 3] = 0;
+    header[ 4] = 1;
+    header[ 5] = 0;
+    header[ 6] = cols;
+    header[ 7] = 0;
+    header[ 8] = rows;
+    header[ 9] = 0;
+    header[10] = 1;
+    header[11] = 0;
+    header[12] = 1;
+    header[13] = 0;
+    header[14] = 0x4a;
+    header[15] = 0;
+
+    fwrite(header, 16, 1, ofP);
+    
+    /* image */
+
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        unsigned int p;
+        unsigned int c;
+
+        for (p = 0, c = 0, col = 0; col < cols; ++col) {
+            char const output = image[row][col] == PBM_BLACK ? '1' : '0';
+
+            putc(output, ofP);
         }
-        break;
-    case FMT_NOL:
-        /* header - this is a hack */
-        header[0]='N';
-        header[1]='O';
-        header[2]='L';
-        header[4]=header[7]=header[8]=header[14]=header[16]=1;
-        header[6]=4;
-        header[10]=cols;
-        header[12]=rows;
-        header[18]=0x53;
-        fwrite(header,20,1,stdout);
-
-        /* image */
-        for (row=0;row<rows;row++) {
-            for (p=c=col=0;col<cols;col++) {
-                if (image[row][col]==PBM_BLACK) putchar('1');
-                else putchar('0');
+    }
+
+    /* padding (to keep gnokii happy) */
+    for (it = 0; it < 8 - cols * rows % 8; ++it)
+        putc('0', ofP);
+}
+
+
+
+static void
+convertToNpm(bit **       const image,
+             unsigned int const cols,
+             unsigned int const rows,
+             const char * const text,
+             FILE *       const ofP) {
+
+    unsigned int row;
+    char header[132];
+    size_t len;
+
+    if (text) 
+        len = strlen(text);
+    else
+        len = 0;
+
+    /* header and optional text */
+
+    header[       0] = 'N';
+    header[       1] = 'P';
+    header[       2] = 'M';
+    header[       3] = 0;
+    header[       4] = len;
+    header[       5] = 0;
+    memcpy(&header[5], text, len);
+    header[ 6 + len] = cols;
+    header[ 7 + len] = rows;
+    header[ 8 + len] = 1;
+    header[ 9 + len] = 1;
+    header[10 + len] = 0; /* unknown */
+
+    assert(10 + len < sizeof(header));
+
+    fwrite(header, 11 + len, 1, ofP);
+    
+    /* image: stream of bits, each row padded to a byte boundary
+       inspired by gnokii/common/gsm-filesystems.c
+     */
+    for (row = 0; row < rows; row++) {
+        unsigned int byteNumber;
+        int bitNumber;
+        char buffer[32];  /* picture messages are (always?) 72 x 28 */
+        unsigned int col;
+
+        byteNumber = 0;
+        bitNumber = 7;
+
+        MEMSZERO(buffer);
+
+        for (col = 0; col < cols; ++col) {
+            if (image[row][col] == PBM_BLACK)
+                buffer[byteNumber] |= (1 << bitNumber);
+            --bitNumber;
+            if (bitNumber < 0 && col < (cols - 1)) {
+                bitNumber = 7;
+                ++byteNumber;
             }
         }
+        fwrite(buffer, byteNumber + 1, 1, ofP);
+    }
+}
+
+
+
+int 
+main(int    argc,
+     char * argv[]) {
+
+    struct cmdlineInfo cmdline;
+    FILE  * ifP;
+    bit ** bits;
+    int rows, cols;
+
+    pbm_init(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+    bits = pbm_readpbm(ifP, &cols, &rows);
+    pm_close(ifP);
+
+    switch (cmdline.outputFormat) {
+    case FMT_HEX_NGG:
+        convertToHexNgg(bits, cols, rows, stdout);
+        break;
+    case FMT_HEX_NOL:
+        convertToHexNol(bits, cols, rows, cmdline.networkCode, stdout);
+        break;
+    case FMT_HEX_NPM:
+        convertToHexNpm(bits, cols, rows, cmdline.txt, stdout);
         break;
     case FMT_NGG:
-        /* header - this is a hack */
-        header[0]='N';
-        header[1]='G';
-        header[2]='G';
-        header[4]=header[10]=header[12]=1;
-        header[6]=cols;
-        header[8]=rows;
-        header[14]=0x4a;
-        fwrite(header,16,1,stdout);
-
-        /* image */
-        for (row=0;row<rows;row++) {
-            for (p=c=col=0;col<cols;col++) {
-                if (image[row][col]==PBM_BLACK) putchar('1');
-                else putchar('0');
-            }
-        }
+        convertToNgg(bits, cols, rows, stdout);
+        break;
+    case FMT_NOL:
+        convertToNol(bits, cols, rows, stdout);
+        break;
+    case FMT_NPM:
+        convertToNpm(bits, cols, rows, cmdline.txt, stdout);
         break;
-    default:
-        pm_error("Output format %d not implemented!\n"
-                 "Contact Tim Ruehsen <tim.ruehsen@openmediasystem.de>\n",
-                 output);
-        return 1;
     }
+
+freeCmdline(cmdline);
+
     return 0;
 }
 
+
+
+/* Copyright (C)2001 OMS Open Media System GmbH, Tim Rühsen
+** <tim.ruehsen@openmediasystem.de>.
+**
+** 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.
+
+  Created 2001.06.07
+
+Notes:
+  - limited to rows <= 255 and columns <= 255
+  - limited to b/w graphics, not animated
+
+Testing:
+  Testing was done with SwissCom SMSC (Switzerland) and IC3S SMSC (Germany).
+  The data was send with EMI/UCP protocol over TCP/IP.
+
+  - 7.6.2001: tested with Nokia 3210: 72x14 Operator Logo
+  - 7.6.2001: tested with Nokia 6210: 72x14 Operator Logo and 
+              72x14 Group Graphic
+*/
diff --git a/converter/ppm/ppmtobmp.c b/converter/ppm/ppmtobmp.c
index 071f3b12..b9ac063e 100644
--- a/converter/ppm/ppmtobmp.c
+++ b/converter/ppm/ppmtobmp.c
@@ -647,7 +647,7 @@ choose_colortype_bpp(struct cmdline_info const cmdline,
             pm_error("There are too many colors in the image to "
                      "represent in the\n"
                      "number of bits per pixel you requested: %d.\n"
-                     "You may use Ppmquant to reduce the number of "
+                     "You may use Pnmquant to reduce the number of "
                      "colors in the image.",
                      cmdline.bpp);
         else
diff --git a/converter/ppm/ppmtompeg/Makefile b/converter/ppm/ppmtompeg/Makefile
index 5e923fee..6db72ea9 100644
--- a/converter/ppm/ppmtompeg/Makefile
+++ b/converter/ppm/ppmtompeg/Makefile
@@ -18,11 +18,12 @@ else
   JPEGLIBX = $(JPEGLIB)
 endif
 
-INCLUDES = -I$(SRCDIR)/$(SUBDIR)/headers 
+COMP_INCLUDES = -I$(SRCDIR)/$(SUBDIR)/headers 
 
+EXTERN_INCLUDES =
 ifneq ($(JPEGHDR_DIR),NONE)
   ifneq ($(JPEGHDR_DIR)x,x)
-    INCLUDES += -I$(JPEGHDR_DIR)
+    EXTERN_INCLUDES += -I$(JPEGHDR_DIR)
   endif
 endif
 
diff --git a/converter/ppm/xvminitoppm.c b/converter/ppm/xvminitoppm.c
index dfc76fcf..8cea1f9d 100644
--- a/converter/ppm/xvminitoppm.c
+++ b/converter/ppm/xvminitoppm.c
@@ -12,6 +12,7 @@
 
 #include <assert.h>
 #include <string.h>
+#include <errno.h>
 
 #include "pm_c_util.h"
 #include "nstring.h"
@@ -61,7 +62,8 @@ getline(FILE * const ifP,
     rc = fgets(buf, size, ifP);
     if (rc == NULL) {
         if (ferror(ifP))
-            pm_perror("read error");
+            pm_error("read error.  fgets() failed, errno=%d (%s)",
+                     errno, strerror(errno));
         else
             pm_error("unexpected EOF");
     }
diff --git a/doc/HISTORY b/doc/HISTORY
index 9e057e6d..64c0e95b 100644
--- a/doc/HISTORY
+++ b/doc/HISTORY
@@ -1,41 +1,55 @@
 See http://netpbm.sourceforge.net/history.html for a general history of
 Netpbm.
 
-
 CHANGE HISTORY 
 --------------
 
-06.09.11 BJH  Release 10.35.05
-
-              Remove some generated files from release so build works.
+06.09.21 BJH  Release 10.36.0
 
-              Properly clean thinkjettopbm.c with 'make distclean'.
+              pbmtonokia: Add plain NPM output capability.  Thanks Paul Bolle
+              <pebolle@tiscali.nl>.
 
-06.09.10 BJH  Release 10.35.04
+              pbmtonokia: Pad to 8 bytes.  Thanks Paul Bolle
+              <pebolle@tiscali.nl>.
 
-              Fix version number control.
+              pbmtonokia: Limit text to 120 bytes.  Thanks Paul Bolle
+              <pebolle@tiscali.nl>.
 
-06.09.09 BJH  Release 10.35.03
+              pamthreshold: don't gather global information, and reread
+              file, for local thresholding (to save time, resources).
+              Thanks Erik Auerswald <auerswal@unix-ag.uni-kl.de>.
 
-              Remove general history from change file
+              libnetpbm: BK color matching adjustments, change of
+              enum bk_color.  Thanks "Kenan Kalajdzic"
+              <kalajdzic@gmail.com>.
 
-06.09.03 BJH  Release 10.35.02
+              Fix version number control.
 
               pbmtonokia: fix headers of NGG and NOL to include 3 character
               magic.
 
+              Release allocated memory before longjmping from libnetpbm.
+
+              Eliminate pm_perror() in favor of informative error messages.
+
+              libnetpbm: fix rgb:r/g/b color name parsing for invalid
+              syntax.
+
+              Add pm_errormsg(), pm_setusererrormsg(), pm_setusermessage().
+
               pm_accept_to_pamtuples: fix bug: fill in pam structure.
               Thanks "Christian Schlotter" <schlotter@users.sourceforge.net>.
 
-06.08.26 BJH  Release 10.35.01
-
               Fix selection of MMX_SEE fastpath based on GNU compiler version
               number.
 
-              libnetpbm: fix rgb:r/g/b color name parsing for invalid
-              syntax.
+              Properly clean thinkjettopbm.c with 'make distclean'.
+
+              Remove some generated files from release so build works.
+
+              Remove general history from change file
 
-06.08.19 BJH  Release 10.35.00
+06.08.19 BJH  Release 10.35.0
 
               Add pgmdeshadow.
 
diff --git a/doc/INSTALL b/doc/INSTALL
index 78cf1ebf..c83b725d 100644
--- a/doc/INSTALL
+++ b/doc/INSTALL
@@ -173,6 +173,9 @@ shared libraries (which is kept in /var/ld/ld.config).  Make sure that
 path includes the directory in which you installed the Netpbm shared
 library.  You can also use the LD_LIBRARY_PATH environment variable.
 
+On AIX, the environment variable LIBPATH affects the share library search
+path.  On AIX 5.3 and newer, you can use LD_LIBRARY_PATH as well.
+
 Besides the Netpbm shared library, libnetpbm, several of the converter
 programs, e.g. Jpegtopnm, use separately distributed libraries that
 understand the graphics format involved.  You need to make sure your
diff --git a/editor/pamperspective.c b/editor/pamperspective.c
index fdf446c7..08011cc3 100644
--- a/editor/pamperspective.c
+++ b/editor/pamperspective.c
@@ -161,9 +161,9 @@ typedef struct {
          xw_ll, yw_ll, zw_ll,  xw_lr, yw_lr, zw_lr;
 
   /* Originally I planned to include the possibility to move the
-     centre of projection, that is the pixel the camera "looks at".  It
+     center of projection, that is the pixel the camera "looks at".  It
      turned out, maybe surprisingly, that this does not have any
-     effect. So now this centre is moved to (0,0).
+     effect. So now this center is moved to (0,0).
      
      Another original plan was to correct the output parameters
      depending on the lengths of the paralellograms sides or its
@@ -714,7 +714,7 @@ static void determine_world_parallelogram (world_data *const world,
   point of an image determines a line in the world.
   
   3 equations state that the 4 points form a parallelogram.  The 4th
-  equation is for normalization and states, that the centre of the
+  equation is for normalization and states, that the center of the
   parallelogram has a z-coordinate of 1.
 -----------------------------------------------------------------------------*/
 {
@@ -1021,8 +1021,8 @@ static void determine_coefficients_pixel (world_data *const world,
   Constructs ax,...,cz from xw_ul,...,zw_lr
      
   The calculations assume pixel coordinates, that is the point ul
-  corresponds to the centre of the pixel (0,0) and the point lr
-  corresponds to the centre of the pixel (width-1,height-1)
+  corresponds to the center of the pixel (0,0) and the point lr
+  corresponds to the center of the pixel (width-1,height-1)
 -----------------------------------------------------------------------------*/
 {
   number width,height;
diff --git a/editor/pamthreshold.c b/editor/pamthreshold.c
index 40260e79..4caaed9b 100644
--- a/editor/pamthreshold.c
+++ b/editor/pamthreshold.c
@@ -508,9 +508,16 @@ thresholdLocal(struct pam *       const inpamP,
 
     windowHeight = MIN(oddLocalHeight, inpamP->height);
 
-    analyzeDistribution(inpamP, &histogram, &globalRange);
-
-    computeGlobalThreshold(inpamP, histogram, globalRange, &globalThreshold);
+    /* global information is needed for dual thresholding */
+    if (cmdline.dual) {
+        analyzeDistribution(inpamP, &histogram, &globalRange);
+        computeGlobalThreshold(inpamP, histogram, globalRange,
+                               &globalThreshold);
+    } else {
+        histogram = NULL;
+        initRange(&globalRange);
+        globalThreshold = 1.0;
+    }
 
     outrow = pnm_allocpamrow(outpamP);
 
@@ -580,17 +587,17 @@ main(int argc, char **argv) {
 
     parseCommandLine(argc, argv, &cmdline);
 
-    if (cmdline.simple)
+    if (cmdline.simple || cmdline.local)
         ifP = pm_openr(cmdline.inputFileName);
     else
         ifP = pm_openr_seekable(cmdline.inputFileName);
 
-    /* threshold each image in the PAM file */
+    /* Threshold each image in the PAM file */
     eof = FALSE;
     while (!eof) {
         pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
 
-        /* set output image parameters for a bilevel image */
+        /* Set output image parameters for a bilevel image */
         outpam.size        = sizeof(outpam);
         outpam.len         = PAM_STRUCT_SIZE(tuple_type);
         outpam.file        = stdout;
@@ -605,7 +612,7 @@ main(int argc, char **argv) {
 
         pnm_writepaminit(&outpam);
 
-        /* do the thresholding */
+        /* Do the thresholding */
 
         if (cmdline.simple)
             thresholdSimple(&inpam, &outpam, cmdline.threshold);
diff --git a/editor/pbmpscale.c b/editor/pbmpscale.c
index 63f203ed..2e24f3cd 100644
--- a/editor/pbmpscale.c
+++ b/editor/pbmpscale.c
@@ -81,119 +81,125 @@ void nextrow_pscale(ifd, row)
 
 }
 
+
+
 int
-main(argc, argv)
-     int argc;
-     char *argv[];
-{
-   FILE *ifd;
-   register bit *outrow;
-   register int row, col, i, k;
-   int scale, cutoff, ucutoff ;
-   unsigned char *flags;
-
-   pbm_init( &argc, argv );
-
-   if (argc < 2)
-      pm_usage("scale [pbmfile]");
-
-   scale = atoi(argv[1]);
-   if (scale < 1)
-      pm_perror("bad scale (< 1)");
-
-   if (argc == 3)
-      ifd = pm_openr(argv[2]);
-   else
-      ifd = stdin ;
-
-   inrow[0] = inrow[1] = inrow[2] = NULL;
-   pbm_readpbminit(ifd, &columns, &rows, &format) ;
-
-   outrow = pbm_allocrow(columns*scale) ;
-   MALLOCARRAY(flags, columns);
-   if (flags == NULL) 
-       pm_error("out of memory") ;
-
-   pbm_writepbminit(stdout, columns*scale, rows*scale, 0) ;
-
-   cutoff = scale / 2;
-   ucutoff = scale - 1 - cutoff;
-   nextrow_pscale(ifd, 0);
-   for (row = 0; row < rows; row++) {
-      nextrow_pscale(ifd, row+1);
-      for (col = 0; col < columns; col++) {
-         flags[col] = 0 ;
-         for (i = 0; i != 8; i += 2) {
-            int vec = inrow[thisrow][col] != PBM_WHITE;
-            for (k = 0; k < 7; k++) {
-               int x = col + xd_pscale[(k+i)&7] ;
-               int y = thisrow + yd_pscale[(k+i)&7] ;
-               vec <<= 1;
-               if (x >=0 && x < columns && inrow[y])
-                  vec |= (inrow[y][x] != PBM_WHITE) ;
+main(int argc, char ** argv) {
+
+    FILE * ifP;
+    bit * outrow;
+    unsigned int row;
+    int scale, cutoff, ucutoff ;
+    unsigned char *flags;
+
+    pbm_init( &argc, argv );
+
+    if (argc < 2)
+        pm_usage("scale [pbmfile]");
+
+    scale = atoi(argv[1]);
+    if (scale < 1)
+        pm_error("Scale argument must be at least one.  You specified '%s'",
+                 argv[1]);
+
+    if (argc == 3)
+        ifP = pm_openr(argv[2]);
+    else
+        ifP = stdin ;
+
+    inrow[0] = inrow[1] = inrow[2] = NULL;
+    pbm_readpbminit(ifP, &columns, &rows, &format) ;
+
+    outrow = pbm_allocrow(columns*scale) ;
+    MALLOCARRAY(flags, columns);
+    if (flags == NULL) 
+        pm_error("out of memory") ;
+
+    pbm_writepbminit(stdout, columns*scale, rows*scale, 0) ;
+
+    cutoff = scale / 2;
+    ucutoff = scale - 1 - cutoff;
+    nextrow_pscale(ifP, 0);
+    for (row = 0; row < rows; ++row) {
+        unsigned int col;
+        unsigned int i;
+        nextrow_pscale(ifP, row+1);
+        for (col = 0; col < columns; ++col) {
+            unsigned int i;
+            flags[col] = 0 ;
+            for (i = 0; i != 8; i += 2) {
+                int vec = inrow[thisrow][col] != PBM_WHITE;
+                unsigned int k;
+                for (k = 0; k < 7; ++k) {
+                    int x = col + xd_pscale[(k+i)&7] ;
+                    int y = thisrow + yd_pscale[(k+i)&7] ;
+                    vec <<= 1;
+                    if (x >=0 && x < columns && inrow[y])
+                        vec |= (inrow[y][x] != PBM_WHITE) ;
+                }
+                flags[col] |= corner(vec)<<i ;
             }
-            flags[col] |= corner(vec)<<i ;
-         }
-      }
-      for (i = 0; i < scale; i++) {
-         bit *ptr = outrow ;
-         int zone = (i > ucutoff) - (i < cutoff) ;
-         int cut = (zone < 0) ? (cutoff - i) :
-                   (zone > 0) ? (i - ucutoff) : 0 ;
-
-         for (col = 0; col < columns; col++) {
-            int pix = inrow[thisrow][col] ;
-            int flag = flags[col] ;
-            int cutl, cutr ;
-
-            switch (zone) {
-            case -1:
-               switch (NW(flag)) {
-               case 0: cutl = 0; break;
-               case 1: cutl = cut; break;
-               case 2: cutl = cut ? cut-1 : 0; break;
-               case 3: cutl = (cut && cutoff > 1) ? cut-1 : cut; break;
-               default: cutl = 0;  /* Should never reach here */
-               }
-               switch (NE(flag)) {
-               case 0: cutr = 0; break;
-               case 1: cutr = cut; break;
-               case 2: cutr = cut ? cut-1 : 0; break;
-               case 3: cutr = (cut && cutoff > 1) ? cut-1 : cut; break;
-               default: cutr = 0;  /* Should never reach here */
-              }
-               break;
-            case 0:
-               cutl = cutr = 0;
-               break ;
-            case 1:
-               switch (SW(flag)) {
-               case 0: cutl = 0; break;
-               case 1: cutl = cut; break;
-               case 2: cutl = cut ? cut-1 : 0; break;
-               case 3: cutl = (cut && cutoff > 1) ? cut-1 : cut; break;
-               default: cutl = 0;  /* should never reach here */
-               }
-               switch (SE(flag)) {
-               case 0: cutr = 0; break;
-               case 1: cutr = cut; break;
-               case 2: cutr = cut ? cut-1 : 0; break;
-               case 3: cutr = (cut && cutoff > 1) ? cut-1 : cut; break;
-               default: cutr = 0;  /* should never reach here */
-               }
-               break;
-             default: cutl = 0; cutr = 0;  /* Should never reach here */
+        }
+        for (i = 0; i < scale; i++) {
+            bit *ptr = outrow ;
+            int zone = (i > ucutoff) - (i < cutoff) ;
+            int cut = (zone < 0) ? (cutoff - i) :
+                (zone > 0) ? (i - ucutoff) : 0 ;
+
+            for (col = 0; col < columns; ++col) {
+                int pix = inrow[thisrow][col] ;
+                int flag = flags[col] ;
+                int cutl, cutr;
+                unsigned int k;
+
+                switch (zone) {
+                case -1:
+                    switch (NW(flag)) {
+                    case 0: cutl = 0; break;
+                    case 1: cutl = cut; break;
+                    case 2: cutl = cut ? cut-1 : 0; break;
+                    case 3: cutl = (cut && cutoff > 1) ? cut-1 : cut; break;
+                    default: cutl = 0;  /* Should never reach here */
+                    }
+                    switch (NE(flag)) {
+                    case 0: cutr = 0; break;
+                    case 1: cutr = cut; break;
+                    case 2: cutr = cut ? cut-1 : 0; break;
+                    case 3: cutr = (cut && cutoff > 1) ? cut-1 : cut; break;
+                    default: cutr = 0;  /* Should never reach here */
+                    }
+                    break;
+                case 0:
+                    cutl = cutr = 0;
+                    break ;
+                case 1:
+                    switch (SW(flag)) {
+                    case 0: cutl = 0; break;
+                    case 1: cutl = cut; break;
+                    case 2: cutl = cut ? cut-1 : 0; break;
+                    case 3: cutl = (cut && cutoff > 1) ? cut-1 : cut; break;
+                    default: cutl = 0;  /* should never reach here */
+                    }
+                    switch (SE(flag)) {
+                    case 0: cutr = 0; break;
+                    case 1: cutr = cut; break;
+                    case 2: cutr = cut ? cut-1 : 0; break;
+                    case 3: cutr = (cut && cutoff > 1) ? cut-1 : cut; break;
+                    default: cutr = 0;  /* should never reach here */
+                    }
+                    break;
+                default: cutl = 0; cutr = 0;  /* Should never reach here */
+                }
+                for (k = 0; k < cutl; ++k) /* left part */
+                    *ptr++ = !pix ;
+                for (k = 0; k < scale-cutl-cutr; ++k)  /* center part */
+                    *ptr++ = pix ;
+                for (k = 0; k < cutr; ++k) /* right part */
+                    *ptr++ = !pix ;
             }
-            for (k = 0; k < cutl; k++) /* left part */
-               *ptr++ = !pix ;
-            for (k = 0; k < scale-cutl-cutr; k++)  /* centre part */
-               *ptr++ = pix ;
-            for (k = 0; k < cutr; k++) /* right part */
-               *ptr++ = !pix ;
-         }
-         pbm_writepbmrow(stdout, outrow, scale*columns, 0) ;
-      }
-   }
-   pm_close(ifd);
-   exit(0);
+            pbm_writepbmrow(stdout, outrow, scale*columns, 0) ;
+        }
+    }
+    pm_close(ifP);
+    return 0;
 }
diff --git a/editor/pnmcrop.c b/editor/pnmcrop.c
index 5fafbaac..4ef778c3 100644
--- a/editor/pnmcrop.c
+++ b/editor/pnmcrop.c
@@ -338,13 +338,19 @@ findBordersInFile(const char *   const borderFileName,
 
 
 
+static const char *
+ending(unsigned int const n) {
+
+    return n > 1 ? "s" : "";
+}
+
+
+
 static void
 reportOneEdge(unsigned int const oldBorderSize,
               unsigned int const newBorderSize,
               const char * const place) {
 
-#define ending(n) (((n) > 1) ? "s" : "")
-
     if (newBorderSize > oldBorderSize)
         pm_message("Adding %u pixel%s to the %u-pixel %s border",
                    newBorderSize - oldBorderSize,
@@ -376,10 +382,10 @@ reportCroppingParameters(unsigned int const oldLeftBorderSize,
         oldTopBorderSize == 0 && oldBottomBorderSize == 0)
         pm_message("No Border found.");
 
-    reportOneEdge(oldLeftBorderSize,   newLeftBorderSize,   "left"   );
-    reportOneEdge(oldRightBorderSize,  newRightBorderSize,  "right"  );
-    reportOneEdge(oldTopBorderSize,    newTopBorderSize,    "top"    );
-    reportOneEdge(oldBottomBorderSize, newBottomBorderSize, "bottom" );
+    reportOneEdge(oldLeftBorderSize,   newLeftBorderSize,   "left"  );
+    reportOneEdge(oldRightBorderSize,  newRightBorderSize,  "right" );
+    reportOneEdge(oldTopBorderSize,    newTopBorderSize,    "top"   );
+    reportOneEdge(oldBottomBorderSize, newBottomBorderSize, "bottom");
 }
 
 
diff --git a/editor/pnmrotate.c b/editor/pnmrotate.c
index 64c69e2a..fafb2f3a 100644
--- a/editor/pnmrotate.c
+++ b/editor/pnmrotate.c
@@ -705,7 +705,7 @@ shearXToOutputFile(FILE *                 const ofP,
     unsigned int row;
     xel * xelrow;
     
-    pnm_writepnminit(stdout, newcols, rows, maxval, format, 0);
+    pnm_writepnminit(ofP, newcols, rows, maxval, format, 0);
 
     xelrow = pnm_allocrow(newcols);
 
@@ -722,7 +722,7 @@ shearXToOutputFile(FILE *                 const ofP,
         shearFinal(xels[row], xelrow, cols, newcols, format, 
                    bgColor, antialias, shiftAmount, x2shearjunk);
 
-        pnm_writepnmrow(stdout, xelrow, newcols, maxval, format, 0);
+        pnm_writepnmrow(ofP, xelrow, newcols, maxval, format, 0);
     }
     pnm_freerow(xelrow);
 }
diff --git a/generator/pamstereogram.test b/generator/pamstereogram.test
index 7eb01fff..80f70ee0 100644
--- a/generator/pamstereogram.test
+++ b/generator/pamstereogram.test
@@ -20,7 +20,7 @@ echo Test 10.  Should print 1266273778 293:
 ./pamstereogram -randomseed=1 -makemask ../testgrid.pbm | cksum 
 
 echo Test 11.  Should print 3034751595 1070:
-./pamgauss 100 10 -maxval=10000 -sigma 20 | pamfunc -multiplier=500 | \
+pamgauss 100 10 -maxval=10000 -sigma 20 | pamfunc -multiplier=500 | \
   ./pamstereogram -randomseed=1 -dpi=10 -makemask | cksum
 
 # Grayscale
@@ -28,11 +28,11 @@ echo Test 11.  Should print 3034751595 1070:
 echo Test 20.  Should print 2468969328 289:
 ./pamstereogram -randomseed=1 -grayscale ../testgrid.pbm | cksum 
 echo Test 21.  Should print 1946982115 4068:
-./pamseq 1 100 | pnmtile 200 20 | \
+pamseq 1 100 | pnmtile 200 20 | \
   ./pamstereogram -randomseed=1 -dpi=10 -grayscale | \
   cksum
 echo Test 22.  Should print 2078013430 4068:
-./pamseq 1 100 | pnmtile 200 20 | \
+pamseq 1 100 | pnmtile 200 20 | \
   ./pamstereogram -randomseed=1 -dpi=10 -grayscale -maxval 255 | \
   cksum
 
@@ -41,7 +41,7 @@ echo Test 22.  Should print 2078013430 4068:
 echo Test 30.  Should print 1319392622 731:
 ./pamstereogram -randomseed=1 -color ../testgrid.pbm | cksum 
 echo Test 31.  Should print 389886159 12062:
-./pamseq 1 100 | pnmtile 200 20 | \
+pamseq 1 100 | pnmtile 200 20 | \
   ./pamstereogram -randomseed=1 -dpi=10 -color | \
   cksum
 
diff --git a/lib/Makefile b/lib/Makefile
index bd8eccae..704b838a 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -40,7 +40,7 @@ ifneq (${VMS}x,x)
 LIBOBJECTS += libpbmvms.o
 endif
 # Library objects to be linked but not built by Makefile.common:
-LIBOBJECTS_X = util/shhopt.o util/nstring.o util/filename.o
+LIBOBJECTS_X = util/shhopt.o util/nstring.o util/vasprintf.o util/filename.o
 
 MANUALS3 = libnetpbm
 MANUALS5 = pbm pgm ppm pnm pam
@@ -55,8 +55,6 @@ DATAFILES = rgb.txt
 .PHONY: all
 all: libnetpbm extra_staticlib
 
-INCLUDES = -I$(SRCDIR)/$(SUBDIR) -I. -Iimportinc
-
 SUBDIRS = util
 SCRIPTS = 
 BINARIES = 
@@ -64,6 +62,8 @@ BINARIES =
 OMIT_LIBRARY_RULE = 1
 include $(SRCDIR)/Makefile.common
 
+INCLUDES = -I$(SRCDIR)/$(SUBDIR) -I. -Iimportinc
+
 # The following must go after Makefile.common because $(LIBNETPBM) may 
 # contain a reference to $(NETPBM_MAJOR_RELEASE).
 .PHONY: libnetpbm
diff --git a/lib/libpammap.c b/lib/libpammap.c
index 9e90ade0..98c7f798 100644
--- a/lib/libpammap.c
+++ b/lib/libpammap.c
@@ -24,12 +24,13 @@
 #define HASH_SIZE 20023
 
 unsigned int
-pnm_hashtuple(struct pam * const pamP, tuple const tuple) {
+pnm_hashtuple(struct pam * const pamP,
+              tuple        const tuple) {
 /*----------------------------------------------------------------------------
    Return the hash value of the tuple 'tuple' -- i.e. an index into a hash
    table.
 -----------------------------------------------------------------------------*/
-    int i;
+    unsigned int i;
     unsigned int hash;
     const unsigned int hash_factor[] = {33023, 30013, 27011};
 
@@ -281,7 +282,7 @@ computehashrecoverable(struct pam *   const pamP,
        tuple values. 
     */
     for (row = 0; row < pamP->height && !full; ++row) {
-        int col;
+        unsigned int col;
         const tuple * tuplerow;  /* The row of tuples we are processing */
         
         if (tupleArray)
@@ -354,19 +355,20 @@ computetuplefreqhash(struct pam *   const pamP,
     rowbuffer = NULL;
     color = NULL;
 
-    if (setjmp(jmpbuf) == 0) {
-        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
-        computehashrecoverable(pamP, tupleArray, maxsize, newMaxval, sizeP,
-                               &tuplefreqhash, &rowbuffer, &color);
-        pm_setjmpbuf(origJmpbufP);
-    } else {
+    if (setjmp(jmpbuf) != 0) {
         if (color) 
             pnm_freepamtuple(color);
         if (rowbuffer)
             pnm_freepamrow(rowbuffer);
         if (tuplefreqhash)
             pnm_destroytuplehash(tuplefreqhash);
+        pm_setjmpbuf(origJmpbufP);
         pm_longjmp();
+    } else {
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+        computehashrecoverable(pamP, tupleArray, maxsize, newMaxval, sizeP,
+                               &tuplefreqhash, &rowbuffer, &color);
+        pm_setjmpbuf(origJmpbufP);
     }
     return tuplefreqhash;
 }
diff --git a/lib/libpamn.c b/lib/libpamn.c
index c7610100..2ec50e7e 100644
--- a/lib/libpamn.c
+++ b/lib/libpamn.c
@@ -10,6 +10,7 @@
 
 #include "pm_c_util.h"
 #include "mallocvar.h"
+#include "nstring.h"
 #include "pam.h"
 #include "fileio.h"
 #include "pm_gamma.h"
@@ -18,15 +19,19 @@
 
 
 
-tuplen *
-pnm_allocpamrown(const struct pam * const pamP) {
+static void
+allocpamrown(const struct pam * const pamP,
+             tuplen **          const tuplerownP,
+             const char **      const errorP) {
 /*----------------------------------------------------------------------------
    We assume that the dimensions of the image are such that arithmetic
    overflow will not occur in our calculations.  NOTE: pnm_readpaminit()
    ensures this assumption is valid.
 -----------------------------------------------------------------------------*/
-    const int bytes_per_tuple = pamP->depth * sizeof(samplen);
+    int const bytes_per_tuple = pamP->depth * sizeof(samplen);
+
     tuplen * tuplerown;
+    const char * error;
 
     /* The tuple row data structure starts with 'width' pointers to
        the tuples, immediately followed by the 'width' tuples
@@ -35,64 +40,106 @@ pnm_allocpamrown(const struct pam * const pamP) {
 
     tuplerown = malloc(pamP->width * (sizeof(tuplen *) + bytes_per_tuple));
     if (tuplerown == NULL)
-        pm_error("Out of memory allocating space for a tuple row of\n"
-                 "%d tuples by %d samples per tuple by %d bytes per sample.",
-                 pamP->width, pamP->depth, sizeof(samplen));
-
-    {
+        asprintfN(&error, "Out of memory allocating space for a tuple row of"
+                  "%u tuples by %u samples per tuple by %u bytes per sample.",
+                  pamP->width, pamP->depth, sizeof(samplen));
+    else {
         /* Now we initialize the pointers to the individual tuples to make this
            a regulation C two dimensional array.
         */
         
-        char *p;
-        int i;
+        unsigned char * p;
+        unsigned int i;
         
-        p = (char*) (tuplerown + pamP->width);  /* location of Tuple 0 */
-        for (i = 0; i < pamP->width; i++) {
+        p = (unsigned char*) (tuplerown + pamP->width);
+            /* location of Tuple 0 */
+        for (i = 0; i < pamP->width; ++i) {
             tuplerown[i] = (tuplen) p;
             p += bytes_per_tuple;
         }
+        *errorP = NULL;
+        *tuplerownP = tuplerown;
     }
-    return(tuplerown);
 }
 
 
 
-void 
-pnm_readpamrown(const struct pam * const pamP, 
-                tuplen *           const tuplenrow) {
+tuplen *
+pnm_allocpamrown(const struct pam * const pamP) {
+/*----------------------------------------------------------------------------
+   We assume that the dimensions of the image are such that arithmetic
+   overflow will not occur in our calculations.  NOTE: pnm_readpaminit()
+   ensures this assumption is valid.
+-----------------------------------------------------------------------------*/
+    const char * error;
+    tuplen * tuplerown;
 
-    /* For speed, we don't check any of the inputs for consistency 
-       here (unless it's necessary to avoid crashing).  Any consistency
-       checking should have been done by a prior call to 
-       pnm_writepaminit().
-    */
-    assert(pamP->maxval != 0);
+    allocpamrown(pamP, &tuplerown, &error);
 
-    /* Need a special case for raw PBM because it has multiple tuples (8)
-       packed into one byte.
-    */
-    if (PAM_FORMAT_TYPE(pamP->format) == PBM_TYPE) {
-        int col;
-        bit *bitrow;
-        if (pamP->depth != 1)
-            pm_error("Invalid pam structure passed to pnm_readpamrow().  "
-                     "It says PBM format, but 'depth' member is not 1.");
-        bitrow = pbm_allocrow(pamP->width);
-        pbm_readpbmrow(pamP->file, bitrow, pamP->width, pamP->format);
-        for (col = 0; col < pamP->width; col++)
-            tuplenrow[col][0] = 
-                bitrow[col] == PBM_BLACK ? 0.0 : 1.0;
+    if (error) {
+        pm_errormsg("pnm_allocpamrown() failed.  %s", error);
+        strfree(error);
+        pm_longjmp();
+    }
+
+    return tuplerown;
+}
+
+
+
+static void
+readpbmrow(const struct pam * const pamP, 
+           tuplen *           const tuplenrow) {
+
+    bit * bitrow;
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+
+    bitrow = pbm_allocrow(pamP->width);
+    
+    if (setjmp(jmpbuf) != 0) {
         pbm_freerow(bitrow);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int col;
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
+        pbm_readpbmrow(pamP->file, bitrow, pamP->width, pamP->format);
+
+        for (col = 0; col < pamP->width; ++col)
+            tuplenrow[col][0] = bitrow[col] == PBM_BLACK ? 0.0 : 1.0;
+
+        pm_setjmpbuf(origJmpbufP);
+    }
+    pbm_freerow(bitrow);
+}
+
+
+
+static void
+readpamrow(const struct pam * const pamP, 
+           tuplen *           const tuplenrow) {
+
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    tuple * tuplerow;
+    
+    tuplerow = pnm_allocpamrow(pamP);
+    
+    if (setjmp(jmpbuf) != 0) {
+        pnm_freepamrow(tuplerow);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
     } else {
         float const scaler = 1.0 / pamP->maxval;
             /* Note: multiplication is faster than division, so we divide
                once here so we can multiply many times later.
             */
-        int col;
-        tuple * tuplerow;
 
-        tuplerow = pnm_allocpamrow(pamP);
+        unsigned int col;
+
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
 
         pnm_readpamrow(pamP, tuplerow);
         for (col = 0; col < pamP->width; ++col) {
@@ -100,15 +147,16 @@ pnm_readpamrown(const struct pam * const pamP,
             for (plane = 0; plane < pamP->depth; ++plane)
                 tuplenrow[col][plane] = tuplerow[col][plane] * scaler;
         }
-        pnm_freepamrow(tuplerow);
+        pm_setjmpbuf(origJmpbufP);
     }
+    pnm_freepamrow(tuplerow);
 }
 
 
 
 void 
-pnm_writepamrown(const struct pam * const pamP, 
-                 const tuplen *     const tuplenrow) {
+pnm_readpamrown(const struct pam * const pamP, 
+                tuplen *           const tuplenrow) {
 
     /* For speed, we don't check any of the inputs for consistency 
        here (unless it's necessary to avoid crashing).  Any consistency
@@ -121,20 +169,66 @@ pnm_writepamrown(const struct pam * const pamP,
        packed into one byte.
     */
     if (PAM_FORMAT_TYPE(pamP->format) == PBM_TYPE) {
-        int col;
-        bit *bitrow;
-        bitrow = pbm_allocrow(pamP->width);
-        for (col = 0; col < pamP->width; col++)
-            bitrow[col] = 
-                tuplenrow[col][0] < 0.5 ? PBM_BLACK : PBM_WHITE;
+        if (pamP->depth != 1)
+            pm_error("Invalid pam structure passed to pnm_readpamrow().  "
+                     "It says PBM format, but 'depth' member is not 1.");
+
+        readpbmrow(pamP, tuplenrow);
+    } else
+        readpamrow(pamP, tuplenrow);
+}
+
+
+
+static void
+writepbmrow(const struct pam * const pamP, 
+            const tuplen *     const tuplenrow) {
+
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    bit * bitrow;
+
+    bitrow = pbm_allocrow(pamP->width);
+
+    if (setjmp(jmpbuf) != 0) {
+        pbm_freerow(bitrow);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int col;
+
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
+        for (col = 0; col < pamP->width; ++col)
+            bitrow[col] = tuplenrow[col][0] < 0.5 ? PBM_BLACK : PBM_WHITE;
         pbm_writepbmrow(pamP->file, bitrow, pamP->width, 
                         pamP->format == PBM_FORMAT);
-        pbm_freerow(bitrow);
+
+        pm_setjmpbuf(origJmpbufP);
+    }
+    pbm_freerow(bitrow);
+} 
+
+
+
+static void
+writepamrow(const struct pam * const pamP, 
+            const tuplen *     const tuplenrow) {
+
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    tuple * tuplerow;
+    
+    tuplerow = pnm_allocpamrow(pamP);
+    
+    if (setjmp(jmpbuf) != 0) {
+        pnm_freepamrow(tuplerow);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
     } else {
-        tuple * tuplerow;
-        int col;
+        unsigned int col;
 
-        tuplerow = pnm_allocpamrow(pamP);
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
 
         for (col = 0; col < pamP->width; ++col) {
             unsigned int plane;
@@ -143,8 +237,32 @@ pnm_writepamrown(const struct pam * const pamP,
                     (tuplenrow[col][plane] * pamP->maxval + 0.5);
         }    
         pnm_writepamrow(pamP, tuplerow);
-        pnm_freepamrow(tuplerow);
+
+        pm_setjmpbuf(origJmpbufP);
     }
+    pnm_freepamrow(tuplerow);
+}
+
+
+
+void 
+pnm_writepamrown(const struct pam * const pamP, 
+                 const tuplen *     const tuplenrow) {
+
+    /* For speed, we don't check any of the inputs for consistency 
+       here (unless it's necessary to avoid crashing).  Any consistency
+       checking should have been done by a prior call to 
+       pnm_writepaminit().
+    */
+    assert(pamP->maxval != 0);
+
+    /* Need a special case for raw PBM because it has multiple tuples (8)
+       packed into one byte.
+    */
+    if (PAM_FORMAT_TYPE(pamP->format) == PBM_TYPE)
+        writepbmrow(pamP, tuplenrow);
+    else
+        writepamrow(pamP, tuplenrow);
 }
 
 
@@ -152,8 +270,8 @@ pnm_writepamrown(const struct pam * const pamP,
 tuplen **
 pnm_allocpamarrayn(const struct pam * const pamP) {
     
-    tuplen **tuplenarray;
-    int row;
+    tuplen ** tuplenarray;
+    const char * error;
 
     /* If the speed of this is ever an issue, it might be sped up a little
        by allocating one large chunk.
@@ -161,12 +279,32 @@ pnm_allocpamarrayn(const struct pam * const pamP) {
     
     MALLOCARRAY(tuplenarray, pamP->height);
     if (tuplenarray == NULL) 
-        pm_error("Out of memory allocating the row pointer section of "
-                 "a %u row array", pamP->height);
-
-    for (row = 0; row < pamP->height; row++) {
-        tuplenarray[row] = pnm_allocpamrown(pamP);
+        asprintfN(&error,
+                  "Out of memory allocating the row pointer section of "
+                  "a %u row array", pamP->height);
+    else {
+        unsigned int rowsDone;
+
+        rowsDone = 0;
+
+        while (rowsDone < pamP->height && !error) {
+            allocpamrown(pamP, &tuplenarray[rowsDone], &error);
+            if (!error)
+                ++rowsDone;
+        }
+        if (error) {
+            unsigned int row;
+            for (row = 0; row < rowsDone; ++row)
+                pnm_freepamrown(tuplenarray[rowsDone]);
+            free(tuplenarray);
+        }
+    }
+    if (error) {
+        pm_errormsg("pnm_allocpamarrayn() failed.  %s", error);
+        strfree(error);
+        pm_longjmp();
     }
+
     return(tuplenarray);
 }
 
@@ -191,16 +329,28 @@ pnm_readpamn(FILE *       const file,
              int          const size) {
 
     tuplen **tuplenarray;
-    int row;
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
 
     pnm_readpaminit(file, pamP, size);
     
     tuplenarray = pnm_allocpamarrayn(pamP);
     
-    for (row = 0; row < pamP->height; row++) 
-        pnm_readpamrown(pamP, tuplenarray[row]);
+    if (setjmp(jmpbuf) != 0) {
+        pnm_freepamarrayn(tuplenarray, pamP);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int row;
 
-    return(tuplenarray);
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
+        for (row = 0; row < pamP->height; ++row) 
+            pnm_readpamrown(pamP, tuplenarray[row]);
+
+        pm_setjmpbuf(origJmpbufP);
+    }
+    return tuplenarray;
 }
 
 
@@ -209,11 +359,11 @@ void
 pnm_writepamn(struct pam * const pamP, 
               tuplen **    const tuplenarray) {
 
-    int row;
+    unsigned int row;
 
     pnm_writepaminit(pamP);
     
-    for (row = 0; row < pamP->height; row++) 
+    for (row = 0; row < pamP->height; ++row) 
         pnm_writepamrown(pamP, tuplenarray[row]);
 }
 
@@ -473,6 +623,8 @@ createUngammaMapOffset(const struct pam * const pamP,
    can be used in a reverse lookup to convert normalized ungamma'ed
    samplen values to integer sample values.  The 0.5 effectively does
    the rounding.
+
+   This never throws an error.  Return value NULL means failed.
 -----------------------------------------------------------------------------*/
     pnm_transformMap * retval;
     pnm_transformMap ungammaTransformMap;
diff --git a/lib/libpamread.c b/lib/libpamread.c
index 1b65745a..85701a90 100644
--- a/lib/libpamread.c
+++ b/lib/libpamread.c
@@ -18,30 +18,46 @@
 
 #include "pam.h"
 #include "fileio.h"
+#include "nstring.h"
 
 
 static void
 readPbmRow(const struct pam * const pamP,
            tuple *            const tuplerow) {
 
-    unsigned char *bitrow;
     if (pamP->depth != 1)
         pm_error("Invalid pam structure passed to pnm_readpamrow().  "
                  "It says PBM format, but 'depth' member is not 1.");
+    else {
+        jmp_buf jmpbuf;
+        jmp_buf * origJmpbufP;
+        unsigned char * bitrow;
+
+        bitrow = (unsigned char *) pbm_allocrow(pbm_packed_bytes(pamP->width));
+
+        if (setjmp(jmpbuf) != 0) {
+            pbm_freerow(bitrow);
+            pm_setjmpbuf(origJmpbufP);
+            pm_longjmp();
+        } else {
+            pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
+            pbm_readpbmrow_packed(pamP->file, bitrow, pamP->width,
+                                  pamP->format);
     
-    bitrow = (unsigned char *) pbm_allocrow(pbm_packed_bytes(pamP->width));
-    pbm_readpbmrow_packed(pamP->file, bitrow, pamP->width, pamP->format);
-    
-    if (tuplerow) {
-        int col;
-        for (col = 0; col < pamP->width; ++col) {
-            tuplerow[col][0] = 
-                ( ((bitrow[col/8] >> (7-col%8)) & 1 ) == PBM_BLACK)
-                ? PAM_PBM_BLACK : PAM_PBM_WHITE
-                ;
+            if (tuplerow) {
+                unsigned int col;
+                for (col = 0; col < pamP->width; ++col) {
+                    tuplerow[col][0] = 
+                        ( ((bitrow[col/8] >> (7-col%8)) & 1 ) == PBM_BLACK)
+                        ? PAM_PBM_BLACK : PAM_PBM_WHITE
+                        ;
+                }
+            }
+            pm_setjmpbuf(origJmpbufP);
         }
-    }   
-    pbm_freerow(bitrow);
+        pbm_freerow(bitrow);
+    }
 }
 
 
@@ -185,6 +201,7 @@ readRawNonPbmRow(const struct pam * const pamP,
 
     unsigned char * inbuf;
     size_t bytesRead;
+    const char * error;
 
     inbuf = pnm_allocrowimage(pamP);
     
@@ -192,25 +209,34 @@ readRawNonPbmRow(const struct pam * const pamP,
 
     if (bytesRead != rowImageSize) {
         if (feof(pamP->file))
-            pm_error("End of file encountered when trying to read a row from "
-                     "input file.");
+            asprintfN(&error,
+                      "End of file encountered when trying to read a row from "
+                      "input file.");
         else 
-            pm_error("Error reading a row from input file.  "
-                     "fread() fails with errno=%d (%s)",
-                     errno, strerror(errno));
-    }
-    if (tuplerow) {
-        switch (pamP->bytes_per_sample) {
-        case 1: parse1BpsRow(pamP, tuplerow, inbuf); break;
-        case 2: parse2BpsRow(pamP, tuplerow, inbuf); break;
-        case 3: parse3BpsRow(pamP, tuplerow, inbuf); break;
-        case 4: parse4BpsRow(pamP, tuplerow, inbuf); break;
-        default:
-            pm_error("invalid bytes per sample passed to "
-                     "pnm_formatpamrow(): %u",  pamP->bytes_per_sample);
+            asprintfN(&error, "Error reading a row from input file.  "
+                      "fread() fails with errno=%d (%s)",
+                      errno, strerror(errno));
+    } else {
+        error = NULL;  /* initial assumption */
+        if (tuplerow) {
+            switch (pamP->bytes_per_sample) {
+            case 1: parse1BpsRow(pamP, tuplerow, inbuf); break;
+            case 2: parse2BpsRow(pamP, tuplerow, inbuf); break;
+            case 3: parse3BpsRow(pamP, tuplerow, inbuf); break;
+            case 4: parse4BpsRow(pamP, tuplerow, inbuf); break;
+            default:
+                asprintfN(&error, "invalid bytes per sample passed to "
+                          "pnm_formatpamrow(): %u",  pamP->bytes_per_sample);
+            }
         }
     }
     pnm_freerowimage(inbuf);
+
+    if (error) {
+        pm_errormsg("%s", error);
+        strfree(error);
+        pm_longjmp();
+    }
 }
 
 
@@ -263,15 +289,27 @@ pnm_readpam(FILE *       const fileP,
             struct pam * const pamP, 
             int          const size) {
 
-    tuple **tuplearray;
-    int row;
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    tuple ** tuplearray;
 
     pnm_readpaminit(fileP, pamP, size);
     
     tuplearray = pnm_allocpamarray(pamP);
     
-    for (row = 0; row < pamP->height; row++) 
-        pnm_readpamrow(pamP, tuplearray[row]);
-
+    if (setjmp(jmpbuf) != 0) {
+        pnm_freepamarray(tuplearray, pamP);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int row;
+
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+            
+        for (row = 0; row < pamP->height; ++row) 
+            pnm_readpamrow(pamP, tuplearray[row]);
+
+        pm_setjmpbuf(origJmpbufP);
+    }
     return tuplearray;
 }
diff --git a/lib/libpamwrite.c b/lib/libpamwrite.c
index 9184a4b5..83f0f41b 100644
--- a/lib/libpamwrite.c
+++ b/lib/libpamwrite.c
@@ -308,22 +308,33 @@ writePamRawRow(const struct pam * const pamP,
    Write mutiple ('count') copies of the same row ('tuplerow') to the file,
    in raw (not plain) format.
 -----------------------------------------------------------------------------*/
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
     unsigned int rowImageSize;
-
     unsigned char * outbuf;  /* malloc'ed */
-    unsigned int i;
 
     outbuf = pnm_allocrowimage(pamP);
 
     pnm_formatpamrow(pamP, tuplerow, outbuf, &rowImageSize);
 
-    for (i = 0; i < count; ++i) {
-        size_t bytesWritten;
-
-        bytesWritten = fwrite(outbuf, 1, rowImageSize, pamP->file);
-        if (bytesWritten != rowImageSize)
-            pm_error("fwrite() failed to write an image row to the file.  "
-                     "errno=%d (%s)", errno, strerror(errno));
+    if (setjmp(jmpbuf) != 0) {
+        pnm_freerowimage(outbuf);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int i;
+
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+        
+        for (i = 0; i < count; ++i) {
+            size_t bytesWritten;
+            
+            bytesWritten = fwrite(outbuf, 1, rowImageSize, pamP->file);
+            if (bytesWritten != rowImageSize)
+                pm_error("fwrite() failed to write an image row to the file.  "
+                         "errno=%d (%s)", errno, strerror(errno));
+        }
+        pm_setjmpbuf(origJmpbufP);
     }
     pnm_freerowimage(outbuf);
 }
diff --git a/lib/libpbm3.c b/lib/libpbm3.c
index 2e7b922c..a5d5ea62 100644
--- a/lib/libpbm3.c
+++ b/lib/libpbm3.c
@@ -58,8 +58,8 @@ static void
 packBitsWithMmxSse(FILE *          const fileP,
                    const bit *     const bitrow,
                    unsigned char * const packedBits,
-                   int             const cols,
-                   int *           const nextColP) {
+                   unsigned int    const cols,
+                   unsigned int *  const nextColP) {
 /*----------------------------------------------------------------------------
    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
@@ -136,8 +136,8 @@ static void
 packBitsGeneric(FILE *          const fileP,
                 const bit *     const bitrow,
                 unsigned char * const packedBits,
-                int             const cols,
-                int *           const nextColP) {
+                unsigned int    const cols,
+                unsigned int *  const nextColP) {
 /*----------------------------------------------------------------------------
    Pack the bits of bitrow[] into byts at 'packedBits'.  Going left to right,
    stop when there aren't enough bits left to fill a whole byte.  Return
@@ -167,41 +167,63 @@ packBitsGeneric(FILE *          const fileP,
 
 
 static void
+packPartialBytes(const bit *     const bitrow,
+                 unsigned int    const cols,
+                 unsigned int    const nextCol,
+                 unsigned char * const packedBits) {
+              
+    /* routine for partial byte at the end of packedBits[]
+       Prior to addition of the above enhancement,
+       this method was used for the entire process
+    */                   
+    
+    unsigned int col;
+    int bitshift;
+    unsigned char item;
+    
+    bitshift = 7;  /* initial value */
+    item = 0;      /* initial value */
+    for (col = nextCol; col < cols; ++col, --bitshift)
+        if (bitrow[col] != 0)
+            item |= 1 << bitshift;
+    
+    packedBits[col/8] = item;
+}
+
+
+
+static void
 writePbmRowRaw(FILE *      const fileP,
                const bit * const bitrow,
                int         const cols) {
 
-    int nextCol;
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    unsigned char * packedBits;
 
-    unsigned char * const packedBits = pbm_allocrow_packed(cols);
+    packedBits = pbm_allocrow_packed(cols);
 
-    if (HAVE_MMX_SSE)
-        packBitsWithMmxSse(fileP, bitrow, packedBits, cols, &nextCol);
-    else 
-        packBitsGeneric(fileP, bitrow, packedBits, cols, &nextCol);
+    if (setjmp(jmpbuf) != 0) {
+        pbm_freerow_packed(packedBits);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int nextCol;
 
-    /* routine for partial byte at the end of packed_bits[]
-       Prior to addition of the above enhancement,
-       this method was used for the entire process
-     */                   
-
-    if (cols % 8 > 0) {
-        int col;
-        int bitshift;
-        unsigned char item;
-
-        bitshift = 7;  /* initial value */
-        item = 0;      /* initial value */
-        for (col = nextCol; col < cols; ++col, --bitshift )
-            if (bitrow[col] !=0)
-                item |= 1 << bitshift
-                ;
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
+        if (HAVE_MMX_SSE)
+            packBitsWithMmxSse(fileP, bitrow, packedBits, cols, &nextCol);
+        else 
+            packBitsGeneric(fileP, bitrow, packedBits, cols, &nextCol);
+
+        if (cols % 8 > 0)
+            packPartialBytes(bitrow, cols, nextCol, packedBits);
         
-        packedBits[col/8] = item;
+        writePackedRawRow(fileP, packedBits, cols);
+
+        pm_setjmpbuf(origJmpbufP);
     }
-    
-    writePackedRawRow(fileP, packedBits, cols);
-    
     pbm_freerow_packed(packedBits);
 }
 
@@ -244,22 +266,37 @@ pbm_writepbmrow(FILE * const fileP,
 
 void
 pbm_writepbmrow_packed(FILE *                const fileP, 
-                       const unsigned char * const packed_bits,
+                       const unsigned char * const packedBits,
                        int                   const cols, 
                        int                   const forceplain) {
 
     if (!forceplain && !pm_plain_output)
-        writePackedRawRow(fileP, packed_bits, cols);
+        writePackedRawRow(fileP, packedBits, cols);
     else {
-        bit *bitrow;
-        int col;
+        jmp_buf jmpbuf;
+        jmp_buf * origJmpbufP;
+        bit * bitrow;
 
         bitrow = pbm_allocrow(cols);
 
-        for (col = 0; col < cols; ++col) 
-            bitrow[col] = 
-                packed_bits[col/8] & (0x80 >> (col%8)) ? PBM_BLACK : PBM_WHITE;
-        writePbmRowPlain(fileP, bitrow, cols);
+        if (setjmp(jmpbuf) != 0) {
+            pbm_freerow(bitrow);
+            pm_setjmpbuf(origJmpbufP);
+            pm_longjmp();
+        } else {
+            unsigned int col;
+            
+            pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
+            for (col = 0; col < cols; ++col) 
+                bitrow[col] = 
+                    packedBits[col/8] & (0x80 >> (col%8)) ?
+                    PBM_BLACK : PBM_WHITE;
+
+            writePbmRowPlain(fileP, bitrow, cols);
+
+            pm_setjmpbuf(origJmpbufP);
+        }
         pbm_freerow(bitrow);
     }
 }
diff --git a/lib/libpgm1.c b/lib/libpgm1.c
index 75fa365b..5b17910a 100644
--- a/lib/libpgm1.c
+++ b/lib/libpgm1.c
@@ -28,6 +28,7 @@
 #include "libpam.h"
 #include "fileio.h"
 #include "mallocvar.h"
+#include "nstring.h"
 
 
 gray *
@@ -190,105 +191,166 @@ pgm_getrawsample(FILE * const file,
 
 
 
-void
-pgm_readpgmrow(FILE * const file,
+static void
+readRpgmRow(FILE * const fileP,
                gray * const grayrow, 
                int    const cols,
                gray   const maxval,
                int    const format) {
 
-    switch (format) {
-    case PGM_FORMAT: {
-        unsigned int col;
-        for (col = 0; col < cols; ++col) {
-            grayrow[col] = pm_getuint(file);
-            if (grayrow[col] > maxval)
-                pm_error("value out of bounds (%u > %u)",
-                         grayrow[col], maxval);
+    unsigned int const bytesPerSample = maxval < 256 ? 1 : 2;
+    int          const bytesPerRow    = cols * bytesPerSample;
+    
+    unsigned char * rowBuffer;
+    const char * error;
+    
+    MALLOCARRAY(rowBuffer, bytesPerRow);
+    if (rowBuffer == NULL)
+        asprintfN(&error, "Unable to allocate memory for row buffer "
+                  "for %u columns", cols);
+    else {
+        ssize_t rc;
+        rc = fread(rowBuffer, 1, bytesPerRow, fileP);
+        if (rc == 0)
+            asprintfN(&error, "Error reading row.  fread() errno=%d (%s)",
+                      errno, strerror(errno));
+        else if (rc != bytesPerRow)
+            asprintfN(&error, "Error reading row.  Short read of %u bytes "
+                      "instead of %u", rc, bytesPerRow);
+        else {
+            error = NULL;
+            if (maxval < 256) {
+                unsigned int col;
+                for (col = 0; col < cols; ++col)
+                    grayrow[col] = (gray)rowBuffer[col];
+            } else {
+                unsigned int bufferCursor;
+                unsigned int col;
+                
+                bufferCursor = 0;  /* Start at beginning of rowBuffer[] */
+                
+                for (col = 0; col < cols; ++col) {
+                    gray g;
+                    
+                    g = rowBuffer[bufferCursor++] << 8;
+                    g |= rowBuffer[bufferCursor++];
+                    
+                    grayrow[col] = g;
+                }
+            }
         }
+        free(rowBuffer);
     }
-    break;
-        
-    case RPGM_FORMAT: {
-        unsigned int const bytesPerSample = maxval < 256 ? 1 : 2;
-        int          const bytesPerRow    = cols * bytesPerSample;
+    if (error) {
+        pm_errormsg("%s", error);
+        strfree(error);
+        pm_longjmp();
+    }
+} 
 
-        unsigned char * rowBuffer;
-        ssize_t rc;
 
-        MALLOCARRAY(rowBuffer, bytesPerRow);
-        if (rowBuffer == NULL)
-            pm_error("Unable to allocate memory for row buffer "
-                     "for %u columns", cols);
 
-        rc = fread(rowBuffer, 1, bytesPerRow, file);
-        if (rc == 0)
-            pm_error("Error reading row.  fread() errno=%d (%s)",
-                     errno, strerror(errno));
-        else if (rc != bytesPerRow)
-            pm_error("Error reading row.  Short read of %u bytes "
-                     "instead of %u", rc, bytesPerRow);
+static void
+readPbmRow(FILE * const fileP,
+           gray * const grayrow, 
+           int    const cols,
+           gray   const maxval,
+           int    const format) {
+
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    bit * bitrow;
+    
+    bitrow = pbm_allocrow(cols);
+    if (setjmp(jmpbuf) != 0) {
+        pbm_freerow(bitrow);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int col;
+
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
+        pbm_readpbmrow(fileP, bitrow, cols, format);
+        for (col = 0; col < cols; ++col)
+            grayrow[col] = (bitrow[col] == PBM_WHITE ) ? maxval : 0;
 
-        if (maxval < 256) {
-            unsigned int col;
-            for (col = 0; col < cols; ++col)
-                grayrow[col] = (gray)rowBuffer[col];
-        } else {
-            unsigned int bufferCursor;
-            unsigned int col;
+        pm_setjmpbuf(origJmpbufP);
+    }
+    pbm_freerow(bitrow);
+}
 
-            bufferCursor = 0;  /* Start at beginning of rowBuffer[] */
 
-            for (col = 0; col < cols; ++col) {
-                gray g;
 
-                g = rowBuffer[bufferCursor++] << 8;
-                g |= rowBuffer[bufferCursor++];
+void
+pgm_readpgmrow(FILE * const fileP,
+               gray * const grayrow, 
+               int    const cols,
+               gray   const maxval,
+               int    const format) {
 
-                grayrow[col] = g;
-            }
+    switch (format) {
+    case PGM_FORMAT: {
+        unsigned int col;
+        for (col = 0; col < cols; ++col) {
+            grayrow[col] = pm_getuint(fileP);
+            if (grayrow[col] > maxval)
+                pm_error("value out of bounds (%u > %u)",
+                         grayrow[col], maxval);
         }
-        free(rowBuffer);
     }
+    break;
+        
+    case RPGM_FORMAT:
+        readRpgmRow(fileP, grayrow, cols, maxval, format);
         break;
     
     case PBM_FORMAT:
-    case RPBM_FORMAT: {
-        bit * bitrow;
-        int col;
-
-        bitrow = pbm_allocrow(cols);
-        pbm_readpbmrow( file, bitrow, cols, format );
-        for (col = 0; col < cols; ++col)
-            grayrow[col] = (bitrow[col] == PBM_WHITE ) ? maxval : 0;
-        pbm_freerow(bitrow);
-    }
+    case RPBM_FORMAT:
+        readPbmRow(fileP, grayrow, cols, maxval, format);
         break;
         
     default:
-        pm_error( "can't happen" );
+        pm_error("can't happen");
     }
 }
 
 
 
 gray **
-pgm_readpgm(FILE * const file,
+pgm_readpgm(FILE * const fileP,
             int *  const colsP,
             int *  const rowsP, 
             gray * const maxvalP) {
 
-    gray** grays;
-    int row;
+    gray ** grays;
+    int rows, cols;
+    gray maxval;
     int format;
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
 
-    pgm_readpgminit( file, colsP, rowsP, maxvalP, &format );
+    pgm_readpgminit(fileP, &cols, &rows, &maxval, &format);
     
-    grays = pgm_allocarray( *colsP, *rowsP );
-    
-    for ( row = 0; row < *rowsP; ++row )
-        pgm_readpgmrow( file, grays[row], *colsP, *maxvalP, format );
+    grays = pgm_allocarray(cols, rows);
+
+    if (setjmp(jmpbuf) != 0) {
+        pgm_freearray(grays, rows);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int row;
     
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
+        for (row = 0; row < rows; ++row)
+            pgm_readpgmrow(fileP, grays[row], cols, maxval, format);
+
+        pm_setjmpbuf(origJmpbufP);
+    }
+    *colsP = cols;
+    *rowsP = rows;
+    *maxvalP = maxval;
     return grays;
 }
 
diff --git a/lib/libpm.c b/lib/libpm.c
index 2e563a09..df59e6c4 100644
--- a/lib/libpm.c
+++ b/lib/libpm.c
@@ -55,7 +55,7 @@
 /* The following are set by pm_init(), then used by subsequent calls to other
    pm_xxx() functions.
    */
-static const char* pm_progname;
+static const char * pm_progname;
 static bool pm_showmessages;  
     /* Programs should display informational messages (because the user didn't
        specify the --quiet option).
@@ -76,6 +76,17 @@ static jmp_buf * pm_jmpbufP = NULL;
        NULL, which is the default value, means when a libnetpbm function
        encounters an error, it causes the process to exit.
     */
+static pm_usererrormsgfn * userErrorMsgFn = NULL;
+    /* A function to call to issue an error message.
+
+       NULL means use the library default: print to Standard Error
+    */
+
+static pm_usermessagefn * userMessageFn = NULL;
+    /* A function to call to issue an error message.
+
+       NULL means use the library default: print to Standard Error
+    */
 
 
 
@@ -108,20 +119,24 @@ pm_longjmp(void) {
 
 
 void
-pm_usage(const char usage[]) {
-    pm_error("usage:  %s %s", pm_progname, usage);
+pm_setusererrormsgfn(pm_usererrormsgfn * fn) {
+
+    userErrorMsgFn = fn;
 }
 
 
 
 void
-pm_perror(const char reason[] ) {
+pm_setusermessagefn(pm_usermessagefn * fn) {
 
-    if (reason != NULL && strlen(reason) != 0)
-        pm_error("%s - errno=%d (%s)", reason, errno, strerror(errno));
-    else
-        pm_error("Something failed with errno=%d (%s)", 
-                 errno, strerror(errno));
+    userMessageFn = fn;
+}
+
+
+
+void
+pm_usage(const char usage[]) {
+    pm_error("usage:  %s %s", pm_progname, usage);
 }
 
 
@@ -134,24 +149,64 @@ pm_message(const char format[], ...) {
     va_start(args, format);
 
     if (pm_showmessages) {
-        fprintf(stderr, "%s: ", pm_progname);
-        vfprintf(stderr, format, args);
-        fputc('\n', stderr);
+        const char * msg;
+        vasprintfN(&msg, format, args);
+
+        if (userMessageFn)
+            userMessageFn(msg);
+        else
+            fprintf(stderr, "%s: %s\n", pm_progname, msg);
+
+        strfree(msg);
     }
     va_end(args);
 }
 
 
 
+static void
+errormsg(const char * const msg) {
+
+    if (userErrorMsgFn)
+        userErrorMsgFn(msg);
+    else
+        fprintf(stderr, "%s: %s\n", pm_progname, msg);
+}
+
+
+
+void PM_GNU_PRINTF_ATTR(1,2)
+pm_errormsg(const char format[], ...) {
+
+    va_list args;
+    const char * msg;
+
+    va_start(args, format);
+
+    vasprintfN(&msg, format, args);
+    
+    errormsg(msg);
+
+    strfree(msg);
+
+    va_end(args);
+}
+
+
+
 void PM_GNU_PRINTF_ATTR(1,2)
 pm_error(const char format[], ...) {
     va_list args;
+    const char * msg;
 
     va_start(args, format);
 
-    fprintf(stderr, "%s: ", pm_progname);
-    vfprintf(stderr, format, args);
-    fputc('\n', stderr);
+    vasprintfN(&msg, format, args);
+    
+    errormsg(msg);
+
+    strfree(msg);
+
     va_end(args);
 
     pm_longjmp();
@@ -186,8 +241,64 @@ pm_freerow(char * const itrow) {
 
 
 
-char**
-pm_allocarray(int const cols, int const rows, int const size )  {
+static void
+allocarrayNoHeap(unsigned char ** const rowIndex,
+                 unsigned int     const cols,
+                 unsigned int     const rows,
+                 unsigned int     const size,
+                 const char **    const errorP) {
+
+    if (UINT_MAX / cols < size)
+        asprintfN(errorP,
+                  "Arithmetic overflow multiplying %u by %u to get the "
+                  "size of a row to allocate.", cols, size);
+    else {
+        unsigned int rowsDone;
+
+        rowsDone = 0;
+        *errorP = NULL;
+
+        while (rowsDone < rows && !*errorP) {
+            unsigned char * const rowSpace = malloc(cols * size);
+            if (rowSpace == NULL)
+                asprintfN(errorP,
+                          "Unable to allocate a %u-column by %u byte row",
+                          cols, size);
+            else
+                rowIndex[rowsDone++] = rowSpace;
+        }
+        if (*errorP) {
+            unsigned int row;
+            for (row = 0; row < rowsDone; ++row)
+                free(rowIndex[row]);
+        }
+    }
+}
+
+
+
+static unsigned char *
+allocRowHeap(unsigned int const cols,
+             unsigned int const rows,
+             unsigned int const size) {
+
+    unsigned char * retval;
+
+    if (UINT_MAX / cols / rows < size)
+        /* Too big even to request the memory ! */
+        retval = NULL;
+    else
+        retval = malloc(rows * cols * size);
+
+    return retval;
+}
+
+
+
+char **
+pm_allocarray(int const cols,
+              int const rows,
+              int const size )  {
 /*----------------------------------------------------------------------------
    Allocate an array of 'rows' rows of 'cols' columns each, with each
    element 'size' bytes.
@@ -206,38 +317,46 @@ pm_allocarray(int const cols, int const rows, int const size )  {
    We use unfragmented format if possible, but if the allocation of the
    row heap fails, we fall back to fragmented.
 -----------------------------------------------------------------------------*/
-    char** rowIndex;
-    char * rowheap;
+    unsigned char ** rowIndex;
+    const char * error;
 
     MALLOCARRAY(rowIndex, rows + 1);
     if (rowIndex == NULL)
-        pm_error("out of memory allocating row index (%u rows) for an array",
-                 rows);
-    rowheap = malloc(rows * cols * size);
-    if (rowheap == NULL) {
-        /* We couldn't get the whole heap in one block, so try fragmented
-           format.
-        */
-        unsigned int row;
-        
-        rowIndex[rows] = NULL;   /* Declare it fragmented format */
-
-        for (row = 0; row < rows; ++row) {
-            rowIndex[row] = pm_allocrow(cols, size);
-            if (rowIndex[row] == NULL)
-                pm_error("out of memory allocating Row %u "
-                         "(%u columns, %u bytes per tuple) "
-                         "of an array", row, cols, size);
-        }
-    } else {
-        /* It's unfragmented format */
-        unsigned int row;
-        rowIndex[rows] = rowheap;  /* Declare it unfragmented format */
+        asprintfN(&error,
+                  "out of memory allocating row index (%u rows) for an array",
+                  rows);
+    else {
+        unsigned char * rowheap;
 
-        for (row = 0; row < rows; ++row)
-            rowIndex[row] = &(rowheap[row * cols * size]);
+        rowheap = allocRowHeap(cols, rows, size);
+
+        if (rowheap) {
+            /* It's unfragmented format */
+
+            rowIndex[rows] = rowheap;  /* Declare it unfragmented format */
+
+            if (rowheap) {
+                unsigned int row;
+                
+                for (row = 0; row < rows; ++row)
+                    rowIndex[row] = &(rowheap[row * cols * size]);
+            }
+            error = NULL;
+        } else {
+            /* We couldn't get the whole heap in one block, so try fragmented
+               format.
+            */
+            rowIndex[rows] = NULL;   /* Declare it fragmented format */
+            
+            allocarrayNoHeap(rowIndex, cols, rows, size, &error);
+        }
     }
-    return rowIndex;
+    if (error) {
+        pm_errormsg("Couldn't allocate %u-row array.  %s", rows, error);
+        strfree(error);
+        pm_longjmp();
+    }
+    return (char **)rowIndex;
 }
 
 
@@ -807,17 +926,65 @@ mkstemp2(char * const filenameBuffer) {
 
 
 
+static void
+makeTmpfileWithTemplate(const char *  const filenameTemplate,
+                        FILE **       const filePP,
+                        const char ** const filenameP,
+                        const char ** const errorP) {
+    
+    char * filenameBuffer;  /* malloc'ed */
+
+    filenameBuffer = strdup(filenameTemplate);
+
+    if (filenameBuffer == NULL)
+        asprintfN(errorP, "Unable to allocate storage for temporary "
+                  "file name");
+    else {
+        int rc;
+        
+        rc = mkstemp2(filenameBuffer);
+        
+        if (rc < 0)
+            asprintfN(errorP,
+                      "Unable to create temporary file according to name "
+                      "pattern '%s'.  mkstemp() failed with errno %d (%s)",
+                      filenameTemplate, errno, strerror(errno));
+        else {
+            int const fd = rc;
+            
+            FILE * fileP;
+            fileP = fdopen(fd, "w+b");
+            
+            if (fileP == NULL)
+                asprintfN(errorP, "Unable to create temporary file.  "
+                          "fdopen() failed with errno %d (%s)",
+                          errno, strerror(errno));
+            else {
+                *errorP = NULL;
+                *filePP = fileP;
+                *filenameP = filenameBuffer;
+            }
+            if (*errorP) {
+                unlink(filenameBuffer);
+                close(fd);
+            }
+        }
+        if (*errorP)
+            strfree(filenameBuffer);
+    }
+}
+
+
+
 void
 pm_make_tmpfile(FILE **       const filePP,
                 const char ** const filenameP) {
 
-    int fd;
-    FILE * fileP;
     const char * filenameTemplate;
-    char * filenameBuffer;  /* malloc'ed */
     unsigned int fnamelen;
     const char * tmpdir;
     const char * dirseparator;
+    const char * error;
 
     fnamelen = strlen (pm_progname) + 10; /* "/" + "_XXXXXX\0" */
 
@@ -832,27 +999,18 @@ pm_make_tmpfile(FILE **       const filePP,
               tmpdir, dirseparator, pm_progname, "_XXXXXX");
 
     if (filenameTemplate == NULL)
-        pm_error("Unable to allocate storage for temporary file name");
-
-    filenameBuffer = strdup(filenameTemplate);
-
-    fd = mkstemp2(filenameBuffer);
-
-    if (fd < 0)
-        pm_error("Unable to create temporary file according to name "
-                 "pattern '%s'.  mkstemp() failed with "
-                 "errno %d (%s)", filenameTemplate, errno, strerror(errno));
+        asprintfN(&error,
+                  "Unable to allocate storage for temporary file name");
     else {
-        fileP = fdopen(fd, "w+b");
+        makeTmpfileWithTemplate(filenameTemplate, filePP, filenameP, &error);
 
-        if (fileP == NULL)
-            pm_error("Unable to create temporary file.  fdopen() failed "
-                     "with errno %d (%s)", errno, strerror(errno));
+        strfree(filenameTemplate);
+    }
+    if (error) {
+        pm_errormsg("%s", error);
+        strfree(error);
+        pm_longjmp();
     }
-    strfree(filenameTemplate);
-
-    *filenameP = filenameBuffer;
-    *filePP = fileP;
 }
 
 
@@ -1212,9 +1370,9 @@ pm_readmagicnumber(FILE * const ifP) {
    Oliver Trepte, oliver@fysik4.kth.se, 930613 */
 
 #define PM_BUF_SIZE 16384      /* First try this size of the buffer, then
-                                   double this until we reach PM_MAX_BUF_INC */
+                                  double this until we reach PM_MAX_BUF_INC */
 #define PM_MAX_BUF_INC 65536   /* Don't allocate more memory in larger blocks
-                                   than this. */
+                                  than this. */
 
 char *
 pm_read_unknown_size(FILE * const file, 
diff --git a/lib/libpnm1.c b/lib/libpnm1.c
index 82f99b93..536e5dc4 100644
--- a/lib/libpnm1.c
+++ b/lib/libpnm1.c
@@ -133,6 +133,74 @@ pnm_readpnminit(FILE *   const fileP,
 
 
 
+static void
+readpgmrow(FILE * const fileP,
+           xel *  const xelrow,
+           int    const cols,
+           xelval const maxval,
+           int    const format) {
+
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    gray * grayrow;
+
+    grayrow = pgm_allocrow(cols);
+
+    if (setjmp(jmpbuf) != 0) {
+        pgm_freerow(grayrow);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int col;
+
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
+        pgm_readpgmrow(fileP, grayrow, cols, (gray) maxval, format);
+
+        for (col = 0; col < cols; ++col)
+            PNM_ASSIGN1(xelrow[col], grayrow[col]);
+
+        pm_setjmpbuf(origJmpbufP);
+    }
+    pgm_freerow(grayrow);
+}
+
+
+
+static void
+readpbmrow(FILE * const fileP,
+               xel *  const xelrow,
+               int    const cols,
+               xelval const maxval,
+               int    const format) {
+
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    bit * bitrow;
+
+    bitrow = pbm_allocrow(cols);
+
+    if (setjmp(jmpbuf) != 0) {
+        pbm_freerow(bitrow);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int col;
+
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
+        pbm_readpbmrow(fileP, bitrow, cols, format);
+
+        for (col = 0; col < cols; ++col)
+            PNM_ASSIGN1(xelrow[col], bitrow[col] == PBM_BLACK ? 0 : maxval);
+
+        pm_setjmpbuf(origJmpbufP);
+    }
+    pbm_freerow(bitrow);
+}
+
+
+
 void
 pnm_readpnmrow(FILE * const fileP,
                xel *  const xelrow,
@@ -145,28 +213,13 @@ pnm_readpnmrow(FILE * const fileP,
         ppm_readppmrow(fileP, (pixel*) xelrow, cols, (pixval) maxval, format);
         break;
 
-    case PGM_TYPE: {
-        gray * grayrow;
-        unsigned int col;
-
-        grayrow = pgm_allocrow(cols);
-        pgm_readpgmrow(fileP, grayrow, cols, (gray) maxval, format);
-        for (col = 0; col < cols; ++col)
-            PNM_ASSIGN1(xelrow[col], grayrow[col]);
-        pgm_freerow(grayrow);
-    }
-    break;
+    case PGM_TYPE:
+        readpgmrow(fileP, xelrow, cols, maxval, format);
+        break;
         
-    case PBM_TYPE: {
-        bit * bitrow;
-        unsigned int col;
-        bitrow = pbm_allocrow(cols);
-        pbm_readpbmrow(fileP, bitrow, cols, format);
-        for (col = 0; col < cols; ++col)
-            PNM_ASSIGN1(xelrow[col], bitrow[col] == PBM_BLACK ? 0: maxval);
-        pbm_freerow(bitrow);
-    }
-    break;
+    case PBM_TYPE:
+        readpbmrow(fileP, xelrow, cols, maxval, format);
+        break;
 
     default:
         pm_error("INTERNAL ERROR.  Impossible format.");
@@ -182,15 +235,35 @@ pnm_readpnm(FILE *   const fileP,
             xelval * const maxvalP,
             int *    const formatP) {
 
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    int cols, rows;
+    xelval maxval;
+    int format;
     xel ** xels;
-    int row;
 
-    pnm_readpnminit(fileP, colsP, rowsP, maxvalP, formatP);
+    pnm_readpnminit(fileP, &cols, &rows, &maxval, &format);
+
+    xels = pnm_allocarray(cols, rows);
 
-    xels = pnm_allocarray(*colsP, *rowsP);
+    if (setjmp(jmpbuf) != 0) {
+        pnm_freearray(xels, rows);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int row;
 
-    for (row = 0; row < *rowsP; ++row)
-        pnm_readpnmrow(fileP, xels[row], *colsP, *maxvalP, *formatP);
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
+        for (row = 0; row < rows; ++row)
+            pnm_readpnmrow(fileP, xels[row], cols, maxval, format);
+
+        pm_setjmpbuf(origJmpbufP);
+    }
+    *colsP = cols;
+    *rowsP = rows;
+    *maxvalP = maxval;
+    *formatP = format;
 
     return xels;
 }
diff --git a/lib/libpnm2.c b/lib/libpnm2.c
index aae78d52..7e4f7e2a 100644
--- a/lib/libpnm2.c
+++ b/lib/libpnm2.c
@@ -55,6 +55,74 @@ pnm_writepnminit(FILE * const fileP,
 
 
 
+static void
+writepgmrow(FILE *       const fileP, 
+            xel *        const xelrow, 
+            unsigned int const cols, 
+            xelval       const maxval, 
+            int          const format, 
+            bool         const plainFormat) {
+    
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    gray * grayrow;
+    
+    grayrow = pgm_allocrow(cols);
+    
+    if (setjmp(jmpbuf) != 0) {
+        pgm_freerow(grayrow);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int col;
+
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+        
+        for (col = 0; col < cols; ++col)
+            grayrow[col] = PNM_GET1(xelrow[col]);
+    
+        pgm_writepgmrow(fileP, grayrow, cols, (gray) maxval, plainFormat);
+
+        pm_setjmpbuf(origJmpbufP);
+    }
+    pgm_freerow(grayrow);
+}
+
+
+
+static void
+writepbmrow(FILE *       const fileP,
+            xel *        const xelrow,
+            unsigned int const cols,
+            bool         const plainFormat) {
+
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    bit * bitrow;
+
+    bitrow = pbm_allocrow(cols);
+    
+    if (setjmp(jmpbuf) != 0) {
+        pbm_freerow(bitrow);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int col;
+
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
+        for (col = 0; col < cols; ++col)
+            bitrow[col] = PNM_GET1(xelrow[col]) == 0 ? PBM_BLACK : PBM_WHITE;
+    
+        pbm_writepbmrow(fileP, bitrow, cols, plainFormat);
+
+        pm_setjmpbuf(origJmpbufP);
+    }
+    pbm_freerow(bitrow);
+}    
+
+
+
 void
 pnm_writepnmrow(FILE * const fileP, 
                 xel *  const xelrow, 
@@ -71,35 +139,13 @@ pnm_writepnmrow(FILE * const fileP,
                         plainFormat);
         break;
 
-    case PGM_TYPE: {
-        gray* grayrow;
-        unsigned int col;
-
-        grayrow = pgm_allocrow(cols);
-
-        for (col = 0; col < cols; ++col)
-            grayrow[col] = PNM_GET1(xelrow[col]);
-
-        pgm_writepgmrow(fileP, grayrow, cols, (gray) maxval, plainFormat);
-
-        pgm_freerow( grayrow );
-    }
-    break;
-
-    case PBM_TYPE: {
-        bit* bitrow;
-        unsigned int col;
-
-        bitrow = pbm_allocrow(cols);
-
-        for (col = 0; col < cols; ++col)
-            bitrow[col] = PNM_GET1(xelrow[col]) == 0 ? PBM_BLACK : PBM_WHITE;
-
-        pbm_writepbmrow(fileP, bitrow, cols, plainFormat);
+    case PGM_TYPE:
+        writepgmrow(fileP, xelrow, cols, maxval, format, plainFormat);
+        break;
 
-        pbm_freerow(bitrow);
-    }    
-    break;
+    case PBM_TYPE:
+        writepbmrow(fileP, xelrow, cols, plainFormat);
+        break;
     
     default:
         pm_error("invalid format argument received by pnm_writepnmrow(): %d"
diff --git a/lib/libppm1.c b/lib/libppm1.c
index 57a1db7d..a7ea78cf 100644
--- a/lib/libppm1.c
+++ b/lib/libppm1.c
@@ -19,6 +19,7 @@
 #include <string.h>
 #include <stdio.h>
 #include <errno.h>
+
 #include "ppm.h"
 #include "libppm.h"
 #include "pgm.h"
@@ -29,6 +30,7 @@
 #include "libpam.h"
 #include "fileio.h"
 #include "mallocvar.h"
+#include "nstring.h"
 
 
 pixel *
@@ -150,124 +152,211 @@ ppm_readppminit(FILE *   const fileP,
 
 
 
-void
-ppm_readppmrow(FILE*  const fileP, 
-               pixel* const pixelrow, 
-               int    const cols, 
-               pixval const maxval, 
-               int    const format) {
-
-    switch (format) {
-    case PPM_FORMAT: {
-        unsigned int col;
-        for (col = 0; col < cols; ++col) {
-            pixval const r = pm_getuint(fileP);
-            pixval const g = pm_getuint(fileP);
-            pixval const b = pm_getuint(fileP);
-
-            if (r > maxval)
-                pm_error("Red sample value %u is greater than maxval (%u)",
-                         r, maxval);
-            if (g > maxval)
-                pm_error("Green sample value %u is greater than maxval (%u)",
-                         g, maxval);
-            if (b > maxval)
-                pm_error("Blue sample value %u is greater than maxval (%u)",
-                         b, maxval);
-
-            PPM_ASSIGN(pixelrow[col], r, g, b);
-        }
+static void
+readppm(FILE *       const fileP, 
+        pixel *      const pixelrow, 
+        unsigned int const cols, 
+        pixval       const maxval, 
+        int          const format) {
+
+    unsigned int col;
+
+    for (col = 0; col < cols; ++col) {
+        pixval const r = pm_getuint(fileP);
+        pixval const g = pm_getuint(fileP);
+        pixval const b = pm_getuint(fileP);
+        
+        if (r > maxval)
+            pm_error("Red sample value %u is greater than maxval (%u)",
+                     r, maxval);
+        if (g > maxval)
+            pm_error("Green sample value %u is greater than maxval (%u)",
+                     g, maxval);
+        if (b > maxval)
+            pm_error("Blue sample value %u is greater than maxval (%u)",
+                     b, maxval);
+        
+        PPM_ASSIGN(pixelrow[col], r, g, b);
     }
-    break;
+}
 
-    /* For PAM, we require a depth of 3, which means the raster format
-       is identical to Raw PPM!  How convenient.
-    */
-    case PAM_FORMAT:
-    case RPPM_FORMAT: {
-        unsigned int const bytesPerSample = maxval < 256 ? 1 : 2;
-        unsigned int const bytesPerRow    = cols * 3 * bytesPerSample;
-        
-        unsigned int bufferCursor;
-        unsigned char * rowBuffer;
-        ssize_t rc;
 
-        MALLOCARRAY(rowBuffer, bytesPerRow);
+
+static void
+readrppm(FILE *       const fileP, 
+         pixel *      const pixelrow, 
+         unsigned int const cols, 
+         pixval       const maxval, 
+         int          const format) {
+
+    unsigned int const bytesPerSample = maxval < 256 ? 1 : 2;
+    unsigned int const bytesPerRow    = cols * 3 * bytesPerSample;
         
-        if (rowBuffer == NULL)
-            pm_error("Unable to allocate memory for row buffer "
-                     "for %u columns", cols);
+    unsigned char * rowBuffer;
+    const char * error;
+    
+    MALLOCARRAY(rowBuffer, bytesPerRow);
+        
+    if (rowBuffer == NULL)
+        asprintfN(&error, "Unable to allocate memory for row buffer "
+                  "for %u columns", cols);
+    else {
+        ssize_t rc;
 
         rc = fread(rowBuffer, 1, bytesPerRow, fileP);
     
         if (feof(fileP))
-            pm_error("Unexpected EOF reading row of PPM image.");
+            asprintfN(&error, "Unexpected EOF reading row of PPM image.");
         else if (ferror(fileP))
-            pm_error("Error reading row.  fread() errno=%d (%s)",
-                     errno, strerror(errno));
+            asprintfN(&error, "Error reading row.  fread() errno=%d (%s)",
+                      errno, strerror(errno));
         else if (rc != bytesPerRow)
-            pm_error("Error reading row.  Short read of %u bytes "
-                     "instead of %u", rc, bytesPerRow);
-    
-        bufferCursor = 0;  /* start at beginning of rowBuffer[] */
+            asprintfN(&error, "Error reading row.  Short read of %u bytes "
+                      "instead of %u", rc, bytesPerRow);
+        else {
+            unsigned int bufferCursor;
+
+            error = NULL;
+
+            bufferCursor = 0;  /* start at beginning of rowBuffer[] */
         
-        if (bytesPerSample == 1) {
-            unsigned int col;
-            for (col = 0; col < cols; ++col) {
-                pixval const r = rowBuffer[bufferCursor++];
-                pixval const g = rowBuffer[bufferCursor++];
-                pixval const b = rowBuffer[bufferCursor++];
-                PPM_ASSIGN(pixelrow[col], r, g, b);
-            }
-        } else  {
-            /* two byte samples */
-            unsigned int col;
-            for (col = 0; col < cols; ++col) {
-                pixval r, g, b;
-
-                r = rowBuffer[bufferCursor++] << 8;
-                r |= rowBuffer[bufferCursor++];
-
-                g = rowBuffer[bufferCursor++] << 8;
-                g |= rowBuffer[bufferCursor++];
-
-                b = rowBuffer[bufferCursor++] << 8;
-                b |= rowBuffer[bufferCursor++];
-                
-                PPM_ASSIGN(pixelrow[col], r, g, b);
+            if (bytesPerSample == 1) {
+                unsigned int col;
+                for (col = 0; col < cols; ++col) {
+                    pixval const r = rowBuffer[bufferCursor++];
+                    pixval const g = rowBuffer[bufferCursor++];
+                    pixval const b = rowBuffer[bufferCursor++];
+                    PPM_ASSIGN(pixelrow[col], r, g, b);
+                }
+            } else  {
+                /* two byte samples */
+                unsigned int col;
+                for (col = 0; col < cols; ++col) {
+                    pixval r, g, b;
+                    
+                    r = rowBuffer[bufferCursor++] << 8;
+                    r |= rowBuffer[bufferCursor++];
+                    
+                    g = rowBuffer[bufferCursor++] << 8;
+                    g |= rowBuffer[bufferCursor++];
+                    
+                    b = rowBuffer[bufferCursor++] << 8;
+                    b |= rowBuffer[bufferCursor++];
+                    
+                    PPM_ASSIGN(pixelrow[col], r, g, b);
+                }
             }
         }
         free(rowBuffer);
     }
-    break;
+    if (error) {
+        pm_errormsg("%s", error);
+        strfree(error);
+        pm_longjmp();
+    }
+}
 
-    case PGM_FORMAT:
-    case RPGM_FORMAT: {
-        gray * const grayrow = pgm_allocrow(cols);
+
+
+static void
+readpgm(FILE *       const fileP, 
+        pixel *      const pixelrow, 
+        unsigned int const cols, 
+        pixval       const maxval, 
+        int          const format) {
+
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    gray * grayrow;
+
+    grayrow = pgm_allocrow(cols);
+
+    if (setjmp(jmpbuf) != 0) {
+        pgm_freerow(grayrow);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
         unsigned int col;
+    
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
 
         pgm_readpgmrow(fileP, grayrow, cols, maxval, format);
+
         for (col = 0; col < cols; ++col) {
             pixval const g = grayrow[col];
             PPM_ASSIGN(pixelrow[col], g, g, g);
         }
-        pgm_freerow(grayrow);
+        pm_setjmpbuf(origJmpbufP);
     }
-    break;
+    pgm_freerow(grayrow);
+}
 
-    case PBM_FORMAT:
-    case RPBM_FORMAT: {
-        bit * const bitrow = pbm_allocrow(cols);
+
+
+static void
+readpbm(FILE *       const fileP, 
+        pixel *      const pixelrow, 
+        unsigned int const cols, 
+        pixval       const maxval, 
+        int          const format) {
+
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    bit * bitrow;
+
+    bitrow = pbm_allocrow(cols);
+
+    if (setjmp(jmpbuf) != 0) {
+        pbm_freerow(bitrow);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
         unsigned int col;
 
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
         pbm_readpbmrow(fileP, bitrow, cols, format);
+
         for (col = 0; col < cols; ++col) {
             pixval const g = (bitrow[col] == PBM_WHITE) ? maxval : 0;
             PPM_ASSIGN(pixelrow[col], g, g, g);
         }
-        pbm_freerow(bitrow);
+        pm_setjmpbuf(origJmpbufP);
     }
-    break;
+    pbm_freerow(bitrow);
+}
+
+
+
+void
+ppm_readppmrow(FILE *  const fileP, 
+               pixel * const pixelrow, 
+               int     const cols, 
+               pixval  const maxval, 
+               int     const format) {
+
+    switch (format) {
+    case PPM_FORMAT:
+        readppm(fileP, pixelrow, cols, maxval, format);
+        break;
+
+    /* For PAM, we require a depth of 3, which means the raster format
+       is identical to Raw PPM!  How convenient.
+    */
+    case PAM_FORMAT:
+    case RPPM_FORMAT:
+        readrppm(fileP, pixelrow, cols, maxval, format);
+        break;
+
+    case PGM_FORMAT:
+    case RPGM_FORMAT:
+        readpgm(fileP, pixelrow, cols, maxval, format);
+        break;
+
+    case PBM_FORMAT:
+    case RPBM_FORMAT:
+        readpbm(fileP, pixelrow, cols, maxval, format);
+        break;
 
     default:
         pm_error("Invalid format code");
@@ -281,17 +370,36 @@ ppm_readppm(FILE *   const fileP,
             int *    const colsP, 
             int *    const rowsP, 
             pixval * const maxvalP) {
-    pixel** pixels;
-    int row;
+
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    pixel ** pixels;
+    int cols, rows;
+    pixval maxval;
     int format;
 
-    ppm_readppminit(fileP, colsP, rowsP, maxvalP, &format);
+    ppm_readppminit(fileP, &cols, &rows, &maxval, &format);
 
-    pixels = ppm_allocarray(*colsP, *rowsP);
+    pixels = ppm_allocarray(cols, rows);
 
-    for (row = 0; row < *rowsP; ++row)
-        ppm_readppmrow(fileP, pixels[row], *colsP, *maxvalP, format);
+    if (setjmp(jmpbuf) != 0) {
+        ppm_freearray(pixels, rows);
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        unsigned int row;
 
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+
+        for (row = 0; row < rows; ++row)
+            ppm_readppmrow(fileP, pixels[row], cols, maxval, format);
+
+        *colsP = cols;
+        *rowsP = rows;
+        *maxvalP = maxval;
+
+        pm_setjmpbuf(origJmpbufP);
+    }
     return pixels;
 }
 
diff --git a/lib/libppmcmap.c b/lib/libppmcmap.c
index a9efccbc..c1243cb6 100644
--- a/lib/libppmcmap.c
+++ b/lib/libppmcmap.c
@@ -12,9 +12,11 @@
 ** implied warranty.
 */
 
-#include "ppm.h"
-#include "libppm.h"
+#include "pm_c_util.h"
+#include "nstring.h"
 #include "mallocvar.h"
+#include "libppm.h"
+#include "ppm.h"
 #include "ppmcmap.h"
 
 #define HASH_SIZE 20023
@@ -110,94 +112,124 @@ ppm_addtocolorhist( colorhist_vector chv,
 
 
 
-colorhash_table
-ppm_alloccolorhash(void)  {
+static colorhash_table
+alloccolorhash(void)  {
     colorhash_table cht;
     int i;
 
     MALLOCARRAY(cht, HASH_SIZE);
+    if (cht) {
+        for (i = 0; i < HASH_SIZE; ++i)
+            cht[i] = NULL;
+    }
+    return cht;
+}
+
+
+
+colorhash_table
+ppm_alloccolorhash(void)  {
+    colorhash_table cht;
+
+    cht = alloccolorhash();
+
     if (cht == NULL)
         pm_error( "out of memory allocating hash table" );
 
-    for (i = 0; i < HASH_SIZE; ++i)
-        cht[i] = NULL;
-
     return cht;
 }
 
 
 
-static colorhash_table
-computecolorhash(pixel ** const pixels, 
-                 const int cols, const int rows, 
-                 const int maxcolors, int * const colorsP,
-                 FILE * const ifp, pixval const maxval, int const format) {
-/*----------------------------------------------------------------------------
-   Compute a color histogram from an image.  The input is one of two types:
+static void
+readppmrow(FILE *        const fileP, 
+           pixel *       const pixelrow, 
+           int           const cols, 
+           pixval        const maxval, 
+           int           const format,
+           const char ** const errorP) {
 
-   1) a two-dimensional array of pixels 'pixels';  In this case, 'pixels'
-      is non-NULL and 'ifp' is NULL.
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+    
+    if (setjmp(jmpbuf) != 0) {
+        pm_setjmpbuf(origJmpbufP);
+        asprintfN(errorP, "Failed to read row of image.");
+    } else {
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
 
-   2) an open file, positioned to the image data.  In this case,
-      'pixels' is NULL and 'ifp' is non-NULL.  ifp is the stream
-      descriptor for the input file, and 'maxval' and 'format' are
-      parameters of the image data in it.
-      
-      We return with the file still open and its position undefined.  
+        ppm_readppmrow(fileP, pixelrow, cols, maxval, format);
 
-   In either case, the image is 'cols' by 'rows'.
+        *errorP = NULL; /* Would have longjmped if anything went wrong */
+                
+        pm_setjmpbuf(origJmpbufP);
+    }
+}
 
-   Return the number of colors found as *colorsP.
 
-   However, if 'maxcolors' is nonzero and the number of colors is
-   greater than 'maxcolors', return a null return value and *colorsP
-   undefined.
+
+static void
+buildHashTable(FILE *          const ifP,
+               pixel **        const pixels,
+               unsigned int    const cols,
+               unsigned int    const rows,
+               pixval          const maxval,
+               int             const format,
+               unsigned int    const maxcolors,
+               colorhash_table const cht,
+               pixel *         const rowbuffer,
+               int *           const nColorsP,
+               bool *          const tooManyColorsP,
+               const char **   const errorP) {
+/*----------------------------------------------------------------------------
+  Look at all the colors in the file *ifP or array pixels[][] and add
+  them to the hash table 'cht'.
+
+  Even if we fail, we may add some colors to 'cht'.
+
+  As soon as we've seen more that 'maxcolors' colors, we quit.  In that
+  case, only, we return *tooManyColorsP == true.  That is not a failure.
+  'maxcolors' == 0 means infinity.
 -----------------------------------------------------------------------------*/
-    colorhash_table cht;
-    int row;
-    pixel * rowbuffer;  /* malloc'ed */
-        /* Buffer for a row read from the input file; undefined (but still
-           allocated) if input is not from a file.
-        */
-    
-    cht = ppm_alloccolorhash( );
-    *colorsP = 0;   /* initial value */
+    unsigned int row;
+    unsigned int nColors;
 
-    rowbuffer = ppm_allocrow(cols);
+    nColors = 0;   /* initial value */
+    *tooManyColorsP = FALSE; /* initial value */
+    *errorP = NULL;  /* initial value */
 
     /* Go through the entire image, building a hash table of colors. */
-    for (row = 0; row < rows; ++row) {
-        int col;
+    for (row = 0; row < rows && !*tooManyColorsP && !*errorP; ++row) {
+        unsigned int col;
         pixel * pixelrow;  /* The row of pixels we are processing */
 
-        if (ifp) {
-            ppm_readppmrow(ifp, rowbuffer, cols, maxval, format);
+        if (ifP) {
+            readppmrow(ifP, rowbuffer, cols, maxval, format, errorP);
             pixelrow = rowbuffer;
         } else 
             pixelrow = pixels[row];
 
-        for (col = 0; col < cols; ++col) {
+        for (col = 0; col < cols && !*tooManyColorsP && !*errorP; ++col) {
             const pixel apixel = pixelrow[col];
             const int hash = ppm_hashpixel(apixel);
             colorhist_list chl; 
 
             for (chl = cht[hash]; 
-                 chl != (colorhist_list) 0 && 
-                     !PPM_EQUAL(chl->ch.color, apixel);
+                 chl && !PPM_EQUAL(chl->ch.color, apixel);
                  chl = chl->next);
 
             if (chl)
-                chl->ch.value++;
+                ++chl->ch.value;
             else {
                 /* It's not in the hash yet, so add it (if allowed) */
-                ++(*colorsP);
-                if (maxcolors > 0 && *colorsP > maxcolors) {
-                    ppm_freecolorhash(cht);
-                    return NULL;
-                } else {
+                ++nColors;
+                if (maxcolors > 0 && nColors > maxcolors)
+                    *tooManyColorsP = TRUE;
+                else {
                     MALLOCVAR(chl);
                     if (chl == NULL)
-                        pm_error("out of memory computing hash table");
+                        asprintfN(errorP,
+                                  "out of memory computing hash table");
                     chl->ch.color = apixel;
                     chl->ch.value = 1;
                     chl->next = cht[hash];
@@ -206,31 +238,124 @@ computecolorhash(pixel ** const pixels,
             }
         }
     }
-    ppm_freerow(rowbuffer);
-    return cht;
+    *nColorsP = nColors;
+}
+
+
+
+static void
+computecolorhash(pixel **          const pixels, 
+                 unsigned int      const cols,
+                 unsigned int      const rows, 
+                 unsigned int      const maxcolors,
+                 int *             const nColorsP,
+                 FILE *            const ifP,
+                 pixval            const maxval,
+                 int               const format,
+                 colorhash_table * const chtP,
+                 const char **     const errorP) {
+/*----------------------------------------------------------------------------
+   Compute a color histogram from an image.  The input is one of two types:
+
+   1) a two-dimensional array of pixels 'pixels';  In this case, 'pixels'
+      is non-NULL and 'ifP' is NULL.
+
+   2) an open file, positioned to the image data.  In this case,
+      'pixels' is NULL and 'ifP' is non-NULL.  ifP is the stream
+      descriptor for the input file, and 'maxval' and 'format' are
+      parameters of the image data in it.
+      
+      We return with the file still open and its position undefined.  
+
+   In either case, the image is 'cols' by 'rows'.
+
+   Return the number of colors found as *colorsP.
+
+   However, if 'maxcolors' is nonzero and the number of colors is
+   greater than 'maxcolors', return a null return value and *colorsP
+   undefined.
+-----------------------------------------------------------------------------*/
+    pixel * rowbuffer;  /* malloc'ed */
+        /* Buffer for a row read from the input file; undefined (but still
+           allocated) if input is not from a file.
+        */
+
+    MALLOCARRAY(rowbuffer, cols);
+        
+    if (rowbuffer == NULL)
+        asprintfN(errorP, "Unable to allocate %u-column row buffer.", cols);
+    else {
+        colorhash_table cht;
+        bool tooManyColors;
+
+        cht = alloccolorhash();
+
+        if (cht == NULL)
+            asprintfN(errorP, "Unable to allocate color hash.");
+        else {
+            buildHashTable(ifP, pixels, cols, rows, maxval, format, maxcolors,
+                           cht, rowbuffer,
+                           nColorsP, &tooManyColors, errorP);
+                
+            if (tooManyColors) {
+                ppm_freecolorhash(cht);
+                *chtP = NULL;
+            } else
+                *chtP = cht;
+
+            if (*errorP)
+                ppm_freecolorhash(cht);
+        }
+        free(rowbuffer);
+    }
 }
 
 
 
 colorhash_table
 ppm_computecolorhash(pixel ** const pixels, 
-                     const int cols, const int rows, 
-                     const int maxcolors, int * const colorsP) {
+                     int      const cols,
+                     int      const rows, 
+                     int      const maxcolors,
+                     int *    const colorsP) {
+
+    colorhash_table cht;
+    const char * error;
+
+    computecolorhash(pixels, cols, rows, maxcolors, colorsP, 
+                     NULL, 0, 0, &cht, &error);
 
-    return computecolorhash(pixels, cols, rows, maxcolors, colorsP, 
-                            NULL, 0, 0);
+    if (error) {
+        pm_errormsg("%s", error);
+        strfree(error);
+        pm_longjmp();
+    }
+    return cht;
 }
 
 
 
 colorhash_table
-ppm_computecolorhash2(FILE * const ifp,
-                      const int cols, const int rows, 
-                      const pixval maxval, const int format, 
-                      const int maxcolors, int * const colorsP ) {
+ppm_computecolorhash2(FILE * const ifP,
+                      int    const cols,
+                      int    const rows, 
+                      pixval const maxval,
+                      int    const format, 
+                      int    const maxcolors,
+                      int *  const colorsP ) {
+
+    colorhash_table cht;
+    const char * error;
 
-    return computecolorhash(NULL, cols, rows, maxcolors, colorsP,
-                            ifp, maxval, format);
+    computecolorhash(NULL, cols, rows, maxcolors, colorsP,
+                     ifP, maxval, format, &cht, &error);
+
+    if (error) {
+        pm_errormsg("%s", error);
+        strfree(error);
+        pm_longjmp();
+    }
+    return cht;
 }
 
 
@@ -353,30 +478,50 @@ ppm_colorhashtocolorhist(colorhash_table const cht, int const maxcolors) {
 colorhash_table
 ppm_colorhisttocolorhash(colorhist_vector const chv, 
                          int              const colors) {
+
+    colorhash_table retval;
     colorhash_table cht;
-    int i, hash;
-    pixel color;
-    colorhist_list chl;
+    const char * error;
 
-    cht = ppm_alloccolorhash( );  /* Initializes to NULLs */
-
-    for (i = 0; i < colors; ++i) {
-        color = chv[i].color;
-        hash = ppm_hashpixel(color);
-        for (chl = cht[hash]; chl != (colorhist_list) 0; chl = chl->next)
-            if (PPM_EQUAL(chl->ch.color, color))
-                pm_error(
-                    "same color found twice - %d %d %d", PPM_GETR(color),
-                    PPM_GETG(color), PPM_GETB(color) );
-        MALLOCVAR(chl);
-        if (chl == NULL)
-            pm_error("out of memory");
-        chl->ch.color = color;
-        chl->ch.value = i;
-        chl->next = cht[hash];
-        cht[hash] = chl;
+    cht = alloccolorhash( );  /* Initializes to NULLs */
+    if (cht == NULL)
+        asprintfN(&error, "Unable to allocate color hash");
+    else {
+        unsigned int i;
+
+        for (i = 0, error = NULL; i < colors && !error; ++i) {
+            pixel const color = chv[i].color;
+            int const hash = ppm_hashpixel(color);
+            
+            colorhist_list chl;
+
+            for (chl = cht[hash]; chl && !error; chl = chl->next)
+                if (PPM_EQUAL(chl->ch.color, color))
+                    asprintfN(&error, "same color found twice: (%u %u %u)",
+                              PPM_GETR(color),
+                              PPM_GETG(color),
+                              PPM_GETB(color));
+            MALLOCVAR(chl);
+            if (chl == NULL)
+                asprintfN(&error, "out of memory");
+            else {
+                chl->ch.color = color;
+                chl->ch.value = i;
+                chl->next = cht[hash];
+                cht[hash] = chl;
+            }
+        }
+        if (error)
+            ppm_freecolorhash(cht);
     }
-    return cht;
+    if (error) {
+        pm_errormsg("%s", error);
+        strfree(error);
+        pm_longjmp();
+    } else
+        retval = cht;
+
+    return retval;
 }
 
 
diff --git a/lib/libppmcolor.c b/lib/libppmcolor.c
index 7cfacd29..a200ab2b 100644
--- a/lib/libppmcolor.c
+++ b/lib/libppmcolor.c
@@ -19,6 +19,7 @@
 
 #include "pm_c_util.h"
 #include "mallocvar.h"
+#include "nstring.h"
 #include "ppm.h"
 #include "colorname.h"
 
@@ -247,22 +248,6 @@ parseNewDecX11(char       const colorname[],
 
 
 
-static bool
-isHexString(char const string[],
-            int  const hexit[]) {
-
-    bool retval;
-    const char * p;
-
-    for (p = &string[0], retval = true; *p && retval == true; ++p) {
-        if (hexit[(unsigned int)*p] == -1)
-            retval = false;
-    }
-    return retval;
-}
-
-
-
 static void
 parseOldX11(char       const colorname[], 
             pixval     const maxval,
@@ -278,7 +263,7 @@ parseOldX11(char       const colorname[],
     
     computeHexTable(hexit);
 
-    if (!isHexString(&colorname[1], hexit))
+    if (!strishex(&colorname[1]))
         pm_error("Non-hexadecimal characters in #-type color specification");
 
     switch (strlen(colorname) - 1 /* (Number of hex digits) */) {
@@ -471,11 +456,12 @@ processColorfileEntry(struct colorfile_entry const ce,
                       colorhash_table        const cht,
                       const char **          const colornames,
                       pixel *                const colors,
-                      unsigned int *         const colornameIndexP) {
+                      unsigned int *         const colornameIndexP,
+                      const char **          const errorP) {
 
     if (*colornameIndexP >= MAXCOLORNAMES)
-        pm_error("Too many colors in colorname dictionary.  "
-                 "Max allowed is %u", MAXCOLORNAMES);
+        asprintfN(errorP, "Too many colors in colorname dictionary.  "
+                  "Max allowed is %u", MAXCOLORNAMES);
     else {
         pixel color;
 
@@ -487,13 +473,17 @@ processColorfileEntry(struct colorfile_entry const ce,
                file gives for each color, so we just ignore the
                current entry.  
             */
+            *errorP = NULL;
         } else {
             ppm_addtocolorhash(cht, &color, *colornameIndexP);
             colornames[*colornameIndexP] = strdup(ce.colorname);
             colors[*colornameIndexP] = color;
             if (colornames[*colornameIndexP] == NULL)
-                pm_error("Unable to allocate space for color name");
-            ++(*colornameIndexP);
+                asprintfN(errorP, "Unable to allocate space for color name");
+            else {
+                *errorP = NULL;
+                ++(*colornameIndexP);
+            }
         }
     }
 }
@@ -501,39 +491,173 @@ processColorfileEntry(struct colorfile_entry const ce,
 
 
 static void
-readcolordict(const char *    const fileName,
+openColornameFile(const char *  const fileName,
+                  bool          const mustOpen,
+                  FILE **       const filePP,
+                  const char ** const errorP) {
+
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+
+    if (setjmp(jmpbuf) != 0) {
+        asprintfN(errorP, "Failed to open color name file");
+        pm_setjmpbuf(origJmpbufP);
+        pm_longjmp();
+    } else {
+        *filePP = pm_openColornameFile(fileName, mustOpen);
+
+        *errorP = NULL;  /* Would have longjmped if there were a problem */
+
+        pm_setjmpbuf(origJmpbufP);
+    }
+}
+
+
+
+static void
+readOpenColorFile(FILE *          const colorFileP,
+                  unsigned int *  const nColorsP,
+                  const char **   const colornames,
+                  pixel *         const colors,
+                  colorhash_table const cht,
+                  const char **   const errorP) {
+/*----------------------------------------------------------------------------
+   Read the color dictionary file *colorFileP and add the colors in it
+   to colornames[], colors[], and 'cht'.
+
+   We may add colors to 'cht' even if we fail.
+-----------------------------------------------------------------------------*/
+    unsigned int nColorsDone;
+    bool done;
+
+    nColorsDone = 0;
+    done = FALSE;
+    *errorP = NULL;
+
+    while (!done && !*errorP) {
+        struct colorfile_entry const ce = pm_colorget(colorFileP);
+        
+        if (!ce.colorname)  
+            done = TRUE;
+        else 
+            processColorfileEntry(ce, cht, colornames, colors,
+                                  &nColorsDone, errorP);
+    }
+    if (!*errorP) {
+        *nColorsP = nColorsDone;
+        
+        while (nColorsDone < MAXCOLORNAMES)
+            colornames[nColorsDone++] = NULL;
+    }
+    
+    if (*errorP) {
+        unsigned int colorIndex;
+
+        for (colorIndex = 0; colorIndex < nColorsDone; ++colorIndex)
+            strfree(colornames[colorIndex]);
+    }
+}
+
+
+
+static colorhash_table
+allocColorHash(void) {
+
+    colorhash_table cht;
+    jmp_buf jmpbuf;
+    jmp_buf * origJmpbufP;
+
+    if (setjmp(jmpbuf) != 0)
+        cht = NULL;
+    else {
+        pm_setjmpbufsave(&jmpbuf, &origJmpbufP);
+        cht = ppm_alloccolorhash();
+    }
+    pm_setjmpbuf(origJmpbufP);
+
+    return cht;
+}
+
+
+
+static void
+readColorFile(const char *    const fileName,
               bool            const mustOpen,
               unsigned int *  const nColorsP,
               const char **   const colornames,
-              pixel * const   colors,
-              colorhash_table const cht) {
+              pixel *         const colors,
+              colorhash_table const cht,
+              const char **   const errorP) {
 
-    FILE * colorFile;
+    FILE * colorFileP;
 
-    colorFile = pm_openColornameFile(fileName, mustOpen);
+    openColornameFile(fileName, mustOpen, &colorFileP, errorP);
+    if (!*errorP) {
+        if (colorFileP == NULL) {
+            /* Couldn't open it, but Caller says treat same as
+               empty file
+            */
+            *nColorsP = 0;
+            *errorP = NULL;
+        } else {
+            readOpenColorFile(colorFileP, nColorsP, colornames, colors, cht,
+                              errorP);
+            
+            fclose(colorFileP);
+        }
+    }
+}
 
-    if (colorFile != NULL) {
-        unsigned int colornameIndex;
-        bool done;
+    
 
-        colornameIndex = 0;  /* initial value */
-        done = FALSE;
-        while (!done) {
-            struct colorfile_entry const ce = pm_colorget(colorFile);
+static void
+readcolordict(const char *      const fileName,
+              bool              const mustOpen,
+              unsigned int *    const nColorsP,
+              const char ***    const colornamesP,
+              pixel **          const colorsP,
+              colorhash_table * const chtP,
+              const char **     const errorP) {
 
-            if (!ce.colorname)  
-                done = TRUE;
-            else 
-                processColorfileEntry(ce, cht, colornames, colors,
-                                      &colornameIndex);
-        }
+    const char ** colornames;
 
-        *nColorsP = colornameIndex;
+    MALLOCARRAY(colornames, MAXCOLORNAMES);
 
-        while (colornameIndex < MAXCOLORNAMES)
-            colornames[colornameIndex++] = NULL;
+    if (colornames == NULL)
+        asprintfN(errorP, "Unable to allocate space for colorname table.");
+    else {
+        pixel * colors;
 
-        fclose(colorFile);
+        MALLOCARRAY(colors, MAXCOLORNAMES);
+        
+        if (colors == NULL)
+            asprintfN(errorP, "Unable to allocate space for color table.");
+        else {
+            colorhash_table cht;
+
+            cht = allocColorHash();
+            
+            if (cht == NULL)
+                asprintfN(errorP, "Unable to allocate space for color hash");
+            else {
+                readColorFile(fileName, mustOpen,
+                              nColorsP, colornames, colors, cht,
+                              errorP);
+
+                if (*errorP)
+                    ppm_freecolorhash(cht);
+                else
+                    *chtP = cht;
+            }
+            if (*errorP)
+                free(colors);
+            else
+                *colorsP = colors;
+        }
+        if (*errorP)
+            free(colornames);
+        else
+            *colornamesP = colornames;
     }
 }
 
@@ -551,32 +675,31 @@ ppm_readcolordict(const char *      const fileName,
     const char ** colornames;
     pixel * colors;
     unsigned int nColors;
+    const char * error;
 
-    cht = ppm_alloccolorhash();
-
-    MALLOCARRAY(colornames, MAXCOLORNAMES);
-
-    colors = ppm_allocrow(MAXCOLORNAMES);
+    readcolordict(fileName, mustOpen, &nColors, &colornames, &colors, &cht,
+                  &error);
 
-    if (colornames == NULL)
-        pm_error("Unable to allocate space for colorname table.");
-
-    readcolordict(fileName, mustOpen, &nColors, colornames, colors, cht);
-
-    if (chtP)
-        *chtP = cht;
-    else
+    if (error) {
+        pm_errormsg("%s", error);
+        strfree(error);
         ppm_freecolorhash(cht);
-    if (colornamesP)
-        *colornamesP = colornames;
-    else
-        ppm_freecolornames(colornames);
-    if (colorsP)
-        *colorsP = colors;
-    else
-        ppm_freerow(colors);
-    if (nColorsP)
-        *nColorsP = nColors;
+    } else {
+        if (chtP)
+            *chtP = cht;
+        else
+            ppm_freecolorhash(cht);
+        if (colornamesP)
+            *colornamesP = colornames;
+        else
+            ppm_freecolornames(colornames);
+        if (colorsP)
+            *colorsP = colors;
+        else
+            ppm_freerow(colors);
+        if (nColorsP)
+            *nColorsP = nColors;
+    }
 }
 
 
diff --git a/lib/libppmfuzzy.c b/lib/libppmfuzzy.c
index 6127d5d5..e149b42a 100644
--- a/lib/libppmfuzzy.c
+++ b/lib/libppmfuzzy.c
@@ -84,7 +84,7 @@ memberTrapez(fzLog const x1,
 static fzLog
 hueIsAround000(double const hue) {
 
-    return memberZ(10, 30, hue);
+    return memberZ(10, 20, hue);
 }
 
 
@@ -92,7 +92,7 @@ hueIsAround000(double const hue) {
 static fzLog
 hueIsAround015(double const hue) {
 
-    return memberZ(30, 40, hue);
+    return memberZ(20, 40, hue);
 }
 
 
@@ -100,7 +100,7 @@ hueIsAround015(double const hue) {
 static fzLog
 hueIsAround030(double const hue) {
 
-    return memberTrapez(10, 30, 40, 60, hue);
+    return memberTrapez(10, 20, 40, 60, hue);
 }
 
 
@@ -108,7 +108,7 @@ hueIsAround030(double const hue) {
 static fzLog
 hueIsAround060(double const hue) {
 
-    return memberTrapez(40, 60, 60, 80, hue);
+    return memberTrapez(40, 50, 60, 70, hue);
 }
 
 
@@ -116,7 +116,7 @@ hueIsAround060(double const hue) {
 static fzLog
 hueIsAround120(double const hue) {
 
-    return memberTrapez(60, 80, 150, 180, hue);
+    return memberTrapez(60, 70, 150, 180, hue);
 }
 
 
@@ -160,14 +160,14 @@ hueIsAround360(double const hue) {
 static fzLog
 satIsVeryLow(double const sat) {
 
-    return memberZ(0.02, 0.1, sat);
+    return memberZ(0.03, 0.08, sat);
 }
 
 
 
 static fzLog
 satIsLow(double const sat) {
-    return memberTrapez(0.02, 0.1, 0.2, 0.3, sat);
+    return memberTrapez(0.03, 0.08, 0.17, 0.2, sat);
 }
 
 
@@ -175,7 +175,7 @@ satIsLow(double const sat) {
 static fzLog
 satIsMedium(double const sat) {
 
-    return memberTrapez(0.2, 0.3, 0.6, 0.7, sat);
+    return memberTrapez(0.17, 0.2, 0.6, 0.8, sat);
 }
 
 
@@ -183,7 +183,7 @@ satIsMedium(double const sat) {
 static fzLog
 satIsHigh(double const sat) {
 
-    return memberS(0.6, 0.7, sat);
+    return memberS(0.6, 0.8, sat);
 }
 
 
@@ -195,7 +195,7 @@ satIsHigh(double const sat) {
 static fzLog
 valIsVeryLow(double const val) {
 
-    return memberZ(0.1, 0.2, val);
+    return memberZ(0.05, 0.2, val);
 }
 
 
@@ -203,7 +203,7 @@ valIsVeryLow(double const val) {
 static fzLog
 valIsLow(double const val) {
 
-    return memberTrapez(0.1, 0.2, 0.3, 0.6, val);
+    return memberTrapez(0.05, 0.2, 0.25, 0.3, val);
 }
 
 
@@ -211,7 +211,7 @@ valIsLow(double const val) {
 static fzLog
 valIsMedium(double const val) {
 
-    return memberTrapez(0.3, 0.6, 0.7, 0.8, val);
+    return memberTrapez(0.25, 0.3, 0.6, 0.7, val);
 }
 
 
@@ -219,7 +219,15 @@ valIsMedium(double const val) {
 static fzLog
 valIsHigh(double const val) {
 
-    return memberS(0.7, 0.8, val);
+    return memberTrapez(0.6, 0.7, 0.95, 0.97, val);
+}
+
+
+
+static fzLog
+valIsVeryHigh(double const val) {
+
+    return memberS(0.95, 0.97, val);
 }
 
 
@@ -269,10 +277,11 @@ matchBk(pixel     const color,
     fzLog const satMedium  = satIsMedium(hsv.s);
     fzLog const satHigh    = satIsHigh(hsv.s);
 
-    fzLog const valVeryLow = valIsVeryLow(hsv.v);
-    fzLog const valLow     = valIsLow(hsv.v);
-    fzLog const valMedium  = valIsMedium(hsv.v);
-    fzLog const valHigh    = valIsHigh(hsv.v);
+    fzLog const valVeryLow  = valIsVeryLow(hsv.v);
+    fzLog const valLow      = valIsLow(hsv.v);
+    fzLog const valMedium   = valIsMedium(hsv.v);
+    fzLog const valHigh     = valIsHigh(hsv.v);
+    fzLog const valVeryHigh = valIsVeryHigh(hsv.v);
 
     fzLog const hueAround000 = hueIsAround000(hsv.h);
     fzLog const hueAround015 = hueIsAround015(hsv.h);
@@ -285,13 +294,13 @@ matchBk(pixel     const color,
     fzLog const hueAround360 = hueIsAround360(hsv.h);
 
     (*bkMatchP)[BKCOLOR_BLACK]  =
-        fzAnd(fzOr(satVeryLow, satLow), valVeryLow);
+        fzAnd(fzOr(satVeryLow, satLow), fzOr(valVeryLow, valLow));
 
     (*bkMatchP)[BKCOLOR_GRAY]   =
-        fzAnd(fzOr(satVeryLow, satLow), fzOr(valLow, valMedium));
+        fzAnd(satVeryLow, fzAnd(fzNot(valVeryLow), fzNot(valVeryHigh)));
 
     (*bkMatchP)[BKCOLOR_WHITE]  =
-        fzAnd(fzOr(satVeryLow, satLow), valHigh);
+        fzAnd(satVeryLow, valVeryHigh);
     
     (*bkMatchP)[BKCOLOR_RED]    =
         fzAnd(fzAnd(fzOr(hueAround000, hueAround360), fzNot(satVeryLow)),
@@ -300,38 +309,40 @@ matchBk(pixel     const color,
 
     (*bkMatchP)[BKCOLOR_ORANGE] =
         fzAnd(fzAnd(hueAround030, fzOr(satMedium, satHigh)),
-              fzOr(valMedium, valHigh)
+              fzOr(fzOr(valMedium, valHigh), valVeryHigh)
              );
 
     (*bkMatchP)[BKCOLOR_YELLOW] =
         fzAnd(fzAnd(hueAround060, fzOr(satMedium, satHigh)),
-              fzOr(valMedium, valHigh)
+              fzOr(valHigh, valVeryHigh)
              );
 
     (*bkMatchP)[BKCOLOR_GREEN]  =
-        fzAnd(fzAnd(hueAround120, fzNot(satVeryLow)),
-              fzOr(valMedium, valHigh)
+        fzAnd(fzAnd(hueAround120, fzOr(satMedium, satHigh)),
+              fzAnd(fzNot(valVeryLow), fzNot(valLow))
              );
 
     (*bkMatchP)[BKCOLOR_BLUE]   =
-        fzAnd(fzAnd(hueAround180, fzAnd(fzNot(satVeryLow), fzNot(satLow))),
-              fzOr(valMedium, valHigh)
+        fzAnd(fzAnd(hueAround180, fzNot(satVeryLow)),
+              fzNot(valVeryLow)
              );
 
     (*bkMatchP)[BKCOLOR_VIOLET] =
-        fzAnd(fzAnd(hueAround270, fzNot(satVeryLow)),
+        fzAnd(fzAnd(hueAround270, fzOr(satMedium, satHigh)),
               fzOr(valMedium, valHigh)
              );
 
     (*bkMatchP)[BKCOLOR_PURPLE] =
-        fzAnd(fzAnd(hueAround320, fzNot(satVeryLow)),
+        fzAnd(fzAnd(hueAround320, fzOr(satMedium, satHigh)),
               fzOr(valMedium, valHigh)
              );
 
     (*bkMatchP)[BKCOLOR_BROWN]  =
-        fzAnd(fzOr(hueAround015, hueAround360),
-              fzAnd(fzNot(satVeryLow), fzNot(valHigh))
-             );
+	fzOr(
+             fzAnd(fzOr(hueAround015, hueAround360),
+                   fzAnd(fzNot(satVeryLow), fzOr(valLow, valMedium))),
+             fzAnd(hueAround015, satLow)
+	    );
 }
 
 
@@ -359,17 +370,17 @@ ppm_bk_color_from_color(pixel  const color,
 
 
 static pixel const bkColorMap[BKCOLOR_COUNT] = {
-    {  0,   0,   0}, /* BKCOLOR_BLACK  */
     {174, 174, 174}, /* BKCOLOR_GRAY   */
-    {255, 255, 255}, /* BKCOLOR_WHITE  */
-    {255,   0,   0}, /* BKCOLOR_RED    */
+    {128,  42,  42}, /* BKCOLOR_BROWN  */
     {255, 128,   0}, /* BKCOLOR_ORANGE */
+    {255,   0,   0}, /* BKCOLOR_RED    */
     {255, 255,   0}, /* BKCOLOR_YELLOW */
     {  0, 255,   0}, /* BKCOLOR_GREEN  */
     {  0,   0, 255}, /* BKCOLOR_BLUE   */
     {143,  94, 153}, /* BKCOLOR_VIOLET */
     {160,  32, 240}, /* BKCOLOR_PURPLE */
-    {128,  42,  42}  /* BKCOLOR_BROWN  */
+    {255, 255, 255}, /* BKCOLOR_WHITE  */
+    {  0,   0,   0}  /* BKCOLOR_BLACK  */
 };
 
 
@@ -393,17 +404,17 @@ ppm_color_from_bk_color(bk_color const bkColor,
 
 
 static const char * const bkColorNameMap[BKCOLOR_COUNT] = {
-    "black",
     "gray",
-    "white",
-    "red",
+    "brown",
     "orange",
+    "red",
     "yellow",
     "green",
     "blue",
     "violet",
     "purple",
-    "brown"
+    "white",
+    "black"
 };
 
 
diff --git a/lib/pm.h b/lib/pm.h
index 040a6a4b..696d763c 100644
--- a/lib/pm.h
+++ b/lib/pm.h
@@ -154,15 +154,25 @@ pm_setjmpbufsave(jmp_buf *  const jmpbufP,
 void
 pm_longjmp(void);
 
+
+typedef void pm_usermessagefn(const char * msg);
+
+void
+pm_setusermessagefn(pm_usermessagefn * fn);
+
+typedef void pm_usererrormsgfn(const char * msg);
+
+void
+pm_setusererrormsgfn(pm_usererrormsgfn * fn);
+
 void PM_GNU_PRINTF_ATTR(1,2)
 pm_message (const char format[], ...);     
 
 void PM_GNU_PRINTF_ATTR(1,2)
-pm_error (const char reason[], ...);       
+pm_errormsg(const char format[], ...);
 
-/* Obsolete - use helpful error message instead */
-void
-pm_perror (const char reason[]);           
+void PM_GNU_PRINTF_ATTR(1,2)
+pm_error (const char reason[], ...);       
 
 /* Obsolete - use shhopt and user's manual instead */
 void 
diff --git a/lib/ppm.h b/lib/ppm.h
index 033330b9..622d3e09 100644
--- a/lib/ppm.h
+++ b/lib/ppm.h
@@ -259,22 +259,25 @@ ppm_saturation(pixel const p,
 
 typedef enum {
     /* A color from the set of universally understood colors developed
-       by Brent Berlin and Paul Kay
+       by Brent Berlin and Paul Kay.
+
+       Algorithms in libnetpbm depend on the numerical representations
+       of these values being as follows.
     */
-    BKCOLOR_BLACK = 0,
-    BKCOLOR_GRAY,
-    BKCOLOR_WHITE,
-    BKCOLOR_RED,
+    BKCOLOR_GRAY = 0,
+    BKCOLOR_BROWN,
     BKCOLOR_ORANGE,
+    BKCOLOR_RED,
     BKCOLOR_YELLOW,
     BKCOLOR_GREEN,
     BKCOLOR_BLUE,
     BKCOLOR_VIOLET,
     BKCOLOR_PURPLE,
-    BKCOLOR_BROWN
+    BKCOLOR_WHITE,
+    BKCOLOR_BLACK
 } bk_color;
 
-#define BKCOLOR_COUNT (BKCOLOR_BROWN+1)
+#define BKCOLOR_COUNT (BKCOLOR_BLACK+1)
 
 bk_color
 ppm_bk_color_from_color(pixel  const color,
diff --git a/lib/util/Makefile b/lib/util/Makefile
index 8f461f28..ffa1db5b 100644
--- a/lib/util/Makefile
+++ b/lib/util/Makefile
@@ -7,11 +7,9 @@ VPATH=.:$(SRCDIR)/$(SUBDIR)
 
 include $(BUILDDIR)/Makefile.config
 
-INCLUDES = -I $(BUILDDIR) -I $(SRCDIR)/$(SUBDIR)/..
-
 # nstring is required for asprintf(), etc.  Also some systems don't have
 # snprintf(), e.g. Solaris 2.5.1.  2002.03.29.
-UTILOBJECTS = shhopt.o nstring.o filename.o
+UTILOBJECTS = shhopt.o nstring.o vasprintf.o filename.o
 
 MERGE_OBJECTS =
 
@@ -19,6 +17,8 @@ all: $(UTILOBJECTS)
 
 include $(SRCDIR)/Makefile.common
 
+INCLUDES = -I $(BUILDDIR) -I $(SRCDIR)/$(SUBDIR)/..
+
 $(UTILOBJECTS):%.o:%.c importinc
 	$(CC) -c $(INCLUDES) -DNDEBUG $(CFLAGS) $(CFLAGS_SHLIB) \
 	  $(CFLAGS_PERSONAL) $(CADD) -o $@ $<
diff --git a/lib/util/nstring.c b/lib/util/nstring.c
index 702a3c44..58500547 100644
--- a/lib/util/nstring.c
+++ b/lib/util/nstring.c
@@ -740,15 +740,6 @@ const char * const strsol = "NO MEMORY TO CREATE STRING!";
 
 
 
-/* We would like to have vasprintfN(), but it is difficult because you
-   can't run through a va_list twice, which we would want to do: once
-   to measure the length; once actually to build the string.  On some
-   machines, you can simply make two copies of the va_list variable in
-   normal C fashion, but on others you need va_copy, which is a
-   relatively recent invention.  In particular, the simple va_list copy
-   failed on an AMD64 Gcc Linux system in March 2006.
-*/
-
 void PM_GNU_PRINTF_ATTR(2,3)
 asprintfN(const char ** const resultP,
           const char *  const fmt, 
@@ -904,3 +895,20 @@ memmemN(const char * const haystack,
 
     return NULL;
 }
+
+
+
+bool
+strishex(const char * const subject) {
+
+    bool retval;
+    unsigned int i;
+
+    retval = TRUE;  /* initial assumption */
+
+    for (i = 0; i < strlen(subject); ++i)
+        if (!ISXDIGIT(subject[i]))
+            retval = FALSE;
+
+    return retval;
+}
diff --git a/lib/util/nstring.h b/lib/util/nstring.h
index 9ed20051..70a53f45 100644
--- a/lib/util/nstring.h
+++ b/lib/util/nstring.h
@@ -5,6 +5,7 @@
 #include <string.h>
 #include <ctype.h>
 
+#include "pm_c_util.h"
 #include "pm.h"  /* For PM_GNU_PRINTF_ATTR, __inline__ */
 
 #ifdef __cplusplus
@@ -134,6 +135,11 @@ asprintfN(const char ** const resultP,
           const char *  const fmt,
           ...) PM_GNU_PRINTF_ATTR(2,3);
 
+void
+vasprintfN(const char ** const resultP,
+           const char *  const format,
+           va_list             args);
+
 void 
 strfree(const char * const string);
 
@@ -150,6 +156,9 @@ memmemN(const char * const haystack,
         const char * const needle,
         size_t       const needlelen);
 
+bool
+strishex(const char * const subject);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/other/Makefile b/other/Makefile
index 87f92f96..139527e3 100644
--- a/other/Makefile
+++ b/other/Makefile
@@ -9,9 +9,10 @@ include $(BUILDDIR)/Makefile.config
 
 SUBDIRS = pamx
 
+EXTERN_INCLUDES =
 ifneq ($(LINUXSVGALIB),NONE)
   ifneq ($(LINUXSVGAHDR_DIR),)
-    INCLUDES += -I$(LINUXSVGAHDR_DIR)
+    EXTERN_INCLUDES += -I$(LINUXSVGAHDR_DIR)
   endif
 endif
 
diff --git a/other/pamx/Makefile b/other/pamx/Makefile
index a86a3331..8aab2608 100644
--- a/other/pamx/Makefile
+++ b/other/pamx/Makefile
@@ -7,9 +7,10 @@ VPATH=.:$(SRCDIR)/$(SUBDIR)
 
 include $(BUILDDIR)/Makefile.config
 
+EXTERN_INCLUDE =
 ifneq ($(X11LIB),NONE)
   ifneq ($(X11HDR_DIR),)
-    INCLUDES += -I$(X11HDR_DIR)
+    EXTERN_INCLUDES += -I$(X11HDR_DIR)
   endif
 endif
 
diff --git a/urt/scanargs.c b/urt/scanargs.c
index 416f2380..b91f3e37 100644
--- a/urt/scanargs.c
+++ b/urt/scanargs.c
@@ -49,9 +49,9 @@
 #include <stdarg.h>
 #endif
 
+#include "pm_c_util.h"
 #include "nstring.h"
 
-typedef char bool;
 /* 
  * An explicit assumption is made in this code that all pointers look
  * alike, except possible char * pointers.