about summary refs log tree commit diff
path: root/lib/libpam.c
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2006-08-19 03:12:28 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2006-08-19 03:12:28 +0000
commit1fd361a1ea06e44286c213ca1f814f49306fdc43 (patch)
tree64c8c96cf54d8718847339a403e5e67b922e8c3f /lib/libpam.c
downloadnetpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.tar.gz
netpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.tar.xz
netpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.zip
Create Subversion repository
git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@1 9d0c8265-081b-0410-96cb-a4ca84ce46f8
Diffstat (limited to 'lib/libpam.c')
-rw-r--r--lib/libpam.c1098
1 files changed, 1098 insertions, 0 deletions
diff --git a/lib/libpam.c b/lib/libpam.c
new file mode 100644
index 00000000..bbfa7e32
--- /dev/null
+++ b/lib/libpam.c
@@ -0,0 +1,1098 @@
+/*----------------------------------------------------------------------------
+                                  libpam.c
+------------------------------------------------------------------------------
+   These are the library functions, which belong in the libnetpbm library,
+   that deal with the PAM (Portable Arbitrary Format) image format.
+-----------------------------------------------------------------------------*/
+
+/* See libpm.c for the complicated explanation of this 32/64 bit file
+   offset stuff.
+*/
+#define _FILE_OFFSET_BITS 64
+#define _LARGE_FILES  
+#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <string.h>
+#include <limits.h>
+#include <assert.h>
+#include <stdio.h>
+
+#include <math.h>
+
+#include "pm_c_util.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "pam.h"
+#include "ppm.h"
+#include "libpbm.h"
+#include "libpgm.h"
+#include "libppm.h"
+#include "colorname.h"
+#include "fileio.h"
+
+#include "libpam.h"
+
+
+
+static unsigned int
+allocationDepth(const struct pam * const pamP) {
+
+    unsigned int retval;
+
+    if (pamP->len >= PAM_STRUCT_SIZE(allocation_depth)) {
+        if (pamP->allocation_depth == 0)
+            retval = pamP->depth;
+        else {
+            if (pamP->depth > pamP->allocation_depth)
+                pm_error("'allocationDepth' (%u) is smaller than 'depth' (%u)",
+                         pamP->allocation_depth, pamP->depth);
+            retval = pamP->allocation_depth;
+        }
+    } else
+        retval = pamP->depth;
+    return retval;
+}
+
+
+
+static const char **
+pamCommentP(const struct pam * const pamP) {
+
+    const char ** retval;
+
+    if (pamP->len >= PAM_STRUCT_SIZE(comment_p))
+        retval = pamP->comment_p;
+    else
+        retval = NULL;
+
+    return retval;
+}
+
+
+static void
+validateComputableSize(struct pam * const pamP) {
+/*----------------------------------------------------------------------------
+   Validate that the dimensions of the image are such that it can be
+   processed in typical ways on this machine without worrying about
+   overflows.  Note that in C, arithmetic is always modulus arithmetic,
+   so if your values are too big, the result is not what you expect.
+   That failed expectation can be disastrous if you use it to allocate
+   memory.
+
+   It is very normal to allocate space for a tuplerow, so we make sure
+   the size of a tuple row, in bytes, can be represented by an 'int'.
+
+   Another common operation is adding 1 or 2 to the highest row, column,
+   or plane number in the image, so we make sure that's possible.
+-----------------------------------------------------------------------------*/
+    unsigned int const depth = allocationDepth(pamP);
+
+    if (depth > INT_MAX/sizeof(sample))
+        pm_error("image depth (%u) too large to be processed", depth);
+    else if (depth * sizeof(sample) > INT_MAX/pamP->width)
+        pm_error("image width and depth (%u, %u) too large "
+                 "to be processed.", pamP->width, depth);
+    else if (pamP->width * (depth * sizeof(sample)) >
+             INT_MAX - depth * sizeof(tuple *))
+        pm_error("image width and depth (%u, %u) too large "
+                 "to be processed.", pamP->width, depth);
+    
+    if (depth > INT_MAX - 2)
+        pm_error("image depth (%u) too large to be processed", depth);
+    if (pamP->width > INT_MAX - 2)
+        pm_error("image width (%u) too large to be processed", pamP->width);
+    if (pamP->height > INT_MAX - 2)
+        pm_error("image height (%u) too large to be processed", pamP->height);
+}
+
+
+
+tuple
+pnm_allocpamtuple(const struct pam * const pamP) {
+
+    tuple retval;
+
+    retval = malloc(allocationDepth(pamP) * sizeof(retval[0]));
+
+    if (retval == NULL)
+        pm_error("Out of memory allocating %u-plane tuple", 
+                 allocationDepth(pamP));
+
+    return retval;
+}
+
+
+
+int
+pnm_tupleequal(const struct pam * const pamP, 
+               tuple              const comparand, 
+               tuple              const comparator) {
+
+    unsigned int plane;
+    bool equal;
+
+    equal = TRUE;  /* initial value */
+    for (plane = 0; plane < pamP->depth; ++plane) 
+        if (comparand[plane] != comparator[plane])
+            equal = FALSE;
+
+    return equal;
+}
+
+
+
+
+void
+pnm_assigntuple(const struct pam * const pamP,
+                tuple              const dest,
+                tuple              const source) {
+
+    unsigned int plane;
+    for (plane = 0; plane < pamP->depth; ++plane) {
+        dest[plane] = source[plane];
+    }
+}
+
+
+
+static void
+scaleTuple(const struct pam * const pamP,
+           tuple              const dest,
+           tuple              const source, 
+           sample             const newmaxval) {
+
+    unsigned int plane;
+    for (plane = 0; plane < pamP->depth; ++plane) 
+        dest[plane] = pnm_scalesample(source[plane], pamP->maxval, newmaxval);
+}
+
+
+
+void
+pnm_scaletuple(const struct pam * const pamP,
+               tuple              const dest,
+               tuple              const source, 
+               sample             const newmaxval) {
+
+    scaleTuple(pamP, dest, source, newmaxval);
+}
+
+
+
+void
+pnm_createBlackTuple(const struct pam * const pamP, 
+                     tuple *            const blackTupleP) {
+/*----------------------------------------------------------------------------
+   Create a "black" tuple.  By that we mean a tuple all of whose elements
+   are zero.  If it's an RGB, grayscale, or b&w pixel, that means it's black.
+-----------------------------------------------------------------------------*/
+    *blackTupleP = pnm_allocpamtuple(pamP);
+    if (pamP->format == PAM_FORMAT) {
+        /* In this format, we don't know the meaning of "black", so we
+           just punt.
+           */
+        int i;
+        for (i = 0; i < pamP->depth; i++) 
+            (*blackTupleP)[i] = 0;
+    } else {
+        xel black_xel;
+        black_xel = pnm_blackxel(pamP->maxval, pamP->format);
+        (*blackTupleP)[0] = PPM_GETR(black_xel);
+        (*blackTupleP)[1] = PPM_GETG(black_xel);
+        (*blackTupleP)[2] = PPM_GETB(black_xel);
+    }
+}
+
+
+
+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) {
+/*----------------------------------------------------------------------------
+   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.
+-----------------------------------------------------------------------------*/
+    /* 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.  
+    */
+
+    int const bytesPerTuple = allocationDepth(pamP) * sizeof(sample);
+    tuple * tuplerow;
+
+    tuplerow = malloc(pamP->width * (sizeof(tuple *) + bytesPerTuple));
+                      
+    if (tuplerow != NULL) {
+        /* Now we initialize the pointers to the individual tuples
+           to make this a regulation C two dimensional array.  
+        */
+        char * p;
+        unsigned int col;
+        
+        p = (char*) (tuplerow + pamP->width);  /* location of Tuple 0 */
+        for (col = 0; col < pamP->width; ++col) {
+                tuplerow[col] = (tuple) p;
+                p += bytesPerTuple;
+        }
+    }
+    return tuplerow;
+}
+
+
+
+tuple *
+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));
+
+    return tuplerow;
+}
+
+
+
+static unsigned int 
+rowimagesize(const struct pam * const pamP) {
+
+    /* If repeatedly calculating this turns out to be a significant
+       performance problem, we could keep this in struct pam like
+       bytes_per_sample.
+    */
+
+    if (PAM_FORMAT_TYPE(pamP->format) == PBM_TYPE)
+        return pbm_packed_bytes(pamP->width);
+    else 
+        return (pamP->width * pamP->bytes_per_sample * pamP->depth);
+}
+
+
+
+unsigned char *
+pnm_allocrowimage(const struct pam * const pamP) {
+
+    unsigned int const rowsize = rowimagesize(pamP);
+    unsigned int const overrunSpaceNeeded = 8;
+        /* This is the number of extra bytes of space libnetpbm needs to have
+           at the end of the buffer so it can use fast, lazy algorithms.
+        */
+    unsigned int const size = rowsize + overrunSpaceNeeded;
+
+    unsigned char * retval;
+
+    retval = malloc(size);
+
+    if (retval == NULL)
+        pm_error("Unable to allocate %u bytes for a row image buffer",
+                 size);
+
+    return retval;
+}
+
+
+
+void
+pnm_freerowimage(unsigned char * const rowimage) {
+    free(rowimage);
+}
+
+
+
+void 
+pnm_scaletuplerow(const struct pam * const pamP,
+                  tuple *            const destRow,
+                  tuple *            const sourceRow,
+                  sample             const newMaxval) {
+
+    if (pamP->maxval == newMaxval) {
+        /* Fast path for common case: no scaling needed */
+        if (destRow != sourceRow) {
+            /* Fast path for common case: it's already what it needs to be */
+            unsigned int col;
+            for (col = 0; col < pamP->width; ++col)
+                pnm_assigntuple(pamP, destRow[col], sourceRow[col]);
+        }
+    } else {        
+        unsigned int col;
+        for (col = 0; col < pamP->width; ++col) 
+            scaleTuple(pamP, destRow[col], sourceRow[col], newMaxval);
+    }
+}
+
+ 
+
+tuple **
+pnm_allocpamarray(const struct pam * const pamP) {
+    
+    tuple **tuplearray;
+
+    /* If the speed of this is ever an issue, it might be sped up a little
+       by allocating one large chunk.
+    */
+    
+    MALLOCARRAY(tuplearray, pamP->height);
+    if (tuplearray == NULL) 
+        pm_error("Out of memory allocating the row pointer section of "
+                 "a %u row array", pamP->height);
+    else {
+        int row;
+        bool outOfMemory;
+
+        outOfMemory = FALSE;
+        for (row = 0; row < pamP->height && !outOfMemory; ++row) {
+            tuplearray[row] = allocPamRow(pamP);
+            if (tuplearray[row] == NULL) {
+                unsigned int freerow;
+                outOfMemory = TRUE;
+                
+                for (freerow = 0; freerow < row; ++freerow)
+                    pnm_freepamrow(tuplearray[row]);
+            }
+        }
+        if (outOfMemory) {
+            free(tuplearray);
+            
+            pm_error("Out of memory allocating the %u rows %u columns wide by "
+                     "%u planes deep", pamP->height, pamP->width,
+                     allocationDepth(pamP));
+        }
+    }
+    return tuplearray;
+}
+
+
+
+void
+pnm_freepamarray(tuple ** const tuplearray, const struct pam * const pamP) {
+
+    int row;
+    for (row = 0; row < pamP->height; row++)
+        pnm_freepamrow(tuplearray[row]);
+
+    free(tuplearray);
+}
+
+
+
+void 
+pnm_setminallocationdepth(struct pam * const pamP,
+                          unsigned int const allocationDepth) {
+
+    if (pamP->len < PAM_STRUCT_SIZE(allocation_depth))
+        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->allocation_depth = MAX(allocationDepth, pamP->depth);
+        
+    validateComputableSize(pamP);
+}
+
+
+
+void
+pnm_setpamrow(const struct pam * const pamP,
+              tuple *            const tuplerow, 
+              sample             const value) {
+
+    int col;
+    for (col = 0; col < pamP->width; ++col) {
+        int plane;
+        for (plane = 0; plane < pamP->depth; ++plane) 
+            tuplerow[col][plane] = value;
+    }
+}
+
+
+
+
+#define MAX_LABEL_LENGTH 8
+#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]) {
+    
+    int buffer_curs;
+
+    buffer_curs = 0;
+    /* Skip initial white space */
+    while (ISSPACE(buffer[buffer_curs])) buffer_curs++;
+
+    {
+        /* Read off label, put as much as will fit into label[] */
+        int label_curs;
+        label_curs = 0;
+        while (!ISSPACE(buffer[buffer_curs]) && buffer[buffer_curs] != '\0') {
+            if (label_curs < MAX_LABEL_LENGTH) 
+                label[label_curs++] = buffer[buffer_curs];
+            buffer_curs++;
+        }
+        label[label_curs] = '\0';  /* null terminate it */
+    }    
+
+    /* Skip white space between label and value */
+    while (ISSPACE(buffer[buffer_curs])) buffer_curs++;
+
+    /* copy value into value[] */
+    strncpy(value, buffer+buffer_curs, MAX_VALUE_LENGTH+1);
+
+    {
+        /* Remove trailing white space from value[] */
+        int value_curs;
+        value_curs = strlen(value)-1;
+        while (value_curs >= 0 && ISSPACE(value[value_curs])) 
+            value[value_curs--] = '\0';
+    }
+}
+
+
+
+struct headerSeen {
+/*----------------------------------------------------------------------------
+   This structure tells what we've seen so far in our progress through the
+   PAM header
+-----------------------------------------------------------------------------*/
+    bool width;
+    bool height;
+    bool depth;
+    bool maxval;
+    bool endhdr;
+};
+
+
+
+static void
+process_header_line(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.
+
+   Put the value that the line defines in *pamP (unless it's ENDHDR).
+
+   Update *headerSeenP with whatever we see.
+-----------------------------------------------------------------------------*/
+    char label[MAX_LABEL_LENGTH+1];
+    char value[MAX_VALUE_LENGTH+1];
+
+    parse_header_line(buffer, label, value);
+
+    if (strcmp(label, "ENDHDR") == 0)
+        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 {
+                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);
+            }
+        }
+    
+        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) {
+            int len = strlen(pamP->tuple_type);
+            if (len + strlen(value) + 1 > sizeof(pamP->tuple_type)-1)
+                pm_error("TUPLTYPE value too long in PAM header");
+            if (len == 0)
+                strcpy(pamP->tuple_type, value);
+            else {
+                strcat(pamP->tuple_type, "\n");
+                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);
+    }
+}
+
+
+
+static void
+appendComment(char **      const commentsP,
+              const char * const commentHeader) {
+
+    const char * const commentLine = &commentHeader[1];
+
+    size_t commentLen = strlen(*commentsP) + strlen(commentLine) + 1;
+
+    assert(commentHeader[0] == '#');
+
+    REALLOCARRAY(*commentsP, commentLen);
+
+    if (*commentsP == NULL)
+        pm_error("Couldn't get storage for %u characters of comments from "
+                 "the PAM header", commentLen);
+
+    strcat(*commentsP, commentLine);
+}
+
+
+
+static void
+disposeOfComments(const struct pam * const pamP,
+                  const char *       const comments) {
+
+    const char ** const retP = pamCommentP(pamP);
+
+    if (retP)
+        *retP = comments;
+    else
+        strfree(comments);
+}
+
+
+
+static void
+readpaminitrest(struct pam * const pamP) {
+/*----------------------------------------------------------------------------
+   Read the rest of the PAM header (after the first line -- the magic
+   number line).  Fill in all the information in *pamP.
+-----------------------------------------------------------------------------*/
+    struct headerSeen headerSeen;
+    char * comments;
+
+    headerSeen.width  = FALSE;
+    headerSeen.height = FALSE;
+    headerSeen.depth  = FALSE;
+    headerSeen.maxval = FALSE;
+    headerSeen.endhdr = FALSE;
+
+    pamP->tuple_type[0] = '\0';
+
+    comments = strdup("");
+
+    { 
+        int c;
+        /* Read off rest of 1st line -- probably just the newline after the 
+           magic number 
+        */
+        while ((c = getc(pamP->file)) != -1 && c != '\n');
+    }    
+
+    while (!headerSeen.endhdr) {
+        char buffer[256];
+        char * rc;
+        rc = fgets(buffer, sizeof(buffer), pamP->file);
+        if (rc == NULL)
+            pm_error("EOF or error reading file while trying to read the "
+                     "PAM header");
+        else {
+            buffer[256-1-1] = '\n';  /* In case fgets() truncated */
+            if (buffer[0] == '#')
+                appendComment(&comments, buffer);
+            else if (stripeq(buffer, ""));
+                /* Ignore it; it's a blank line */
+            else 
+                process_header_line(buffer, pamP, &headerSeen);
+        }
+    }
+
+    disposeOfComments(pamP, comments);
+
+    if (!headerSeen.height)
+        pm_error("No HEIGHT header line in PAM header");
+    if (!headerSeen.width)
+        pm_error("No WIDTH header line in PAM header");
+    if (!headerSeen.depth)
+        pm_error("No DEPTH header line in PAM header");
+    if (!headerSeen.maxval)
+        pm_error("No MAXVAL header line in PAM header");
+
+    if (pamP->height == 0) 
+        pm_error("HEIGHT value is zero in PAM header");
+    if (pamP->width == 0) 
+        pm_error("WIDTH value is zero in PAM header");
+    if (pamP->depth == 0) 
+        pm_error("DEPTH value is zero in PAM header");
+    if (pamP->maxval == 0) 
+        pm_error("MAXVAL value is zero in PAM header");
+    if (pamP->maxval > PAM_OVERALL_MAXVAL)
+        pm_error("MAXVAL value (%lu) in PAM header is greater than %u",
+                 pamP->maxval, PAM_OVERALL_MAXVAL);
+}
+
+
+
+void
+pnm_readpaminitrestaspnm(FILE * const fileP, 
+                         int *  const colsP, 
+                         int *  const rowsP, 
+                         gray * const maxvalP,
+                         int *  const formatP) {
+/*----------------------------------------------------------------------------
+   Read the rest of the PAM header (after the first line) and return
+   information as if it were PPM or PGM.
+
+   Die if it isn't a PAM of the sort we can treat as PPM or PGM.
+-----------------------------------------------------------------------------*/
+    struct pam pam;
+
+    pam.size        = PAM_STRUCT_SIZE(tuple_type);
+    pam.file        = fileP;
+    pam.len         = sizeof(struct pam);
+    pam.format      = PAM_FORMAT;
+
+    readpaminitrest(&pam);
+
+
+    /* A PAM raster of depth 1 is identical to a PGM raster.  A PAM
+       raster of depth 3 is identical to PPM raster.  So
+       ppm_readppmrow() will be able to read the PAM raster as long as
+       the format it thinks it is (PGM or PPM) corresponds to the PAM
+       depth.  Similar for pgm_readpgmrow().
+    */
+    switch (pam.depth) {
+    case 3:
+        *formatP = RPPM_FORMAT;
+        break;
+    case 1:
+        *formatP = RPGM_FORMAT;
+        break;
+    default: {
+        pm_error("Cannot treat PAM image as PPM or PGM, "
+                 "because its depth (%u) "
+                 "is not 1 or 3.", pam.depth);
+    }
+    }
+
+    *colsP   = pam.width;
+    *rowsP   = pam.height;
+    *maxvalP = (gray)pam.maxval;
+}
+
+
+                
+unsigned int
+pnm_bytespersample(sample const maxval) {
+
+    assert(sizeof(maxval) * 8 <= 32);
+
+    if      (maxval >>  8 == 0) return 1;
+    else if (maxval >> 16 == 0) return 2;
+    else if (maxval >> 24 == 0) return 3;
+    else                        return 4;
+}
+
+
+
+void 
+pnm_readpaminit(FILE *       const file, 
+                struct pam * const pamP, 
+                int          const size) {
+
+    if (size < PAM_STRUCT_SIZE(tuple_type)) 
+        pm_error("pam object passed to pnm_readpaminit() is too small.  "
+                 "It must be large\n"
+                 "enough to hold at least up to the "
+                 "'tuple_type' member, but according\n"
+                 "to the 'size' argument, it is only %d bytes long.", 
+                 size);
+
+    pamP->size = size;
+    pamP->file = file;
+    pamP->len = MIN(pamP->size, sizeof(struct pam));
+
+    if (size >= PAM_STRUCT_SIZE(allocation_depth))
+        pamP->allocation_depth = 0;
+
+    /* Get magic number. */
+    pamP->format = pm_readmagicnumber(file);
+
+    switch (PAM_FORMAT_TYPE(pamP->format)) {
+    case PAM_TYPE: 
+        readpaminitrest(pamP);
+        break;
+    case PPM_TYPE: {
+        pixval maxval;
+        ppm_readppminitrest(pamP->file, &pamP->width, &pamP->height, &maxval);
+        pamP->maxval = (sample) maxval;
+        pamP->depth = 3;
+        strcpy(pamP->tuple_type, PAM_PPM_TUPLETYPE);
+        if (pamCommentP(pamP))
+            *pamP->comment_p = strdup("");
+    } break;
+
+    case PGM_TYPE: {
+        gray maxval;
+        pgm_readpgminitrest(pamP->file, &pamP->width, &pamP->height, &maxval);
+        pamP->maxval = (sample) maxval;
+        pamP->depth = 1;
+        strcpy(pamP->tuple_type, PAM_PGM_TUPLETYPE);
+        if (pamCommentP(pamP))
+            *pamP->comment_p = strdup("");
+    } break;
+
+    case PBM_TYPE:
+        pbm_readpbminitrest(pamP->file, &pamP->width,&pamP->height);
+        pamP->maxval = (sample) 1;
+        pamP->depth = 1;
+        strcpy(pamP->tuple_type, PAM_PBM_TUPLETYPE);
+        if (pamCommentP(pamP))
+            *pamP->comment_p = strdup("");
+        break;
+        
+    default:
+        pm_error("bad magic number - not a PAM, PPM, PGM, or PBM file");
+    }
+    
+    pamP->bytes_per_sample = pnm_bytespersample(pamP->maxval);
+    pamP->plainformat = FALSE;
+        /* See below for complex explanation of why this is FALSE. */
+
+    validateComputableSize(pamP);
+}
+
+
+
+static void
+writeComments(const struct pam * const pamP) {
+/*----------------------------------------------------------------------------
+   Write comments for a PAM header, insofar as *pamP specifies comments.
+-----------------------------------------------------------------------------*/
+    const char ** const commentP = pamCommentP(pamP);
+
+    if (commentP) {
+        const char * const comment = *commentP;
+
+        const char * p;
+        bool startOfLine;
+
+        for (p = &comment[0], startOfLine = TRUE; *p; ++p) {
+            if (startOfLine)
+                fputc('#', pamP->file);
+                
+            fputc(*p, pamP->file);
+
+            if (*p == '\n')
+                startOfLine = TRUE;
+            else
+                startOfLine = FALSE;
+        }
+        if (!startOfLine)
+            fputc('\n', pamP->file);
+    }
+}
+
+
+
+/* About the 'plainformat' member on image input operations:
+
+   'plainformat' is meaningless when reading an image, but we always
+   set it FALSE anyway.
+
+   That's because it is common for a program to copy a pam structure
+   used for input as a pam structure for output, and just update the
+   few fields it cares about -- mainly 'file'.  We want a program like
+   that to write a raw format image, and 'plainformat' in an output
+   pam structure is what determines whether it is raw or plain.  So we
+   set it false here so that it is false in the copied output pam
+   structure.
+   
+   Before 10.32, we set 'plainformat' according to the
+   plainness of the input image, and thought it was a good
+   idea for a program that reads a plain format input image to
+   write a plain format output image.  But that is not what
+   the older libnetpbm facilities (e.g. pnm_writepnm()) do, so
+   for compatibility, the pam facility shouldn't either.  This
+   came to light as we converted programs from the pnm/pbm/ppm
+   facilities to pam.
+*/
+
+void 
+pnm_writepaminit(struct pam * const pamP) {
+
+    const char * tupleType;
+
+    if (pamP->size < pamP->len)
+        pm_error("pam object passed to pnm_writepaminit() is smaller "
+                 "(%d bytes, according to its 'size' element) "
+                 "than the amount of data in it "
+                 "(%d bytes, according to its 'len' element).",
+                 pamP->size, pamP->len);
+
+    if (pamP->len < PAM_STRUCT_SIZE(bytes_per_sample))
+        pm_error("pam object passed to pnm_writepaminit() is too small.  "
+                 "It must be large\n"
+                 "enough to hold at least up through the "
+                 "'bytes_per_sample' member, but according\n"
+                 "to its 'len' member, it is only %d bytes long.", 
+                 pamP->len);
+
+    if (pamP->maxval > PAM_OVERALL_MAXVAL)
+        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))
+        tupleType = "";
+    else
+        tupleType = pamP->tuple_type;
+
+    pamP->bytes_per_sample = pnm_bytespersample(pamP->maxval);
+    
+    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");
+        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, ""))
+            fprintf(pamP->file, "TUPLTYPE %s\n", pamP->tuple_type);
+        fprintf(pamP->file, "ENDHDR\n");
+        break;
+    case PPM_TYPE:
+        /* The depth must be exact, because pnm_writepamrow() is controlled
+           by it, without regard to format.
+        */
+        if (pamP->depth != 3)
+            pm_error("pnm_writepaminit() got PPM format, but depth = %d "
+                     "instead of 3, as required for PPM.", 
+                     pamP->depth);
+        if (pamP->maxval > PPM_OVERALLMAXVAL) 
+            pm_error("pnm_writepaminit() got PPM format, but maxval = %ld, "
+                     "which exceeds the maximum allowed for PPM: %d", 
+                     pamP->maxval, PPM_OVERALLMAXVAL);
+        ppm_writeppminit(pamP->file, pamP->width, pamP->height, 
+                         (pixval) pamP->maxval, pamP->plainformat);
+        break;
+
+    case PGM_TYPE:
+        if (pamP->depth != 1)
+            pm_error("pnm_writepaminit() got PGM format, but depth = %d "
+                     "instead of 1, as required for PGM.",
+                     pamP->depth);
+        if (pamP->maxval > PGM_OVERALLMAXVAL)
+            pm_error("pnm_writepaminit() got PGM format, but maxval = %ld, "
+                     "which exceeds the maximum allowed for PGM: %d", 
+                     pamP->maxval, PGM_OVERALLMAXVAL);
+        pgm_writepgminit(pamP->file, pamP->width, pamP->height, 
+                         (gray) pamP->maxval, pamP->plainformat);
+        break;
+
+    case PBM_TYPE:
+        if (pamP->depth != 1)
+            pm_error("pnm_writepaminit() got PBM format, but depth = %d "
+                     "instead of 1, as required for PBM.",
+                     pamP->depth);
+        if (pamP->maxval != 1) 
+            pm_error("pnm_writepaminit() got PBM format, but maxval = %ld "
+                     "instead of 1, as required for PBM.", pamP->maxval);
+        pbm_writepbminit(pamP->file, pamP->width, pamP->height, 
+                         pamP->plainformat);
+        break;
+
+    default:
+        pm_error("Invalid format passed to pnm_writepaminit(): %d",
+                 pamP->format);
+    }
+}
+
+
+
+void
+pnm_checkpam(const struct pam *   const pamP, 
+             enum pm_check_type   const checkType, 
+             enum pm_check_code * const retvalP) {
+
+    if (checkType != PM_CHECK_BASIC) {
+        if (retvalP) *retvalP = PM_CHECK_UNKNOWN_TYPE;
+    } else switch (PAM_FORMAT_TYPE(pamP->format)) {
+    case PAM_TYPE: {
+        pm_filepos const need_raster_size = 
+            pamP->width * pamP->height * pamP->depth * pamP->bytes_per_sample;
+        pm_check(pamP->file, checkType, need_raster_size, retvalP);
+    }
+        break;
+    case PPM_TYPE:
+        pgm_check(pamP->file, checkType, pamP->format, 
+                  pamP->width, pamP->height, pamP->maxval, retvalP);
+        break;
+    case PGM_TYPE:
+        pgm_check(pamP->file, checkType, pamP->format, 
+                  pamP->width, pamP->height, pamP->maxval, retvalP);
+        break;
+    case PBM_TYPE:
+        pbm_check(pamP->file, checkType, pamP->format, 
+                  pamP->width, pamP->height, retvalP);
+        break;
+    default:
+        if (retvalP) *retvalP = PM_CHECK_UNCHECKABLE;
+    }
+}
+
+
+
+void 
+pnm_maketuplergb(const struct pam * const pamP,
+                 tuple              const tuple) {
+
+    if (allocationDepth(pamP) < 3)
+        pm_error("allocation depth %u passed to pnm_maketuplergb().  "
+                 "Must be at least 3.", allocationDepth(pamP));
+
+    if (pamP->depth < 3)
+        tuple[2] = tuple[1] = tuple[0];
+}
+
+
+
+void 
+pnm_makerowrgb(const struct pam * const pamP,
+               tuple *            const tuplerow) {
+    
+    if (pamP->depth < 3) {
+        unsigned int col;
+
+        if (allocationDepth(pamP) < 3)
+            pm_error("allocation depth %u passed to pnm_makerowrgb().  "
+                     "Must be at least 3.", allocationDepth(pamP));
+        
+        if (strncmp(pamP->tuple_type, "RGB", 3) != 0) {
+            for (col = 0; col < pamP->width; ++col) {
+                tuple const thisTuple = tuplerow[col];
+                thisTuple[2] = thisTuple[1] = thisTuple[0];
+            }
+        }
+    }
+}
+
+
+
+void 
+pnm_makearrayrgb(const struct pam * const pamP,
+                 tuple **           const tuples) {
+
+    if (pamP->depth < 3) {
+        unsigned int row;
+        if (allocationDepth(pamP) < 3)
+            pm_error("allocation depth %u passed to pnm_makearrayrgb().  "
+                     "Must be at least 3.", allocationDepth(pamP));
+        
+        for (row = 0; row < pamP->height; ++row) {
+            tuple * const tuplerow = tuples[row];
+            unsigned int col;
+            for (col = 0; col < pamP->width; ++col) {
+                tuple const thisTuple = tuplerow[col];
+                thisTuple[2] = thisTuple[1] = thisTuple[0];
+            }
+        }
+    }
+}
+
+
+
+void
+pnm_getopacity(const struct pam * const pamP,
+               bool *             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.
+    */
+    if (strcmp(pamP->tuple_type, "RGB_ALPHA") == 0) {
+        *haveOpacityP = TRUE;
+        *opacityPlaneP = PAM_TRN_PLANE;
+    } else if (strcmp(pamP->tuple_type, "GRAYSCALE_ALPHA") == 0) {
+        *haveOpacityP = TRUE;
+        *opacityPlaneP = PAM_GRAY_TRN_PLANE;
+    } else
+        *haveOpacityP = FALSE;
+}
+
+
+
+/*=============================================================================
+   pm_system() Standard Input feeder and Standard Output accepter functions.   
+=============================================================================*/
+
+void
+pm_feed_from_pamtuples(int    const pipeToFeedFd,
+                       void * const feederParm) {
+
+    struct pamtuples * const inputTuplesP = feederParm;
+
+    struct pam outpam;
+
+    outpam = *inputTuplesP->pamP;
+    outpam.file = fdopen(pipeToFeedFd, "w");
+
+    /* The following signals (and normally kills) the process with
+       SIGPIPE if the pipe does not take all 'inputLength' bytes.
+    */
+    pnm_writepam(&outpam, *inputTuplesP->tuplesP);
+
+    pm_close(outpam.file);
+}
+
+
+
+void
+pm_accept_to_pamtuples(int    const pipeToSuckFd,
+                       void * const accepterParm ) {
+
+    struct pamtuples * const outputTuplesP = accepterParm;
+
+    struct pam inpam;
+
+    *outputTuplesP->tuplesP =
+        pnm_readpam(fdopen(pipeToSuckFd, "r"), &inpam, sizeof(inpam));
+
+    pm_close(inpam.file);
+}