about summary refs log tree commit diff
path: root/editor/pnmhisteq.c
diff options
context:
space:
mode:
Diffstat (limited to 'editor/pnmhisteq.c')
-rw-r--r--editor/pnmhisteq.c253
1 files changed, 189 insertions, 64 deletions
diff --git a/editor/pnmhisteq.c b/editor/pnmhisteq.c
index 1987efc3..8af42019 100644
--- a/editor/pnmhisteq.c
+++ b/editor/pnmhisteq.c
@@ -24,6 +24,8 @@ struct cmdlineInfo {
     */
     const char * inputFileName;
     unsigned int gray;
+    unsigned int noblack;
+    unsigned int nowhite;
     const char * wmap;
     const char * rmap;
     unsigned int verbose;
@@ -39,7 +41,7 @@ parseCommandLine(int argc, char ** argv,
    was passed to us as the argv array.
 -----------------------------------------------------------------------------*/
     optEntry *option_def;
-        /* Instructions to optParseOptions3 on how to parse our options.
+        /* Instructions to pm_optParseOptions3 on how to parse our options.
          */
     optStruct3 opt;
 
@@ -55,6 +57,10 @@ parseCommandLine(int argc, char ** argv,
             &wmapSpec,          0);
     OPTENT3(0, "gray",     OPT_FLAG,   NULL,
             &cmdlineP->gray,    0);
+    OPTENT3(0, "noblack",     OPT_FLAG,   NULL,
+            &cmdlineP->noblack,    0);
+    OPTENT3(0, "nowhite",     OPT_FLAG,   NULL,
+            &cmdlineP->nowhite,    0);
     OPTENT3(0, "verbose",  OPT_FLAG,   NULL,
             &cmdlineP->verbose, 0);
 
@@ -62,7 +68,7 @@ parseCommandLine(int argc, char ** argv,
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
     opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
 
-    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+    pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
 
@@ -91,8 +97,6 @@ computeLuminosityHistogram(xel * const *   const xels,
                            int             const format,
                            bool            const monoOnly,
                            unsigned int ** const lumahistP,
-                           xelval *        const lminP,
-                           xelval *        const lmaxP,
                            unsigned int *  const pixelCountP) {
 /*----------------------------------------------------------------------------
   Scan the image and build the luminosity histogram.  If the input is
@@ -144,7 +148,7 @@ computeLuminosityHistogram(xel * const *   const xels,
             for (col = 0; col < cols; ++col) {
                 xel const thisXel = xels[row][col];
                 if (!monoOnly || PPM_ISGRAY(thisXel)) {
-                    xelval const l = PPM_LUMIN(thisXel);
+                    xelval const l = ppm_luminosity(thisXel);
 
                     lmin = MIN(lmin, l);
                     lmax = MAX(lmax, l);
@@ -162,25 +166,6 @@ computeLuminosityHistogram(xel * const *   const xels,
 
     *lumahistP = lumahist;
     *pixelCountP = pixelCount;
-    *lminP = lmin;
-    *lmaxP = lmax;
-}
-
-
-
-static void
-findMaxLuma(const xelval * const lumahist,
-            xelval         const maxval,
-            xelval *       const maxLumaP) {
-
-    xelval maxluma;
-    unsigned int i;
-
-    for (i = 0, maxluma = 0; i <= maxval; ++i)
-        if (lumahist[i] > 0)
-            maxluma = i;
-
-    *maxLumaP = maxluma;
 }
 
 
@@ -216,53 +201,159 @@ readMapFile(const char * const rmapFileName,
 
 
 
+static xelval
+maxLumaPresent(const xelval * const lumahist,
+               xelval         const darkestCandidate,
+               xelval         const brightestCandidate) {
+/*----------------------------------------------------------------------------
+    The maximum luminosity in the image, in the range ['darkestCandidate',
+   'brightestCandidate'], given that the luminosity histogram for the image is
+   'lumahist' (lumahist[N] is the number of pixels in the image with
+   luminosity N).
+-----------------------------------------------------------------------------*/
+    xelval maxluma;
+    xelval i;
+
+    for (i = darkestCandidate, maxluma = darkestCandidate;
+         i <= brightestCandidate;
+         ++i) {
+
+        if (lumahist[i] > 0)
+            maxluma = i;
+    }
+    return maxluma;
+}
+
+
+
 static void
-computeMap(const unsigned int * const lumahist,
-           xelval               const maxval,
-           unsigned int         const pixelCount,
-           gray *               const lumamap) {
+equalize(const unsigned int * const lumahist,
+         xelval               const darkestRemap,
+         xelval               const brightestRemap,
+         unsigned int         const remapPixelCount,
+         gray *               const lumamap) {
+/*----------------------------------------------------------------------------
+   Fill in the mappings of luminosities from 'darkestRemap' through
+   'brightestRemap' in 'lumamap', to achieve an equalization based on the
+   histogram 'lumahist'.  lumahist[N] is the number of pixels in the original
+   image of luminosity N.
+
+   'remapPixelCount' is the number of pixels in the given luminosity range.
+   It is redundant with 'lumahist'; we get it for computational convenience.
+-----------------------------------------------------------------------------*/
+    xelval const maxluma =
+        maxLumaPresent(lumahist, darkestRemap, brightestRemap);
 
-    /* Calculate initial histogram equalization curve. */
+    unsigned int const range = brightestRemap - darkestRemap;
     
-    unsigned int i;
-    unsigned int pixsum;
-    xelval maxluma;
+    {
+        xelval origLum;
+        unsigned int pixsum;
 
-    for (i = 0, pixsum = 0; i <= maxval; ++i) {
+        for (origLum = darkestRemap, pixsum = 0;
+             origLum <= brightestRemap;
+             ++origLum) {
             
-        /* With 16 bit grays, the following calculation can
-           overflow a 32 bit long.  So, we do it in floating
-           point.
-        */
+            /* With 16 bit grays, the following calculation can overflow a 32
+               bit long.  So, we do it in floating point.
+            */
 
-        lumamap[i] = ROUNDU((((double) pixsum * maxval)) / pixelCount);
+            lumamap[origLum] =
+                darkestRemap +
+                ROUNDU((((double) pixsum * range)) / remapPixelCount);
+            
+            pixsum += lumahist[origLum];
+        }
 
-        pixsum += lumahist[i];
     }
-
-    findMaxLuma(lumahist, maxval, &maxluma);
-
     {
-        double const lscale = (double)maxval /
-            ((lumahist[maxluma] > 0) ?
-             (double) lumamap[maxluma] : (double) maxval);
+        double const lscale = (double)range /
+            ((lumamap[maxluma] > darkestRemap) ?
+             (double) lumamap[maxluma] - darkestRemap : (double) range);
 
-        unsigned int i;
+        xelval origLum;
 
         /* Normalize so that the brightest pixels are set to maxval. */
 
-        for (i = 0; i <= maxval; ++i)
-            lumamap[i] = MIN(maxval, ROUNDU(lumamap[i] * lscale));
+        for (origLum = darkestRemap; origLum <= brightestRemap; ++origLum)
+            lumamap[origLum] =
+                MIN(brightestRemap, 
+                    darkestRemap + ROUNDU(lumamap[origLum] * lscale));
     }
 }
 
 
 
 static void
+computeMap(const unsigned int * const lumahist,
+           xelval               const maxval,
+           unsigned int         const pixelCount,
+           bool                 const noblack,
+           bool                 const nowhite,
+           gray *               const lumamap) {
+/*----------------------------------------------------------------------------
+  Calculate initial histogram equalization curve.
+
+  'lumahist' is the luminosity histogram for the image; lumahist[N] is
+  the number of pixels that have luminosity N.
+
+  'maxval' is the maxval of the image (ergo the maximum luminosity).
+
+  'pixelCount' is the number of pixels in the image, which is redundant
+  with 'lumahist' but provided for computational convenience.
+   
+  'noblack' means don't include the black pixels in the equalization and
+  make the black pixels in the output the same ones as in the input.
+
+  'nowhite' is equivalent for the white pixels.
+
+  We return the map as *lumamap, where lumamap[N] is the luminosity in the
+  output of a pixel with luminosity N in the input.  Its storage size must
+  be at least 'maxval' + 1.
+-----------------------------------------------------------------------------*/
+    xelval darkestRemap, brightestRemap;
+        /* The lowest and highest luminosity values that we will remap
+           according to the equalization strategy.  They're just 0 and maxval
+           unless modified by 'noblack' and 'nowhite'.
+        */
+    unsigned int remapPixelCount;
+        /* The number of pixels we map according to the equalization
+           strategy; it doesn't include black pixels and white pixels that
+           are excluded from the equalization because of 'noblack' and
+           'nowhite'
+        */
+
+    remapPixelCount = pixelCount;  /* Initial assumption */
+
+    if (noblack) {
+        lumamap[0] = 0;
+        darkestRemap = 1;
+        remapPixelCount -= lumahist[0];
+    } else {
+        darkestRemap = 0;
+    }
+
+    if (nowhite) {
+        lumamap[maxval] = maxval;
+        brightestRemap = maxval - 1;
+        remapPixelCount -= lumahist[maxval];
+    } else {
+        brightestRemap = maxval;
+    }
+
+    equalize(lumahist, darkestRemap, brightestRemap, remapPixelCount,
+             lumamap);
+}
+
+
+
+static void
 getMapping(const char *         const rmapFileName,
            const unsigned int * const lumahist,
            xelval               const maxval,
            unsigned int         const pixelCount,
+           bool                 const noblack,
+           bool                 const nowhite,
            gray **              const lumamapP) {
 /*----------------------------------------------------------------------------
   Calculate the luminosity mapping table which gives the
@@ -275,7 +366,7 @@ getMapping(const char *         const rmapFileName,
     if (rmapFileName)
         readMapFile(rmapFileName, maxval, lumamap);
     else
-        computeMap(lumahist, maxval, pixelCount, lumamap);
+        computeMap(lumahist, maxval, pixelCount, noblack, nowhite, lumamap);
 
     *lumamapP = lumamap;
 }
@@ -302,6 +393,48 @@ reportMap(const unsigned int * const lumahist,
 
 
 
+static xel
+scaleXel(xel    const thisXel,
+         double const scaler) {
+/*----------------------------------------------------------------------------
+   Scale the components of 'xel' by multiplying by 'scaler'.
+
+   Assume this doesn't cause it to exceed maxval.
+-----------------------------------------------------------------------------*/
+    xel retval;
+
+    PNM_ASSIGN(retval,
+               ((xelval)(PNM_GETR(thisXel) * scaler + 0.5)),
+               ((xelval)(PNM_GETG(thisXel) * scaler + 0.5)),
+               ((xelval)(PNM_GETB(thisXel) * scaler + 0.5)));
+    
+    return retval;
+}
+
+
+
+static xel
+remapRgbValue(xel          const thisXel,
+              xelval       const maxval,
+              const gray * const lumamap) {
+/*----------------------------------------------------------------------------
+  Return the color 'thisXel' with its HSV value changed per 'lumamap' but
+  the same hue and saturation.
+
+  'maxval' is the maxval for 'xel' and our return value.
+-----------------------------------------------------------------------------*/
+    struct hsv const hsv =
+        ppm_hsv_from_color(thisXel, maxval);
+    xelval const oldValue =
+        MIN(maxval, ROUNDU(hsv.v * maxval));
+    xelval const newValue =
+        lumamap[oldValue];
+    
+    return scaleXel(thisXel, (double)newValue/oldValue);
+}
+
+
+
 static void
 remap(xel **       const xels,
       unsigned int const cols,
@@ -323,16 +456,8 @@ remap(xel **       const xels,
                 if (monoOnly && PPM_ISGRAY(thisXel)) {
                     /* Leave this pixel alone */
                 } else {
-                    struct hsv hsv;
-                    xelval iv;
-
-                    hsv = ppm_hsv_from_color(thisXel, maxval);
-                    iv = MIN(maxval, ROUNDU(hsv.v * maxval));
-                    
-                    hsv.v = MIN(1.0, 
-                                ((double) lumamap[iv]) / ((double) maxval));
-
-                    xels[row][col] = ppm_color_from_hsv(hsv, maxval);
+                    xels[row][col] = remapRgbValue(xels[row][col],
+                                                   maxval, lumamap);
                 }
             }
         }
@@ -376,7 +501,6 @@ main(int argc, char * argv[]) {
 
     struct cmdlineInfo cmdline;
     FILE * ifP;
-    xelval lmin, lmax;
     gray * lumamap;           /* Luminosity map */
     unsigned int * lumahist;  /* Histogram of luminosity values */
     int rows, cols;           /* Rows, columns of input image */
@@ -395,11 +519,12 @@ main(int argc, char * argv[]) {
 
     pm_close(ifP);
 
-    computeLuminosityHistogram(xels, rows, cols, maxval, format,
-                               cmdline.gray, &lumahist, &lmin, &lmax,
-                               &pixelCount);
+    computeLuminosityHistogram(xels, rows, cols, maxval, format, cmdline.gray,
+                               &lumahist, &pixelCount);
 
-    getMapping(cmdline.rmap, lumahist, maxval, pixelCount, &lumamap);
+    getMapping(cmdline.rmap, lumahist, maxval, pixelCount,
+               cmdline.noblack > 0, cmdline.nowhite > 0,
+               &lumamap);
 
     if (cmdline.verbose)
         reportMap(lumahist, maxval, lumamap);