about summary refs log tree commit diff
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2011-08-04 02:22:57 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2011-08-04 02:22:57 +0000
commitbe7a0f83c56b8ebe89f522efc4f0643d4818cd4f (patch)
tree4f1aae8316c4280339356b49a5a4f6d46a088a60
parent0bbc0d23d5ed7bdde9045a784525d141aa98d286 (diff)
downloadnetpbm-mirror-be7a0f83c56b8ebe89f522efc4f0643d4818cd4f.tar.gz
netpbm-mirror-be7a0f83c56b8ebe89f522efc4f0643d4818cd4f.tar.xz
netpbm-mirror-be7a0f83c56b8ebe89f522efc4f0643d4818cd4f.zip
pamcomp: retain opacity information from underlying image
git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@1526 9d0c8265-081b-0410-96cb-a4ca84ce46f8
-rw-r--r--doc/HISTORY2
-rw-r--r--editor/pamcomp.c262
-rw-r--r--lib/libpam.c210
-rw-r--r--lib/pam.h25
4 files changed, 429 insertions, 70 deletions
diff --git a/doc/HISTORY b/doc/HISTORY
index c9524a93..d256e921 100644
--- a/doc/HISTORY
+++ b/doc/HISTORY
@@ -6,6 +6,8 @@ CHANGE HISTORY
 
 not yet  BJH  Release 10.56.00
 
+              pamcomp: Retain opacity information from underlying image.
+
               pnmtops: Add PBM fast path.  Thanks Prophet of the Way
               <afu@wta.att.ne.jp>.
 
diff --git a/editor/pamcomp.c b/editor/pamcomp.c
index afdf4451..78f30e9c 100644
--- a/editor/pamcomp.c
+++ b/editor/pamcomp.c
@@ -217,32 +217,65 @@ commonFormat(int const formatA,
 
 
 
-static void
+typedef enum { TT_BLACKANDWHITE, TT_GRAYSCALE, TT_RGB } BaseTupletype;
+
+
+
+static BaseTupletype
 commonTupletype(const char * const tupletypeA, 
-                const char * const tupletypeB, 
-                char *       const tupletypeOut,
-                unsigned int const size) {
+                const char * const tupletypeB) {
 
     if (strneq(tupletypeA, "RGB", 3) ||
         strneq(tupletypeB, "RGB", 3))
-        strncpy(tupletypeOut, "RGB", size);
+        return TT_RGB;
     else if (strneq(tupletypeA, "GRAYSCALE", 9) ||
              strneq(tupletypeB, "GRAYSCALE", 9))
-        strncpy(tupletypeOut, "GRAYSCALE", size);
+        return TT_GRAYSCALE;
     else if (strneq(tupletypeA, "BLACKANDWHITE", 13) ||
              strneq(tupletypeB, "BLACKANDWHITE", 13))
-        strncpy(tupletypeOut, "BLACKANDWHITE", size);
+        return TT_BLACKANDWHITE;
     else
         /* Results are undefined for this case, so we do a hail Mary. */
-        strncpy(tupletypeOut, tupletypeA, size);
+        return TT_RGB;
 }
 
 
 
 static void
-determineOutputType(struct pam * const composedPamP,
-                    struct pam * const underlayPamP,
-                    struct pam * const overlayPamP) {
+determineOutputTupleType(BaseTupletype const baseTupletype,
+                         bool          const underlayHaveOpacity,
+                         char *        const tupleType,
+                         size_t        const size) {
+
+    char buffer[80];
+
+    switch (baseTupletype) {
+    case TT_BLACKANDWHITE:
+        STRSCPY(buffer, "RGB");
+        break;
+    case TT_GRAYSCALE:
+        STRSCPY(buffer, "GRAYSCALE");
+        break;
+    case TT_RGB:
+        STRSCPY(buffer, "RGB");
+        break;
+    }
+
+    if (underlayHaveOpacity)
+        STRSCAT(buffer, "_ALPHA");
+
+    strncpy(tupleType, buffer, size);
+}
+
+
+
+static void
+determineOutputType(const struct pam * const underlayPamP,
+                    const struct pam * const overlayPamP,
+                    struct pam *       const composedPamP) {
+
+    BaseTupletype const baseTupletype =
+        commonTupletype(underlayPamP->tuple_type, overlayPamP->tuple_type);
 
     composedPamP->height = underlayPamP->height;
     composedPamP->width  = underlayPamP->width;
@@ -250,22 +283,22 @@ determineOutputType(struct pam * const composedPamP,
     composedPamP->format = commonFormat(underlayPamP->format, 
                                         overlayPamP->format);
     composedPamP->plainformat = FALSE;
-    commonTupletype(underlayPamP->tuple_type, overlayPamP->tuple_type,
-                    composedPamP->tuple_type, 
-                    sizeof(composedPamP->tuple_type));
 
     composedPamP->maxval = pm_lcm(underlayPamP->maxval, overlayPamP->maxval, 
                                   1, PNM_OVERALLMAXVAL);
 
-    if (streq(composedPamP->tuple_type, "RGB"))
-        composedPamP->depth = 3;
-    else if (streq(composedPamP->tuple_type, "GRAYSCALE"))
-        composedPamP->depth = 1;
-    else if (streq(composedPamP->tuple_type, "BLACKANDWHITE"))
-        composedPamP->depth = 1;
-    else
-        /* Results are undefined for this case, so we just do something safe */
-        composedPamP->depth = MIN(underlayPamP->depth, overlayPamP->depth);
+    composedPamP->visual = true;
+    composedPamP->color_depth = (baseTupletype == TT_RGB ? 3 : 1);
+    composedPamP->have_opacity = underlayPamP->have_opacity;
+    composedPamP->opacity_plane = (baseTupletype == TT_RGB ? 3 : 1);
+
+    composedPamP->depth =
+        (baseTupletype == TT_RGB ? 3 : 1) +
+        (underlayPamP->have_opacity ? 1 : 0);
+
+    determineOutputTupleType(baseTupletype, underlayPamP->have_opacity,
+                             composedPamP->tuple_type,
+                             sizeof(composedPamP->tuple_type));
 }
 
 
@@ -381,8 +414,9 @@ composeComponents(sample           const compA,
                   sample           const maxval,
                   enum sampleScale const sampleScale) {
 /*----------------------------------------------------------------------------
-  Compose a single component of each of two pixels, with 'distrib' being
+  Compose a single color component of each of two pixels, with 'distrib' being
   the fraction of 'compA' in the result, 1-distrib the fraction of 'compB'.
+  Note that this does not apply to an opacity component.
 
   'sampleScale' tells in what domain the 'distrib' fraction applies:
   brightness or light intensity (gamma-adjusted or not).
@@ -426,8 +460,6 @@ overlayPixel(tuple            const overlayTuple,
              struct pam *     const underlayPamP,
              tuplen           const alphaTuplen,
              bool             const invertAlpha,
-             bool             const overlayHasOpacity,
-             unsigned int     const opacityPlane,
              tuple            const composedTuple,
              struct pam *     const composedPamP,
              float            const masterOpacity,
@@ -446,8 +478,7 @@ overlayPixel(tuple            const overlayTuple,
    underlay pixel shows through and 0 means the overlay pixel is invisible and
    the underlay pixel shows through in full force.
 
-     'overlayHasOpacity' means that 'overlayTuple' has an opacity component,
-     in particular its 'opacityPlane' sample.
+     if *overlayPamP may indicate that 'overlayTuple' has an opacity component.
 
      'alphaTuplen' is a normalized tuple whose first sample is the opacity
      factor, except that iff 'invertAlpha' is true, it is a transparency
@@ -467,9 +498,9 @@ overlayPixel(tuple            const overlayTuple,
 
     overlayWeight = masterOpacity;  /* initial value */
     
-    if (overlayHasOpacity)
+    if (overlayPamP->have_opacity)
         overlayWeight *= (float)
-            overlayTuple[opacityPlane] / overlayPamP->maxval;
+            overlayTuple[overlayPamP->opacity_plane] / overlayPamP->maxval;
     
     if (alphaTuplen) {
         float const alphaval = 
@@ -480,31 +511,48 @@ overlayPixel(tuple            const overlayTuple,
     {
         unsigned int plane;
         
-        for (plane = 0; plane < composedPamP->depth; ++plane)
+        for (plane = 0; plane < composedPamP->color_depth; ++plane)
             composedTuple[plane] = 
                 composeComponents(overlayTuple[plane], underlayTuple[plane], 
                                   overlayWeight,
                                   composedPamP->maxval,
                                   sampleScale);
+
+        if (composedPamP->have_opacity) {
+            /* copy opacity from underlay to output */
+            assert(underlayPamP->have_opacity);
+            composedTuple[composedPamP->opacity_plane] =
+                underlayTuple[underlayPamP->opacity_plane];
+        }
     }
 }
 
 
 
 static void
-adaptRowToOutputFormat(struct pam * const inpamP,
-                       tuple *      const tuplerow,
-                       struct pam * const outpamP) {
+adaptRowFormat(struct pam * const inpamP,
+               struct pam * const outpamP,
+               tuple *      const tuplerow) {
 /*----------------------------------------------------------------------------
    Convert the row in 'tuplerow', which is in a format described by
    *inpamP, to the format described by *outpamP.
 
    'tuplerow' must have enough allocated depth to do this.
 -----------------------------------------------------------------------------*/
+    assert(outpamP->visual);
+    assert(inpamP->visual);
+
     pnm_scaletuplerow(inpamP, tuplerow, tuplerow, outpamP->maxval);
 
-    if (strneq(outpamP->tuple_type, "RGB", 3))
-        pnm_makerowrgb(inpamP, tuplerow);
+    if (outpamP->color_depth == 3) {
+        if (outpamP->have_opacity)
+            pnm_makerowrgba(inpamP, tuplerow);
+        else
+            pnm_makerowrgb(inpamP, tuplerow);
+    } else {
+        if (outpamP->have_opacity)
+            pnm_addopacityrow(inpamP, tuplerow);
+    }
 }
 
 
@@ -515,16 +563,21 @@ composeRow(int              const originleft,
            struct pam *     const overlayPamP,
            bool             const invertAlpha,
            float            const masterOpacity,
-           bool             const overlayHasOpacity,
-           unsigned int     const opacityPlane,
            struct pam *     const composedPamP,
            enum sampleScale const sampleScale,
            const tuple *    const underlayTuplerow,
            const tuple *    const overlayTuplerow,
            const tuplen *   const alphaTuplerown,
            tuple *          const composedTuplerow) {
+/*----------------------------------------------------------------------------
+   Create a row of tuples ('composedTupleRow') which is the composition of
+   row 'overlayTupleRow' laid over row 'underlayTupleRow', starting at
+   column 'originLeft'.
 
+   *underlayPamP and *overlayPamP describe the respective tuple rows.
+-----------------------------------------------------------------------------*/
     unsigned int col;
+
     for (col = 0; col < composedPamP->width; ++col) {
         int const ovlcol = col - originleft;
 
@@ -535,7 +588,6 @@ composeRow(int              const originleft,
             overlayPixel(overlayTuplerow[ovlcol], overlayPamP,
                          underlayTuplerow[col], underlayPamP,
                          alphaTuplen, invertAlpha,
-                         overlayHasOpacity, opacityPlane,
                          composedTuplerow[col], composedPamP,
                          masterOpacity, sampleScale);
         } else
@@ -548,6 +600,55 @@ composeRow(int              const originleft,
 
 
 static void
+determineInputAdaptations(const struct pam * const underlayPamP,
+                          const struct pam * const overlayPamP,
+                          const struct pam * const composedPamP,
+                          struct pam *       const adaptUnderlayPamP,
+                          struct pam *       const adaptOverlayPamP) {
+/*----------------------------------------------------------------------------
+   For easy of computation, this program reads a tuple row from one of the
+   input files, then transforms it something similar to the format of the
+   eventual output tuple row.  E.g. if the input is grayscale and the
+   output color, it converts the depth 1 row read from the file to a depth
+   3 row for use in computations.
+
+   This function determines what the result of that transformation should be.
+   It's not as simple as it sounds because of opacity.  The overlay may have
+   an opacity plane that has to be kept for the computations, while the output
+   has no opacity plane.
+
+   Our output PAMs are meaningless except in the fields that pertain to a
+   row of tuples.  E.g. the file descriptor and image height members are
+   meaningless.
+-----------------------------------------------------------------------------*/
+    /* We make the underlay row identical to the composed (output) row,
+       except for its width.
+    */
+
+    *adaptUnderlayPamP = *composedPamP;
+    adaptUnderlayPamP->width = underlayPamP->width;
+
+    /* Same for the overlay row, except that it retains is original
+       opacity.
+    */
+
+    adaptOverlayPamP->width = overlayPamP->width;
+    adaptOverlayPamP->tuple_type[0] = '\0';  /* a hack; this doesn't matter */
+    adaptOverlayPamP->visual = true;
+    adaptOverlayPamP->color_depth = composedPamP->color_depth;
+    adaptOverlayPamP->have_opacity = overlayPamP->have_opacity;
+    adaptOverlayPamP->opacity_plane = composedPamP->color_depth;
+    adaptOverlayPamP->depth =
+        composedPamP->color_depth +
+        (overlayPamP->have_opacity ? 1 : 0);
+    adaptOverlayPamP->maxval = composedPamP->maxval;
+    adaptOverlayPamP->bytes_per_sample = composedPamP->bytes_per_sample;
+    adaptOverlayPamP->allocation_depth = overlayPamP->allocation_depth;
+}
+
+
+
+static void
 composite(int          const originleft, 
           int          const origintop, 
           struct pam * const underlayPamP,
@@ -584,12 +685,10 @@ composite(int          const originleft,
     int overlayRow;   /* NB may be negative */
     tuple * composedTuplerow;
     tuple * underlayTuplerow;
+    struct pam adaptUnderlayPam;
     tuple * overlayTuplerow;
+    struct pam adaptOverlayPam;
     tuplen * alphaTuplerown;
-    bool overlayHasOpacity;
-    unsigned int opacityPlane;
-
-    pnm_getopacity(overlayPamP, &overlayHasOpacity, &opacityPlane);
 
     composedTuplerow = pnm_allocpamrow(composedPamP);
     underlayTuplerow = pnm_allocpamrow(underlayPamP);
@@ -599,6 +698,9 @@ composite(int          const originleft,
     else
         alphaTuplerown = NULL;
 
+    determineInputAdaptations(underlayPamP, overlayPamP, composedPamP,
+                              &adaptUnderlayPam, &adaptOverlayPam);
+
     pnm_writepaminit(composedPamP);
 
     assert(INT_MAX - overlayPamP->height > origintop); /* arg constraint */
@@ -610,15 +712,13 @@ composite(int          const originleft,
 
         if (overlayRow >= 0 && overlayRow < overlayPamP->height) {
             pnm_readpamrow(overlayPamP, overlayTuplerow);
-            adaptRowToOutputFormat(overlayPamP, overlayTuplerow, composedPamP);
+            adaptRowFormat(overlayPamP, &adaptOverlayPam, overlayTuplerow);
             if (alphaPamP)
                 pnm_readpamrown(alphaPamP, alphaTuplerown);
         }
         if (underlayRow >= 0 && underlayRow < underlayPamP->height) {
             pnm_readpamrow(underlayPamP, underlayTuplerow);
-            adaptRowToOutputFormat(underlayPamP, underlayTuplerow, 
-                                   composedPamP);
-
+            adaptRowFormat(underlayPamP, &adaptUnderlayPam, underlayTuplerow);
             if (underlayRow < origintop || 
                 underlayRow >= origintop + overlayPamP->height) {
             
@@ -626,9 +726,9 @@ composite(int          const originleft,
 
                 pnm_writepamrow(composedPamP, underlayTuplerow);
             } else {
-                composeRow(originleft, underlayPamP, overlayPamP,
-                           invertAlpha, masterOpacity, overlayHasOpacity,
-                           opacityPlane, composedPamP, sampleScale,
+                composeRow(originleft, &adaptUnderlayPam, &adaptOverlayPam,
+                           invertAlpha, masterOpacity, 
+                           composedPamP, sampleScale,
                            underlayTuplerow, overlayTuplerow, alphaTuplerown,
                            composedTuplerow);
                 
@@ -645,6 +745,30 @@ composite(int          const originleft,
 
 
 
+static void
+initAlphaFile(struct cmdlineInfo const cmdline,
+              struct pam *       const overlayPamP,
+              FILE **            const filePP,
+              struct pam *       const pamP) {
+
+    FILE * fileP;
+    
+    if (cmdline.alphaFilespec) {
+        fileP = pm_openr(cmdline.alphaFilespec);
+        pamP->comment_p = NULL;
+        pnm_readpaminit(fileP, pamP, PAM_STRUCT_SIZE(opacity_plane));
+
+        if (overlayPamP->width != pamP->width || 
+            overlayPamP->height != pamP->height)
+            pm_error("Opacity map and overlay image are not the same size");
+    } else
+        fileP = NULL;
+
+    *filePP = fileP;
+}
+
+
+
 int
 main(int argc, const char *argv[]) {
 
@@ -663,34 +787,46 @@ main(int argc, const char *argv[]) {
     parseCommandLine(argc, argv, &cmdline);
 
     overlayFileP = pm_openr(cmdline.overlayFilespec);
+
+    overlayPam.comment_p = NULL;
     pnm_readpaminit(overlayFileP, &overlayPam, 
-                    PAM_STRUCT_SIZE(allocation_depth));
-    if (cmdline.alphaFilespec) {
-        alphaFileP = pm_openr(cmdline.alphaFilespec);
-        pnm_readpaminit(alphaFileP, &alphaPam, 
-                        PAM_STRUCT_SIZE(allocation_depth));
+                    PAM_STRUCT_SIZE(opacity_plane));
 
-        if (overlayPam.width != alphaPam.width || 
-            overlayPam.height != overlayPam.height)
-            pm_error("Opacity map and overlay image are not the same size");
-    } else
-        alphaFileP = NULL;
+    if (overlayPam.len < PAM_STRUCT_SIZE(opacity_plane))
+        pm_error("Libnetpbm is too old.  This program requires libnetpbm from "
+                 "Netpbm 10.56 (September 2011) or newer");
+
+    if (!overlayPam.visual)
+        pm_error("Overlay image has tuple type '%s', which is not a "
+                 "standard visual type.  We don't know how to compose.",
+                 overlayPam.tuple_type);
+
+    initAlphaFile(cmdline, &overlayPam, &alphaFileP, &alphaPam);
 
     underlayFileP = pm_openr(cmdline.underlyingFilespec);
 
+    underlayPam.comment_p = NULL;
     pnm_readpaminit(underlayFileP, &underlayPam, 
-                    PAM_STRUCT_SIZE(allocation_depth));
+                    PAM_STRUCT_SIZE(opacity_plane));
+
+    assert(underlayPam.len >= PAM_STRUCT_SIZE(opacity_plane));
 
+    if (!overlayPam.visual)
+        pm_error("Overlay image has tuple type '%s', which is not a "
+                 "standard visual type.  We don't know how to compose.",
+                 overlayPam.tuple_type);
+    
     computeOverlayPosition(underlayPam.width, underlayPam.height, 
                            overlayPam.width,  overlayPam.height,
                            cmdline, &originLeft, &originTop);
 
-    composedPam.size             = sizeof(composedPam);
+    composedPam.size             = PAM_STRUCT_SIZE(opacity_plane);
     composedPam.len              = PAM_STRUCT_SIZE(allocation_depth);
     composedPam.allocation_depth = 0;
     composedPam.file             = pm_openw(cmdline.outputFilespec);
+    composedPam.comment_p        = NULL;
 
-    determineOutputType(&composedPam, &underlayPam, &overlayPam);
+    determineOutputType(&underlayPam, &overlayPam, &composedPam);
 
     pnm_setminallocationdepth(&underlayPam, composedPam.depth);
     pnm_setminallocationdepth(&overlayPam,  composedPam.depth);
diff --git a/lib/libpam.c b/lib/libpam.c
index 8af2c04a..9ae33f08 100644
--- a/lib/libpam.c
+++ b/lib/libpam.c
@@ -70,6 +70,7 @@ pamCommentP(const struct pam * const pamP) {
 }
 
 
+
 static void
 validateComputableSize(struct pam * const pamP) {
 /*----------------------------------------------------------------------------
@@ -764,6 +765,107 @@ pnm_bytespersample(sample const maxval) {
 
 
 
+static void
+validateMinDepth(const struct pam * const pamP,
+                 unsigned int       const minDepth) {
+
+    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 implied by
+   the pamP->tuple_type 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 (STRSEQ(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 (STRSEQ(tupleType, "GRAYSCALE")) {
+            visual = true;
+            colorDepth = 1;
+            haveOpacity = false;
+        } else if (STRSEQ(tupleType, "GRAYSCALE_ALPHA")) {
+            visual = true;
+            colorDepth = 1;
+            haveOpacity = true;
+            opacityPlane = PAM_GRAY_TRN_PLANE;
+            validateMinDepth(pamP, 2);
+        } else if (STRSEQ(tupleType, "RGB")) {
+            visual = true;
+            colorDepth = 3;
+            haveOpacity = false;
+            validateMinDepth(pamP, 3);
+        } else if (STRSEQ(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->have_opacity = haveOpacity;
+}
+
+
+
 void 
 pnm_readpaminit(FILE *       const file, 
                 struct pam * const pamP, 
@@ -829,6 +931,8 @@ pnm_readpaminit(FILE *       const file,
     pamP->plainformat = FALSE;
         /* See below for complex explanation of why this is FALSE. */
 
+    interpretTupleType(pamP);
+
     validateComputableSize(pamP);
 }
 
@@ -916,14 +1020,18 @@ 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);
+
+    interpretTupleType(pamP);
+
+    pamP->len = MIN(pamP->size, sizeof(struct pam));
     
     switch (PAM_FORMAT_TYPE(pamP->format)) {
     case PAM_TYPE:
@@ -1081,14 +1189,102 @@ 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,
                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 'haveOpacity', etc.
+       to struct pam.
     */
     if (streq(pamP->tuple_type, "RGB_ALPHA")) {
         *haveOpacityP = TRUE;
diff --git a/lib/pam.h b/lib/pam.h
index 4fa478ea..42d1dc75 100644
--- a/lib/pam.h
+++ b/lib/pam.h
@@ -109,6 +109,23 @@ struct pam {
            libnetpbm does not return comments and does not allocate any
            storage.
         */
+    int visual;  /* boolean */
+        /* tuple_type is one of the PAM-defined tuple types for visual
+           images ("GRAYSCALE", "RGB_ALPHA", etc.).
+        */
+    unsigned int color_depth;
+        /* Number of color planes (i.e. 'depth', but without transparency).
+           The color planes are the lowest numbered ones.  Meaningless if
+           'visual' is false.
+        */
+    int have_opacity;   /* boolean */
+        /* The tuples have an opacity (transparency, alpha) plane.
+           Meaningless if 'visual' is false.
+        */
+    unsigned int opacity_plane;
+        /* The plane number of the opacity plane;  meaningless if
+           'haveOpacity' is false or 'visual' is false.
+        */
 };
 
 #define PAM_HAVE_ALLOCATION_DEPTH 1
@@ -275,6 +292,14 @@ pnm_makearrayrgb(const struct pam * const pamP,
                  tuple **           const tuples);
 
 void
+pnm_makerowrgba(const struct pam * const pamP,
+                tuple *            const tuplerow);
+
+void
+pnm_addopacityrow(const struct pam * const pamP,
+                  tuple *            const tuplerow);
+
+void
 pnm_getopacity(const struct pam * const pamP,
                int *              const haveOpacityP,
                unsigned int *     const opacityPlaneP);