about summary refs log tree commit diff
path: root/editor/pamcomp.c
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2011-08-13 19:42:59 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2011-08-13 19:42:59 +0000
commit0fcb2bd2dbc09257b3b7c9fe92873cf115bfac59 (patch)
treef68a4fcfe12c40f9c569f7e095c321a6fbe81c2c /editor/pamcomp.c
parent0c3cb4b7fb69de19bd842d43fd68222a84d7bd5e (diff)
downloadnetpbm-mirror-0fcb2bd2dbc09257b3b7c9fe92873cf115bfac59.tar.gz
netpbm-mirror-0fcb2bd2dbc09257b3b7c9fe92873cf115bfac59.tar.xz
netpbm-mirror-0fcb2bd2dbc09257b3b7c9fe92873cf115bfac59.zip
Add -mixtransparency
git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@1539 9d0c8265-081b-0410-96cb-a4ca84ce46f8
Diffstat (limited to 'editor/pamcomp.c')
-rw-r--r--editor/pamcomp.c203
1 files changed, 175 insertions, 28 deletions
diff --git a/editor/pamcomp.c b/editor/pamcomp.c
index 78f30e9c..9855e173 100644
--- a/editor/pamcomp.c
+++ b/editor/pamcomp.c
@@ -39,13 +39,30 @@ enum vertPos {ABOVE, TOP, MIDDLE, BOTTOM, BELOW};
 
 
 enum sampleScale {INTENSITY_SAMPLE, GAMMA_SAMPLE};
-/* This indicates a scale for a PAM sample value.  INTENSITY_SAMPLE means
-   the value is proportional to light intensity; GAMMA_SAMPLE means the 
-   value is gamma-adjusted as defined in the PGM/PPM spec.  In both
-   scales, the values are continuous and normalized to the range 0..1.
+    /* This indicates a scale for a PAM sample value.  INTENSITY_SAMPLE means
+       the value is proportional to light intensity; GAMMA_SAMPLE means the 
+       value is gamma-adjusted as defined in the PGM/PPM spec.  In both
+       scales, the values are continuous and normalized to the range 0..1.
+       
+       This scale has no meaning if the PAM is not a visual image.  
+    */
 
-   This scale has no meaning if the PAM is not a visual image.  
-*/
+enum alphaMix {AM_KEEPUNDER, AM_OVERLAY};
+    /* This is a way to combine the alpha channels (transparency/opacity)
+       of the underlay and overlay images to form the alpha channel of the
+       composed result.
+
+       AM_KEEPUNDER means the alpha for the composed result is identical
+       to that of the underlay image.  I.e. the overlay merely modifies the
+       color.
+
+       AM_OVERLAY means the result is as if the underlay and overlay images
+       are plastic slides and they are taped together to form a composed slide.
+       So for one thing, the transparency of the composed image is the product
+       of the transparencies of the component images.  But the color is also
+       different, because the transparency of the underlaying image affects
+       its contribution to the composition.
+    */
 
 struct cmdlineInfo {
     /* All the information the user supplied in the command line,
@@ -61,6 +78,7 @@ struct cmdlineInfo {
     enum horizPos align;
     enum vertPos valign;
     unsigned int linear;
+    unsigned int mixtransparency;
 };
 
 
@@ -93,22 +111,24 @@ parseCommandLine(int                        argc,
     MALLOCARRAY_NOFAIL(option_def, 100);
 
     option_def_index = 0;   /* incremented by OPTENT3 */
-    OPTENT3(0, "invert",     OPT_FLAG,   NULL,                  
+    OPTENT3(0, "invert",             OPT_FLAG,   NULL,                  
             &cmdlineP->alphaInvert,       0);
-    OPTENT3(0, "xoff",       OPT_INT,    &cmdlineP->xoff,       
+    OPTENT3(0, "xoff",               OPT_INT,    &cmdlineP->xoff,       
             &xoffSpec,                    0);
-    OPTENT3(0, "yoff",       OPT_INT,    &cmdlineP->yoff,       
+    OPTENT3(0, "yoff",               OPT_INT,    &cmdlineP->yoff,       
             &yoffSpec,                    0);
-    OPTENT3(0, "opacity",    OPT_FLOAT, &cmdlineP->opacity,
+    OPTENT3(0, "opacity",            OPT_FLOAT,  &cmdlineP->opacity,
             &opacitySpec,                 0);
-    OPTENT3(0, "alpha",      OPT_STRING, &cmdlineP->alphaFilespec,
+    OPTENT3(0, "alpha",              OPT_STRING, &cmdlineP->alphaFilespec,
             &alphaSpec,                   0);
-    OPTENT3(0, "align",      OPT_STRING, &align,
+    OPTENT3(0, "align",              OPT_STRING, &align,
             &alignSpec,                   0);
-    OPTENT3(0, "valign",     OPT_STRING, &valign,
+    OPTENT3(0, "valign",             OPT_STRING, &valign,
             &valignSpec,                  0);
-    OPTENT3(0, "linear",     OPT_FLAG,    NULL,       
+    OPTENT3(0, "linear",             OPT_FLAG,   NULL,       
             &cmdlineP->linear,            0);
+    OPTENT3(0, "mixtransparency",    OPT_FLAG,   NULL,       
+            &cmdlineP->mixtransparency,   0);
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
@@ -407,10 +427,61 @@ computeOverlayPosition(int                const underCols,
 
 
 
+/*----------------------------------------------------------------------------
+   COMPOSING TRANSPARENT PIXELS - AM_OVERLAY
+
+   There are various interpretations of composing transparent pixels, which
+   you can see in the definition of enum alphaMix.
+
+   For AM_OVERLAY, which means the result is as if the underlaying and
+   overlaying images are plastic slides and we taped them together to make a
+   composed plastic slide, the calculations go as follows.
+
+   Opacity and transparency are fractions in [0,1] and are complements:
+
+     X_T = 1 - X_O
+
+   We consider a further composed image, where in the future someone projects
+   another image (the background image) through our composed slide.  The
+   opacity and color of our composed image must be such that the light
+   emerging (the result image) is the same as if it travelled serially through
+   the underlaying and overlaying slide.
+
+   The transparency of each slide is the fraction of light that gets
+   through that slide, so the transparency of the composed slide is the
+   product of the underlay and overlay transparencies.
+
+       C_T = U_T * O_T
+
+   The composed transparency determines the distribution of background and
+   composed image in the result.  We have to choose the composed color such
+   that the result of combining it thus with the background image is the same
+   as the result of combining the background image with the underlay image
+   (distributed according to the underlay transparency), then combining that
+   with the overlay image (distributed according to the overlay transparency).
+
+       C = (O_T * U_O * U + O_O * O) / C_O   if C_O != 0
+
+   But this is meaningless if both components are fully transparent, which
+   means composed image opacity (C_O) is zero.  In that case, the composed
+   color is irrelevant to the result image and we can choose whatever color
+   is most likely to be convenient in applications that don't use the
+   transparency of the composed image.  We choose the pure underlay color.
+
+       C = U   if C_O = 0
+
+   function composeComponents() computes the above formula.
+
+-----------------------------------------------------------------------------*/
+
+
+
 static sample
 composeComponents(sample           const compA, 
                   sample           const compB,
                   float            const distrib,
+                  float            const aFactor,
+                  float            const composedFactor,
                   sample           const maxval,
                   enum sampleScale const sampleScale) {
 /*----------------------------------------------------------------------------
@@ -421,6 +492,13 @@ composeComponents(sample           const compA,
   'sampleScale' tells in what domain the 'distrib' fraction applies:
   brightness or light intensity (gamma-adjusted or not).
 
+  'aFactor' is a factor in [0,1] to apply to 'compA' first.
+
+  'composedFactor' is a factor to apply to the result.
+
+  See above for explanation of why 'aFactor' and 'composedFactor' are
+  useful.
+
   The inputs and result are based on a maxval of 'maxval'.
   
   Note that while 'distrib' in the straightforward case is always in
@@ -429,21 +507,23 @@ composeComponents(sample           const compA,
 -----------------------------------------------------------------------------*/
     sample retval;
 
-    if (fabs(1.0-distrib) < .001)
+    if (fabs(distrib) > .999 && aFactor > .999 && composedFactor > .999)
         /* Fast path for common case */
         retval = compA;
     else {
         if (sampleScale == INTENSITY_SAMPLE) {
             sample const mix = 
-                ROUNDU(compA * distrib + compB * (1.0 - distrib));
+                ROUNDU(compA * aFactor * distrib + compB * (1.0 - distrib));
             retval = MIN(maxval, MAX(0, mix));
         } else {
             float const compANormalized = (float)compA/maxval;
             float const compBNormalized = (float)compB/maxval;
             float const compALinear = pm_ungamma709(compANormalized);
             float const compBLinear = pm_ungamma709(compBNormalized);
+            float const compALinearAdj = compALinear * aFactor;
             float const mix = 
-                compALinear * distrib + compBLinear * (1.0 - distrib);
+                compALinearAdj * distrib + compBLinear * (1.0 - distrib)
+                * composedFactor;
             sample const sampleValue = ROUNDU(pm_gamma709(mix) * maxval);
             retval = MIN(maxval, MAX(0, sampleValue));
         }
@@ -453,6 +533,40 @@ composeComponents(sample           const compA,
 
 
 
+static sample
+composedOpacity(tuple         const uTuple,
+                struct pam *  const uPamP,
+                tuple         const oTuple,
+                struct pam *  const oPamP,
+                struct pam *  const cPamP,
+                enum alphaMix const alphaMix) {
+
+    sample retval;
+
+    assert(uPamP->have_opacity);
+
+    switch (alphaMix) {
+    case AM_OVERLAY: {
+        /* output transparency is product of two input transparencies */
+        float const underlayTrans =
+            1.0 - ((float)uTuple[uPamP->opacity_plane]/uPamP->maxval);
+        float const overlayTrans =
+            1.0 - ((float)oTuple[oPamP->opacity_plane]/oPamP->maxval);
+        float const composedTrans = underlayTrans * overlayTrans;
+        sample const sampleValue =  (1.0 - composedTrans) * cPamP->maxval;
+        retval = MIN(cPamP->maxval, MAX(0, sampleValue));
+        pm_message("underlay = %lu/%f, overlay = %f, composed = %f",
+                   uTuple[uPamP->opacity_plane],underlayTrans, overlayTrans, composedTrans);
+    } break;
+    case AM_KEEPUNDER:
+        retval = uTuple[uPamP->opacity_plane];
+        break;
+    }
+    return retval;
+}
+
+
+
 static void
 overlayPixel(tuple            const overlayTuple,
              struct pam *     const overlayPamP,
@@ -463,7 +577,8 @@ overlayPixel(tuple            const overlayTuple,
              tuple            const composedTuple,
              struct pam *     const composedPamP,
              float            const masterOpacity,
-             enum sampleScale const sampleScale) {
+             enum sampleScale const sampleScale,
+             enum alphaMix    const alphaMix) {
 /*----------------------------------------------------------------------------
    Generate the result of overlaying one pixel with another, taking opacity
    into account, viz overlaying 'underlayTuple' with 'overlayTuple'.
@@ -491,10 +606,16 @@ overlayPixel(tuple            const overlayTuple,
    factors apply to brightness (.5 means half the brightness of the result
    comes from the underlay pixel, half comes from the overlay).
 
+   'alphaMix' tells how to determine the opacity of the result pixel.
+
    Return the result as 'composedTuple', which has the form described by
    'composedPamP'.
 -----------------------------------------------------------------------------*/
     float overlayWeight;
+    float underlayWeight;
+        /* Part of formula for AM_OVERLAY -- see explanation above */
+    float composedWeight;
+        /* Part of formula for AM_OVERLAY -- see explanation above */
 
     overlayWeight = masterOpacity;  /* initial value */
     
@@ -508,22 +629,44 @@ overlayPixel(tuple            const overlayTuple,
         overlayWeight *= alphaval;
     }
 
+    if (underlayPamP->have_opacity && alphaMix == AM_OVERLAY) {
+        struct pam * const uPamP = underlayPamP;
+        struct pam * const oPamP = overlayPamP;
+        sample const uOpacity = underlayTuple[uPamP->opacity_plane];
+        sample const oOpacity = overlayTuple[oPamP->opacity_plane];
+        sample const uMaxval = uPamP->maxval;
+        sample const oMaxval = oPamP->maxval;
+        float  const uOpacityN = uOpacity / uMaxval;
+        float  const oOpacityN = oOpacity / oMaxval;
+        float  const composedTrans = (1.0 - uOpacityN) * (1.0 * oOpacityN);
+        
+        if (composedTrans > .999) {
+            underlayWeight = 1.0;
+            composedWeight = 1.0;
+        } else {
+            underlayWeight = uOpacityN;
+            composedWeight = 1.0 / (1.0 - composedTrans);
+        }
+    } else {
+        underlayWeight = 1.0;
+        composedWeight = 1.0;
+    }
     {
         unsigned int plane;
         
         for (plane = 0; plane < composedPamP->color_depth; ++plane)
             composedTuple[plane] = 
                 composeComponents(overlayTuple[plane], underlayTuple[plane], 
-                                  overlayWeight,
+                                  overlayWeight, underlayWeight,
+                                  composedWeight,
                                   composedPamP->maxval,
                                   sampleScale);
 
-        if (composedPamP->have_opacity) {
-            /* copy opacity from underlay to output */
-            assert(underlayPamP->have_opacity);
+        if (composedPamP->have_opacity)
             composedTuple[composedPamP->opacity_plane] =
-                underlayTuple[underlayPamP->opacity_plane];
-        }
+                composedOpacity(underlayTuple, underlayPamP,
+                                overlayTuple, overlayPamP,
+                                composedPamP, alphaMix);
     }
 }
 
@@ -565,6 +708,7 @@ composeRow(int              const originleft,
            float            const masterOpacity,
            struct pam *     const composedPamP,
            enum sampleScale const sampleScale,
+           enum alphaMix    const alphaMix,
            const tuple *    const underlayTuplerow,
            const tuple *    const overlayTuplerow,
            const tuplen *   const alphaTuplerown,
@@ -589,7 +733,7 @@ composeRow(int              const originleft,
                          underlayTuplerow[col], underlayPamP,
                          alphaTuplen, invertAlpha,
                          composedTuplerow[col], composedPamP,
-                         masterOpacity, sampleScale);
+                         masterOpacity, sampleScale, alphaMix);
         } else
             /* Overlay image does not touch this column. */
             pnm_assigntuple(composedPamP, composedTuplerow[col],
@@ -657,7 +801,8 @@ composite(int          const originleft,
           bool         const invertAlpha,
           float        const masterOpacity,
           struct pam * const composedPamP,
-          bool         const assumeLinear) {
+          bool         const assumeLinear,
+          bool         const mixTransparency) {
 /*----------------------------------------------------------------------------
    Overlay the overlay image in the array 'overlayImage', described by
    *overlayPamP, onto the underlying image from the input image file
@@ -680,6 +825,8 @@ composite(int          const originleft,
 -----------------------------------------------------------------------------*/
     enum sampleScale const sampleScale = 
         assumeLinear ? INTENSITY_SAMPLE : GAMMA_SAMPLE;
+    enum alphaMix const alphaMix =
+        mixTransparency ? AM_OVERLAY : AM_KEEPUNDER;
 
     int underlayRow;  /* NB may be negative */
     int overlayRow;   /* NB may be negative */
@@ -728,7 +875,7 @@ composite(int          const originleft,
             } else {
                 composeRow(originleft, &adaptUnderlayPam, &adaptOverlayPam,
                            invertAlpha, masterOpacity, 
-                           composedPamP, sampleScale,
+                           composedPamP, sampleScale, alphaMix,
                            underlayTuplerow, overlayTuplerow, alphaTuplerown,
                            composedTuplerow);
                 
@@ -834,7 +981,7 @@ main(int argc, const char *argv[]) {
     composite(originLeft, originTop,
               &underlayPam, &overlayPam, alphaFileP ? &alphaPam : NULL,
               cmdline.alphaInvert, cmdline.opacity,
-              &composedPam, cmdline.linear);
+              &composedPam, cmdline.linear, cmdline.mixtransparency);
 
     if (alphaFileP)
         pm_close(alphaFileP);