about summary refs log tree commit diff
path: root/lib
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 /lib
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
Diffstat (limited to 'lib')
-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
19 files changed, 1641 insertions, 645 deletions
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