about summary refs log tree commit diff
path: root/lib/libpam.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libpam.c')
-rw-r--r--lib/libpam.c553
1 files changed, 436 insertions, 117 deletions
diff --git a/lib/libpam.c b/lib/libpam.c
index ab75fab6..cc6368e1 100644
--- a/lib/libpam.c
+++ b/lib/libpam.c
@@ -1,9 +1,12 @@
-/*----------------------------------------------------------------------------
+/*=============================================================================
                                   libpam.c
-------------------------------------------------------------------------------
+===============================================================================
    These are the library functions, which belong in the libnetpbm library,
    that deal with the PAM (Portable Arbitrary Format) image format.
------------------------------------------------------------------------------*/
+
+   This file was originally written by Bryan Henderson and is contributed
+   to the public domain by him and subsequent authors.
+=============================================================================*/
 
 /* See pmfileio.c for the complicated explanation of this 32/64 bit file
    offset stuff.
@@ -20,9 +23,10 @@
 
 #include <math.h>
 
-#include "pm_c_util.h"
-#include "mallocvar.h"
-#include "nstring.h"
+#include "netpbm/pm_c_util.h"
+#include "netpbm/mallocvar.h"
+#include "netpbm/nstring.h"
+
 #include "pam.h"
 #include "ppm.h"
 #include "libpbm.h"
@@ -70,6 +74,7 @@ pamCommentP(const struct pam * const pamP) {
 }
 
 
+
 static void
 validateComputableSize(struct pam * const pamP) {
 /*----------------------------------------------------------------------------
@@ -205,21 +210,6 @@ pnm_createBlackTuple(const struct pam * const pamP,
 
 
 
-void
-createBlackTuple(const struct pam * const pamP, 
-                 tuple *            const blackTupleP) {
-
-/* This is poorly named, because it lacks the "pnm" prefix.  But for some
-   reason, this is how we originally named this.  So to maintain backward
-   compatibility with binaries that refer to "createBlackTuple", we define
-   this.  The preferred name, pnm_createBlackTuple() was new in Netpbm 10.20,
-   January 2004.  We should eventually retire createBlackTuple().
-*/
-    pnm_createBlackTuple(pamP, blackTupleP);
-}
-
-
-
 static tuple *
 allocPamRow(const struct pam * const pamP) {
 /*----------------------------------------------------------------------------
@@ -227,12 +217,11 @@ allocPamRow(const struct pam * const pamP) {
    overflow will not occur in our calculations.  NOTE: pnm_readpaminit()
    ensures this assumption is valid.
 -----------------------------------------------------------------------------*/
-    /* The tuple row data structure starts with 'width' pointers to
-       the tuples, immediately followed by the 'width' tuples
-       themselves.  Each tuple consists of 'depth' samples.  
+    /* The tuple row data structure starts with pointers to the tuples,
+       immediately followed by the tuples themselves.
     */
 
-    int const bytesPerTuple = allocationDepth(pamP) * sizeof(sample);
+    unsigned int const bytesPerTuple = allocationDepth(pamP) * sizeof(sample);
     tuple * tuplerow;
 
     tuplerow = malloc(pamP->width * (sizeof(tuple *) + bytesPerTuple));
@@ -262,9 +251,9 @@ pnm_allocpamrow(const struct pam * const pamP) {
     tuple * const tuplerow = allocPamRow(pamP);
 
     if (tuplerow == 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, allocationDepth(pamP), sizeof(sample));
+        pm_error("Out of memory allocating space for a tuple row of "
+                 "%d tuples by %d samples per tuple by %u bytes per sample.",
+                 pamP->width, allocationDepth(pamP), (unsigned)sizeof(sample));
 
     return tuplerow;
 }
@@ -401,7 +390,7 @@ pnm_setminallocationdepth(struct pam * const pamP,
         pm_error("Can't set minimum allocation depth in pam structure, "
                  "because the structure is only %u bytes long, and to "
                  "have an allocation_depth field, it must bea at least %u",
-                 pamP->len, PAM_STRUCT_SIZE(allocation_depth));
+                 pamP->len, (unsigned)PAM_STRUCT_SIZE(allocation_depth));
 
     pamP->allocation_depth = MAX(allocationDepth, pamP->depth);
         
@@ -430,8 +419,9 @@ pnm_setpamrow(const struct pam * const pamP,
 #define MAX_VALUE_LENGTH 255
 
 static void
-parse_header_line(const char buffer[], char label[MAX_LABEL_LENGTH+1], 
-                  char value[MAX_VALUE_LENGTH+1]) {
+parseHeaderLine(const char buffer[],
+                char label[MAX_LABEL_LENGTH+1], 
+                char value[MAX_VALUE_LENGTH+1]) {
     
     int buffer_curs;
 
@@ -483,9 +473,69 @@ struct headerSeen {
 
 
 static void
-process_header_line(char                const buffer[],
-                    struct pam *        const pamP,
-                    struct headerSeen * const headerSeenP) {
+parseHeaderUint(const char *   const valueString,
+                unsigned int * const valueNumP,
+                const char *   const name) {
+/*----------------------------------------------------------------------------
+   Interpret 'valueString' as the number in a header such as
+   "WIDTH 200".
+
+   'name' is the header name ("WIDTH" in the example).
+-----------------------------------------------------------------------------*/
+
+    if (strlen(valueString) == 0)
+        pm_error("Missing value for %s in PAM file header.", name);
+    else {
+        char * endptr;
+        long int valueNum;
+        errno = 0;  /* Clear errno so we can detect strtol() failure */
+        valueNum = strtol(valueString, &endptr, 10);
+        if (errno != 0)
+            pm_error("Too-large value for %s in "
+                     "PAM file header: '%s'", name, valueString);
+        else if (*endptr != '\0') 
+            pm_error("Non-numeric value for %s in "
+                     "PAM file header: '%s'", name, valueString);
+        else if (valueNum < 0) 
+            pm_error("Negative value for %s in "
+                     "PAM file header: '%s'", name, valueString);
+        else if ((unsigned int)valueNum != valueNum)
+            pm_error("Ridiculously large value for %s in "
+                     "PAM file header: %lu", name, valueNum);
+        else
+            *valueNumP = (unsigned int)valueNum;
+    }
+}
+
+
+
+static void
+parseHeaderInt(const char * const valueString,
+               int *        const valueNumP,
+               const char * const name) {
+/*----------------------------------------------------------------------------
+  This is not what it seems.  It is the same thing as
+  parseHeaderUint, except that the type of the value it returns is
+  "int" instead of "unsigned int".  But that doesn't mean the value can
+  be negative.  We throw an error is it is not positive.
+-----------------------------------------------------------------------------*/
+    unsigned int valueNum;
+
+    parseHeaderUint(valueString, &valueNum, name);
+
+    if ((int)valueNum != valueNum)
+        pm_error("Ridiculously large value for %s in "
+                 "PAM file header: %u", name, valueNum);
+    else
+        *valueNumP = (int)valueNum;
+}
+
+
+
+static void
+processHeaderLine(char                const buffer[],
+                  struct pam *        const pamP,
+                  struct headerSeen * const headerSeenP) {
 /*----------------------------------------------------------------------------
    Process a line from the PAM header.  The line is buffer[], and it is not
    a comment or blank.
@@ -497,67 +547,44 @@ process_header_line(char                const buffer[],
     char label[MAX_LABEL_LENGTH+1];
     char value[MAX_VALUE_LENGTH+1];
 
-    parse_header_line(buffer, label, value);
+    parseHeaderLine(buffer, label, value);
 
-    if (strcmp(label, "ENDHDR") == 0)
+    if (streq(label, "ENDHDR"))
         headerSeenP->endhdr = TRUE;
-    else {
-        if (strcmp(label, "WIDTH") == 0 ||
-            strcmp(label, "HEIGHT") == 0 ||
-            strcmp(label, "DEPTH") == 0 ||
-            strcmp(label, "MAXVAL") == 0) {
-
-            if (strlen(value) == 0)
-                pm_error("Missing value for %s in PAM file header.",
-                         label);
+    else if (streq(label, "WIDTH")) {
+        parseHeaderInt(value, &pamP->width, label);
+        headerSeenP->width = TRUE;
+    } else if (streq(label, "HEIGHT")) {
+        parseHeaderInt(value, &pamP->height, label);
+        headerSeenP->height = TRUE;
+    } else if (streq(label, "DEPTH")) {
+        parseHeaderUint(value, &pamP->depth, label);
+        headerSeenP->depth = TRUE;
+    } else if (streq(label, "MAXVAL")) {
+        unsigned int maxval;
+        parseHeaderUint(value, &maxval, label);
+        if (maxval >= (1<<16))
+            pm_error("Maxval too large: %u.  Max is 65535", maxval);
+        pamP->maxval = maxval;
+        headerSeenP->maxval = TRUE;
+    } else if (streq(label, "TUPLTYPE")) {
+        if (strlen(value) == 0)
+            pm_error("TUPLTYPE header does not have any tuple type text");
+        else {
+            size_t const oldLen = strlen(pamP->tuple_type);
+            if (oldLen + strlen(value) + 1 > sizeof(pamP->tuple_type)-1)
+                pm_error("TUPLTYPE value too long in PAM header");
+            if (oldLen == 0)
+                strcpy(pamP->tuple_type, value);
             else {
-                char *endptr;
-                long int numeric_value;
-                errno = 0;  /* Clear errno so we can detect strtol() failure */
-                numeric_value = strtol(value, &endptr, 10);
-                if (errno != 0)
-                    pm_error("Too-large value for %s in "
-                             "PAM file header: '%s'", label, value);
-                if (*endptr != '\0') 
-                    pm_error("Non-numeric value for %s in "
-                             "PAM file header: '%s'", label, value);
-                else if (numeric_value < 0) 
-                    pm_error("Negative value for %s in "
-                             "PAM file header: '%s'", label, value);
+                strcat(pamP->tuple_type, " ");
+                strcat(pamP->tuple_type, value);
             }
+            pamP->tuple_type[sizeof(pamP->tuple_type)-1] = '\0';
         }
-    
-        if (strcmp(label, "WIDTH") == 0) {
-            pamP->width = atoi(value);
-            headerSeenP->width = TRUE;
-        } else if (strcmp(label, "HEIGHT") == 0) {
-            pamP->height = atoi(value);
-            headerSeenP->height = TRUE;
-        } else if (strcmp(label, "DEPTH") == 0) {
-            pamP->depth = atoi(value);
-            headerSeenP->depth = TRUE;
-        } else if (strcmp(label, "MAXVAL") == 0) {
-            pamP->maxval = atoi(value);
-            headerSeenP->maxval = TRUE;
-        } else if (strcmp(label, "TUPLTYPE") == 0) {
-            if (strlen(value) == 0)
-                pm_error("TUPLTYPE header does not have any tuple type text");
-            else {
-                size_t const oldLen = strlen(pamP->tuple_type);
-                if (oldLen + strlen(value) + 1 > sizeof(pamP->tuple_type)-1)
-                    pm_error("TUPLTYPE value too long in PAM header");
-                if (oldLen == 0)
-                    strcpy(pamP->tuple_type, value);
-                else {
-                    strcat(pamP->tuple_type, " ");
-                    strcat(pamP->tuple_type, value);
-                }
-                pamP->tuple_type[sizeof(pamP->tuple_type)-1] = '\0';
-            }
-        } else 
-            pm_error("Unrecognized header line: '%s'.  "
-                     "Possible missing ENDHDR line?", label);
-    }
+    } else 
+        pm_error("Unrecognized header line type: '%s'.  "
+                 "Possible missing ENDHDR line?", label);
 }
 
 
@@ -576,7 +603,7 @@ appendComment(char **      const commentsP,
 
     if (*commentsP == NULL)
         pm_error("Couldn't get storage for %u characters of comments from "
-                 "the PAM header", commentLen);
+                 "the PAM header", (unsigned)commentLen);
 
     strcat(*commentsP, commentLine);
 }
@@ -592,7 +619,7 @@ disposeOfComments(const struct pam * const pamP,
     if (retP)
         *retP = comments;
     else
-        strfree(comments);
+        pm_strfree(comments);
 }
 
 
@@ -635,10 +662,10 @@ readpaminitrest(struct pam * const pamP) {
             buffer[256-1-1] = '\n';  /* In case fgets() truncated */
             if (buffer[0] == '#')
                 appendComment(&comments, buffer);
-            else if (stripeq(buffer, ""));
+            else if (pm_stripeq(buffer, ""));
                 /* Ignore it; it's a blank line */
             else 
-                process_header_line(buffer, pamP, &headerSeen);
+                processHeaderLine(buffer, pamP, &headerSeen);
         }
     }
 
@@ -718,13 +745,127 @@ pnm_readpaminitrestaspnm(FILE * const fileP,
                 
 unsigned int
 pnm_bytespersample(sample const maxval) {
+/*----------------------------------------------------------------------------
+   Return the number of bytes per sample in the PAM raster of a PAM image
+   with maxval 'maxval'.  It's defined to be the minimum number of bytes
+   needed for that maxval, i.e. 1 for maxval < 256, 2 otherwise.
+-----------------------------------------------------------------------------*/
+
+    /* The PAM format requires maxval to be greater than zero and less than
+       1<<16, but since that is a largely arbitrary restriction, we don't want
+       to rely on it.
+    */
 
-    assert(sizeof(maxval) * 8 <= 32);
+    unsigned int i;
+    sample a;
+
+    for (i = 0, a = maxval; i <= sizeof(maxval); ++i) {
+        if (a == 0)
+            return i;
+        a >>= 8;
+    }
+    return 0;  /* silence compiler warning */
+}
+
+
+
+static void
+validateMinDepth(const struct pam * const pamP,
+                 unsigned int       const minDepth) {
 
-    if      (maxval >>  8 == 0) return 1;
-    else if (maxval >> 16 == 0) return 2;
-    else if (maxval >> 24 == 0) return 3;
-    else                        return 4;
+    if (pamP->depth < minDepth)
+        pm_error("Depth %u is insufficient for tuple type '%s'.  "
+                 "Minimum depth is %u",
+                 pamP->depth, pamP->tuple_type, minDepth);
+}
+
+
+
+static void
+interpretTupleType(struct pam * const pamP) {
+/*----------------------------------------------------------------------------
+   Fill in redundant convenience fields in *pamP with information the
+   pamP->tuple_type value implies:
+
+     visual
+     colorDepth
+     haveOpacity
+     opacityPlane
+
+   Validate the tuple type against the depth and maxval as well.
+-----------------------------------------------------------------------------*/
+    const char * const tupleType =
+        pamP->len >= PAM_STRUCT_SIZE(tuple_type) ? pamP->tuple_type : "";
+
+    bool         visual;
+    unsigned int colorDepth;
+    bool         haveOpacity;
+    unsigned int opacityPlane;
+
+    assert(pamP->depth > 0);
+
+    switch (PAM_FORMAT_TYPE(pamP->format)) {
+    case PAM_TYPE: {
+        if (streq(tupleType, "BLACKANDWHITE")) {
+            visual = true;
+            colorDepth = 1;
+            haveOpacity = false;
+            if (pamP->maxval != 1)
+                pm_error("maxval %u is not consistent with tuple type "
+                         "BLACKANDWHITE (should be 1)",
+                         (unsigned)pamP->maxval);
+        } else if (streq(tupleType, "GRAYSCALE")) {
+            visual = true;
+            colorDepth = 1;
+            haveOpacity = false;
+        } else if (streq(tupleType, "GRAYSCALE_ALPHA")) {
+            visual = true;
+            colorDepth = 1;
+            haveOpacity = true;
+            opacityPlane = PAM_GRAY_TRN_PLANE;
+            validateMinDepth(pamP, 2);
+        } else if (streq(tupleType, "RGB")) {
+            visual = true;
+            colorDepth = 3;
+            haveOpacity = false;
+            validateMinDepth(pamP, 3);
+        } else if (streq(tupleType, "RGB_ALPHA")) {
+            visual = true;
+            colorDepth = 3;
+            haveOpacity = true;
+            opacityPlane = PAM_TRN_PLANE;
+            validateMinDepth(pamP, 4);
+        } else {
+            visual = false;
+        }
+    } break;
+    case PPM_TYPE:
+        visual = true;
+        colorDepth = 3;
+        haveOpacity = false;
+        assert(pamP->depth == 3);
+        break;
+    case PGM_TYPE:
+        visual = true;
+        colorDepth = 1;
+        haveOpacity = false;
+        break;
+    case PBM_TYPE:
+        visual = true;
+        colorDepth = 1;
+        haveOpacity = false;
+        break;
+    default:
+        assert(false);
+    }
+    if (pamP->size >= PAM_STRUCT_SIZE(visual))
+        pamP->visual = visual;
+    if (pamP->size >= PAM_STRUCT_SIZE(color_depth))
+        pamP->color_depth = colorDepth;
+    if (pamP->size >= PAM_STRUCT_SIZE(have_opacity))
+        pamP->have_opacity = haveOpacity;
+    if (pamP->size >= PAM_STRUCT_SIZE(opacity_plane))
+        pamP->opacity_plane = opacityPlane;
 }
 
 
@@ -736,9 +877,9 @@ pnm_readpaminit(FILE *       const file,
 
     if (size < PAM_STRUCT_SIZE(tuple_type)) 
         pm_error("pam object passed to pnm_readpaminit() is too small.  "
-                 "It must be large\n"
+                 "It must be large "
                  "enough to hold at least up to the "
-                 "'tuple_type' member, but according\n"
+                 "'tuple_type' member, but according "
                  "to the 'size' argument, it is only %d bytes long.", 
                  size);
 
@@ -794,6 +935,8 @@ pnm_readpaminit(FILE *       const file,
     pamP->plainformat = FALSE;
         /* See below for complex explanation of why this is FALSE. */
 
+    interpretTupleType(pamP);
+
     validateComputableSize(pamP);
 }
 
@@ -867,9 +1010,9 @@ pnm_writepaminit(struct pam * const pamP) {
 
     if (pamP->size < PAM_STRUCT_SIZE(bytes_per_sample))
         pm_error("pam object passed to pnm_writepaminit() is too small.  "
-                 "It must be large\n"
+                 "It must be large "
                  "enough to hold at least up through the "
-                 "'bytes_per_sample' member, but according\n"
+                 "'bytes_per_sample' member, but according "
                  "to its 'size' member, it is only %u bytes long.", 
                  pamP->size);
     if (pamP->len < PAM_STRUCT_SIZE(maxval))
@@ -881,27 +1024,37 @@ pnm_writepaminit(struct pam * const pamP) {
         pm_error("maxval (%lu) passed to pnm_writepaminit() "
                  "is greater than %u", pamP->maxval, PAM_OVERALL_MAXVAL);
 
-    if (pamP->len < PAM_STRUCT_SIZE(tuple_type))
+    if (pamP->len < PAM_STRUCT_SIZE(tuple_type)) {
         tupleType = "";
-    else
+        if (pamP->size >= PAM_STRUCT_SIZE(tuple_type))
+            pamP->tuple_type[0] = '\0';
+    } else
         tupleType = pamP->tuple_type;
 
-    if (pamP->len < PAM_STRUCT_SIZE(bytes_per_sample))
-        pamP->len = PAM_STRUCT_SIZE(bytes_per_sample);
     pamP->bytes_per_sample = pnm_bytespersample(pamP->maxval);
+
+    if (pamP->size >= PAM_STRUCT_SIZE(comment_p) &&
+        pamP->len < PAM_STRUCT_SIZE(comment_p))
+        pamP->comment_p = NULL;
+
+    if (pamP->size >= PAM_STRUCT_SIZE(allocation_depth) &&
+        pamP->len < PAM_STRUCT_SIZE(allocation_depth))
+        pamP->allocation_depth = 0;
+
+    interpretTupleType(pamP);
+
+    pamP->len = MIN(pamP->size, PAM_STRUCT_SIZE(opacity_plane));
     
     switch (PAM_FORMAT_TYPE(pamP->format)) {
     case PAM_TYPE:
-        if (pm_plain_output)
-            pm_error("There is no plain version of PAM.  -plain option "
-                     "is not allowed");
+        /* See explanation below of why we ignore 'pm_plain_output' here. */
         fprintf(pamP->file, "P7\n");
         writeComments(pamP);
         fprintf(pamP->file, "WIDTH %u\n",   (unsigned)pamP->width);
         fprintf(pamP->file, "HEIGHT %u\n",  (unsigned)pamP->height);
         fprintf(pamP->file, "DEPTH %u\n",   pamP->depth);
         fprintf(pamP->file, "MAXVAL %lu\n", pamP->maxval);
-        if (!stripeq(tupleType, ""))
+        if (!pm_stripeq(tupleType, ""))
             fprintf(pamP->file, "TUPLTYPE %s\n", pamP->tuple_type);
         fprintf(pamP->file, "ENDHDR\n");
         break;
@@ -954,6 +1107,23 @@ pnm_writepaminit(struct pam * const pamP) {
 
 
 
+/* EFFECT OF -plain WHEN WRITING PAM FORMAT:
+
+   Before Netpbm 10.63 (June 2013), pnm_writepaminit() did a pm_error() here
+   if 'pm_plain_output' was set (i.e. the user said -plain).  But this isn't
+   really logical, because -plain is a global option for the program and here
+   we are just writing one image.  As a global option, -plain must be defined
+   to have effect where it makes sense and have no effect where it doesn't.
+   Note that a program that generates GIF just ignores -plain.  Note also that
+   a program could conceivably generate both a PPM image and a PAM image.
+
+   Note also how we handle the other a user can request plain format: the
+   'plainformat' member of the PAM struct.  In the case of PAM, we ignore that
+   member.
+*/
+
+
+
 void
 pnm_checkpam(const struct pam *   const pamP, 
              enum pm_check_type   const checkType, 
@@ -1046,19 +1216,107 @@ pnm_makearrayrgb(const struct pam * const pamP,
 
 
 
+void 
+pnm_makerowrgba(const struct pam * const pamP,
+                tuple *            const tuplerow) {
+/*----------------------------------------------------------------------------
+   Make the tuples 'tuplerow' the RGBA equivalent of what they are now,
+   which is described by *pamP.
+
+   This means afterward, *pamP no longer correctly describes these tuples;
+   Caller must be sure to update *pamP it or not use it anymore.
+
+   We fail if Caller did not supply enough allocated space in 'tuplerow' for
+   the extra planes (tuple allocation depth).
+-----------------------------------------------------------------------------*/
+    if (pamP->len < PAM_STRUCT_SIZE(opacity_plane)) {
+        pm_message("struct pam length %u is too small for pnm_makerowrgba().  "
+                   "This function requires struct pam fields through "
+                   "'opacity_plane'", pamP->len);
+        abort();
+    } else {
+        if (!pamP->visual)
+            pm_error("Non-visual tuples given to pnm_addopacityrow()");
+        
+        if (pamP->color_depth >= 3 && pamP->have_opacity) {
+            /* It's already in RGBA format.  Leave it alone. */
+        } else {
+            unsigned int col;
+
+            if (allocationDepth(pamP) < 4)
+                pm_error("allocation depth %u passed to pnm_makerowrgba().  "
+                         "Must be at least 4.", allocationDepth(pamP));
+        
+            for (col = 0; col < pamP->width; ++col) {
+                tuple const thisTuple = tuplerow[col];
+                thisTuple[PAM_TRN_PLANE] = 
+                    pamP->have_opacity ? thisTuple[pamP->opacity_plane] :
+                    pamP->maxval;
+
+                assert(PAM_RED_PLANE == 0);
+                thisTuple[PAM_BLU_PLANE] = thisTuple[0];
+                thisTuple[PAM_GRN_PLANE] = thisTuple[0];
+            }
+        }
+    }
+}
+
+
+
+void 
+pnm_addopacityrow(const struct pam * const pamP,
+                  tuple *            const tuplerow) {
+/*----------------------------------------------------------------------------
+   Add an opacity plane to the tuples in 'tuplerow', if one isn't already
+   there.
+
+   This means afterward, *pamP no longer correctly describes these tuples;
+   Caller must be sure to update *pamP it or not use it anymore.
+
+   We fail if Caller did not supply enough allocated space in 'tuplerow' for
+   the extra plane (tuple allocation depth).
+-----------------------------------------------------------------------------*/
+    if (pamP->len < PAM_STRUCT_SIZE(opacity_plane)) {
+        pm_message("struct pam length %u is too small for pnm_makerowrgba().  "
+                   "This function requires struct pam fields through "
+                   "'opacity_plane'", pamP->len);
+        abort();
+    } else {
+        if (!pamP->visual)
+            pm_error("Non-visual tuples given to pnm_addopacityrow()");
+        
+        if (pamP->have_opacity) {
+            /* It already has opacity.  Leave it alone. */
+        } else {
+            unsigned int const opacityPlane = pamP->color_depth;
+
+            unsigned int col;
+
+            if (allocationDepth(pamP) < opacityPlane + 1)
+                pm_error("allocation depth %u passed to pnm_addopacityrow().  "
+                         "Must be at least %u.",
+                         allocationDepth(pamP), opacityPlane + 1);
+        
+            for (col = 0; col < pamP->width; ++col)
+                tuplerow[col][opacityPlane] = pamP->maxval;
+        }
+    }
+}
+
+
+
 void
 pnm_getopacity(const struct pam * const pamP,
-               bool *             const haveOpacityP,
+               int *              const haveOpacityP,
                unsigned int *     const opacityPlaneP) {
 
-    /* Design note; If use of this information proliferates, we should
-       probably add it to struct pam as convenience values analogous to
-       bytes_per_sample.
+    /* Usage note: this is obsolete since we added 'have_opacity', etc.
+       to struct pam.
     */
-    if (strcmp(pamP->tuple_type, "RGB_ALPHA") == 0) {
+    if (streq(pamP->tuple_type, "RGB_ALPHA")) {
         *haveOpacityP = TRUE;
         *opacityPlaneP = PAM_TRN_PLANE;
-    } else if (strcmp(pamP->tuple_type, "GRAYSCALE_ALPHA") == 0) {
+    } else if (streq(pamP->tuple_type, "GRAYSCALE_ALPHA")) {
         *haveOpacityP = TRUE;
         *opacityPlaneP = PAM_GRAY_TRN_PLANE;
     } else
@@ -1067,6 +1325,67 @@ pnm_getopacity(const struct pam * const pamP,
 
 
 
+tuple
+pnm_backgroundtuple(struct pam *  const pamP,
+                    tuple      ** const tuples) {
+/*--------------------------------------------------------------------
+  This function was copied from libpnm3.c's pnm_backgroundxel() and
+  modified to use tuples instead of xels.
+----------------------------------------------------------------------*/
+    tuple tuplePtr, bgtuple, ul, ur, ll, lr;
+
+    /* Guess a good background value. */
+    ul = tuples[0][0];
+    ur = tuples[0][pamP->width-1];
+    ll = tuples[pamP->height-1][0];
+    lr = tuples[pamP->height-1][pamP->width-1];
+    bgtuple = NULL;
+
+    /* We first recognize three corners equal.  If not, we look for any
+       two.  If not, we just average all four.
+    */
+    if (pnm_tupleequal(pamP, ul, ur) && pnm_tupleequal(pamP, ur, ll))
+        tuplePtr = ul;
+    else if (pnm_tupleequal(pamP, ul, ur) &&
+             pnm_tupleequal(pamP, ur, lr))
+        tuplePtr = ul;
+    else if (pnm_tupleequal(pamP, ul, ll) &&
+             pnm_tupleequal(pamP, ll, lr))
+        tuplePtr = ul;
+    else if (pnm_tupleequal(pamP, ur, ll) &&
+             pnm_tupleequal(pamP, ll, lr))
+        tuplePtr = ur;
+    else if (pnm_tupleequal(pamP, ul, ur))
+        tuplePtr = ul;
+    else if (pnm_tupleequal(pamP, ul, ll))
+        tuplePtr = ul;
+    else if (pnm_tupleequal(pamP, ul, lr))
+        tuplePtr = ul;
+    else if (pnm_tupleequal(pamP, ur, ll))
+        tuplePtr = ur;
+    else if (pnm_tupleequal(pamP, ur, lr))
+        tuplePtr = ur;
+    else if (pnm_tupleequal(pamP, ll, lr))
+        tuplePtr = ll;
+    else {
+        /* Reimplement libpnm3.c's mean4() but for tuples. */
+        unsigned int plane;
+        bgtuple = pnm_allocpamtuple(pamP);
+        for (plane = 0; plane < pamP->depth; ++plane)
+          bgtuple[plane] = (ul[plane] + ur[plane] + ll[plane] + lr[plane]) / 4;
+    }
+    if (!bgtuple) {
+        unsigned int plane;
+        bgtuple = pnm_allocpamtuple(pamP);
+        for (plane = 0; plane < pamP->depth; ++plane)
+          bgtuple[plane] = tuplePtr[plane];
+    }
+
+    return bgtuple;
+}
+
+
+
 /*=============================================================================
    pm_system() Standard Input feeder and Standard Output accepter functions.   
 =============================================================================*/