text: /*====================================================================*
   -  Copyright (C) 2001 Leptonica.  All rights reserved.
   -
   -  Redistribution and use in source and binary forms, with or without
   -  modification, are permitted provided that the following conditions
   -  are met:
   -  1. Redistributions of source code must retain the above copyright
   -     notice, this list of conditions and the following disclaimer.
   -  2. Redistributions in binary form must reproduce the above
   -     copyright notice, this list of conditions and the following
   -     disclaimer in the documentation and/or other materials
   -     provided with the distribution.
   -
   -  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   -  ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   -  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   -  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL ANY
   -  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
   -  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
   -  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
   -  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
   -  OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   -  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   -  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   *====================================================================*/
  
  /*!
   * \file coloring.c
   * <pre>
   *
   *      Coloring "gray" pixels
   *           PIX             *pixColorGrayRegions()
   *           l_int32          pixColorGray()
   *           PIX             *pixColorGrayMasked()
   *
   *      Adjusting one or more colors to a target color
   *           PIX             *pixSnapColor()
   *           PIX             *pixSnapColorCmap()
   *
   *      Piecewise linear color mapping based on a source/target pair
   *           PIX             *pixLinearMapToTargetColor()
   *           l_int32          pixelLinearMapToTargetColor()
   *
   *      Fractional shift of RGB towards black or white
   *           PIX             *pixShiftByComponent()
   *           l_int32          pixelShiftByComponent()
   *           l_int32          pixelFractionalShift()
   *
   *  There are several "coloring" functions in leptonica.
   *  You can find them in these files:
   *       coloring.c
   *       paintcmap.c
   *       pix2.c
   *       blend.c
   *       enhance.c
   *
   *  They fall into the following categories:
   *
   *  (1) Moving either the light or dark pixels toward a
   *      specified color. (pixColorGray, pixColorGrayMasked)
   *  (2) Forcing all pixels whose color is within some delta of a
   *      specified color to move to that color. (pixSnapColor)
   *  (3) Doing a piecewise linear color shift specified by a source
   *      and a target color.  Each component shifts independently.
   *      (pixLinearMapToTargetColor)
   *  (4) Shifting all colors by a given fraction of their distance
   *      from 0 (if shifting down) or from 255 (if shifting up).
   *      This is useful for colorizing either the background or
   *      the foreground of a grayscale image. (pixShiftByComponent)
   *  (5) Shifting all colors by a component-dependent fraction of
   *      their distance from 0 (if shifting down) or from 255 (if
   *      shifting up).  This is useful for modifying the color to
   *      compensate for color shifts in acquisition or printing.
   *      (enhance.c: pixColorShiftRGB, pixMosaicColorShiftRGB).
   *  (6) Repainting selected pixels. (paintcmap.c: pixSetSelectMaskedCmap)
   *  (7) Blending a fraction of a specific color with the existing RGB
   *      color.  (pix2.c: pixBlendInRect())
   *  (8) Changing selected colors in a colormap.
   *      (paintcmap.c: pixSetSelectCmap, pixSetSelectMaskedCmap)
   *  (9) Shifting all the pixels towards black or white depending on
   *      the gray value of a second image.  (blend.c: pixFadeWithGray)
   *  (10) Changing the hue, saturation or brightness, by changing the
   *      appropriate parameter in HSV color space by a fraction of
   *      the distance toward its end-point.  For example, you can change
   *      the brightness by moving each pixel's v-parameter a specified
   *      fraction of the distance toward 0 (darkening) or toward 255
   *      (brightening).  (enhance.c: pixModifySaturation,
   *      pixModifyHue, pixModifyBrightness)
   * </pre>
   */
  
  #include "allheaders.h"
  
  
  /*---------------------------------------------------------------------*
   *                        Coloring "gray" pixels                       *
   *---------------------------------------------------------------------*/
  /*!
   * \brief   pixColorGrayRegions()
   *
   * \param[in]    pixs       2, 4 or 8 bpp gray, rgb, or colormapped
   * \param[in]    boxa       of regions in which to apply color
   * \param[in]    type       L_PAINT_LIGHT, L_PAINT_DARK
   * \param[in]    thresh     average value below/above which pixel is unchanged
   * \param[in]    rval, gval, bval new color to paint
   * \return  pixd, or NULL on error
   *
   * <pre>
   * Notes:
   *      (1) This generates a new image, where some of the pixels in each
   *          box in the boxa are colorized.  See pixColorGray() for usage
   *          with %type and %thresh.  Note that %thresh is only used for
   *          rgb; it is ignored for colormapped images.
   *      (2) If the input image is colormapped, the new image will be 8 bpp
   *          colormapped if possible; otherwise, it will be converted
   *          to 32 bpp rgb.  Only pixels that are strictly gray will be
   *          colorized.
   *      (3) If the input image is not colormapped, it is converted to rgb.
   *          A "gray" value for a pixel is determined by averaging the
   *          components, and the output rgb value is determined from this.
   *      (4) This can be used in conjunction with pixHasHighlightRed() to
   *          add highlight color to a grayscale image.
   * </pre>
   */
  PIX *
  pixColorGrayRegions(PIX     *pixs,
                      BOXA    *boxa,
                      l_int32  type,
                      l_int32  thresh,
                      l_int32  rval,
                      l_int32  gval,
                      l_int32  bval)
  {
  l_int32   i, n, ncolors, ngray;
  BOX      *box;
  PIX      *pixd;
  PIXCMAP  *cmap;
  
      PROCNAME("pixColorGrayRegions");
  
      if (!pixs || pixGetDepth(pixs) == 1)
          return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
      if (!boxa)
          return (PIX *)ERROR_PTR("boxa not defined", procName, NULL);
      if (type != L_PAINT_LIGHT && type != L_PAINT_DARK)
          return (PIX *)ERROR_PTR("invalid type", procName, NULL);
  
          /* If cmapped and there is room in an 8 bpp colormap for
           * expansion, convert pixs to 8 bpp, and colorize. */
      cmap = pixGetColormap(pixs);
      if (cmap) {
          ncolors = pixcmapGetCount(cmap);
          pixcmapCountGrayColors(cmap, &ngray);
          if (ncolors + ngray < 255) {
              pixd = pixConvertTo8(pixs, 1);  /* always new image */
              pixColorGrayRegionsCmap(pixd, boxa, type, rval, gval, bval);
              return pixd;
          }
      }
  
          /* The output will be rgb.  Make sure the thresholds are valid */
      if (type == L_PAINT_LIGHT) {  /* thresh should be low */
          if (thresh >= 255)
              return (PIX *)ERROR_PTR("thresh must be < 255", procName, NULL);
          if (thresh > 127)
              L_WARNING("threshold set very high\n", procName);
      } else {  /* type == L_PAINT_DARK; thresh should be high */
          if (thresh <= 0)
              return (PIX *)ERROR_PTR("thresh must be > 0", procName, NULL);
          if (thresh < 128)
              L_WARNING("threshold set very low\n", procName);
      }
  
      pixd = pixConvertTo32(pixs);  /* always new image */
      n = boxaGetCount(boxa);
      for (i = 0; i < n; i++) {
          box = boxaGetBox(boxa, i, L_CLONE);
          pixColorGray(pixd, box, type, thresh, rval, gval, bval);
          boxDestroy(&box);
      }
  
      return pixd;
  }
  
  
  /*!
   * \brief   pixColorGray()
   *
   * \param[in]    pixs     8 bpp gray, rgb or colormapped image
   * \param[in]    box      [optional] region in which to apply color; can be NULL
   * \param[in]    type     L_PAINT_LIGHT, L_PAINT_DARK
   * \param[in]    thresh   average value below/above which pixel is unchanged
   * \param[in]    rval, gval, bval    new color to paint
   * \return  0 if OK; 1 on error
   *
   * <pre>
   * Notes:
   *      (1) This is an in-place operation; pixs is modified.
   *          If pixs is colormapped, the operation will add colors to the
   *          colormap.  Otherwise, pixs will be converted to 32 bpp rgb if
   *          it is initially 8 bpp gray.
   *      (2) If type == L_PAINT_LIGHT, it colorizes non-black pixels,
   *          preserving antialiasing.
   *          If type == L_PAINT_DARK, it colorizes non-white pixels,
   *          preserving antialiasing.
   *      (3) If box is NULL, applies function to the entire image; otherwise,
   *          clips the operation to the intersection of the box and pix.
   *      (4) If colormapped, calls pixColorGrayCmap(), which applies the
   *          coloring algorithm only to pixels that are strictly gray.
   *      (5) For RGB, determines a "gray" value by averaging; then uses this
   *          value, plus the input rgb target, to generate the output
   *          pixel values.
   *      (6) thresh is only used for rgb; it is ignored for colormapped pix.
   *          If type == L_PAINT_LIGHT, use thresh = 0 if all pixels are to
   *          be colored (black pixels will be unaltered).
   *          In situations where there are a lot of black pixels,
   *          setting thresh > 0 will make the function considerably
   *          more efficient without affecting the final result.
   *          If type == L_PAINT_DARK, use thresh = 255 if all pixels
   *          are to be colored (white pixels will be unaltered).
   *          In situations where there are a lot of white pixels,
   *          setting thresh < 255 will make the function considerably
   *          more efficient without affecting the final result.
   * </pre>
   */
  l_ok
  pixColorGray(PIX     *pixs,
               BOX     *box,
               l_int32  type,
               l_int32  thresh,
               l_int32  rval,
               l_int32  gval,
               l_int32  bval)
  {
  l_int32    i, j, w, h, d, wpl, x1, x2, y1, y2, bw, bh;
  l_int32    nrval, ngval, nbval, aveval;
  l_float32  factor;
  l_uint32   val32;
  l_uint32  *line, *data;
  PIX       *pixt;
  PIXCMAP   *cmap;
  
      PROCNAME("pixColorGray");
  
      if (!pixs)
          return ERROR_INT("pixs not defined", procName, 1);
      if (type != L_PAINT_LIGHT && type != L_PAINT_DARK)
          return ERROR_INT("invalid type", procName, 1);
  
      cmap = pixGetColormap(pixs);
      pixGetDimensions(pixs, &w, &h, &d);
      if (!cmap && d != 8 && d != 32)
          return ERROR_INT("pixs not cmapped, 8 bpp or rgb", procName, 1);
      if (cmap)
          return pixColorGrayCmap(pixs, box, type, rval, gval, bval);
  
          /* rgb or 8 bpp gray image; check the thresh */
      if (type == L_PAINT_LIGHT) {  /* thresh should be low */
          if (thresh >= 255)
              return ERROR_INT("thresh must be < 255; else this is a no-op",
                               procName, 1);
          if (thresh > 127)
              L_WARNING("threshold set very high\n", procName);
      } else {  /* type == L_PAINT_DARK; thresh should be high */
          if (thresh <= 0)
              return ERROR_INT("thresh must be > 0; else this is a no-op",
                               procName, 1);
          if (thresh < 128)
              L_WARNING("threshold set very low\n", procName);
      }
  
          /* In-place conversion to 32 bpp if necessary */
      if (d == 8) {
          pixt = pixConvertTo32(pixs);
          pixTransferAllData(pixs, &pixt, 1, 0);
      }
  
      if (!box) {
          x1 = y1 = 0;
          x2 = w;
          y2 = h;
      } else {
          boxGetGeometry(box, &x1, &y1, &bw, &bh);
          x2 = x1 + bw - 1;
          y2 = y1 + bh - 1;
      }
  
      data = pixGetData(pixs);
      wpl = pixGetWpl(pixs);
      factor = 1. / 255.;
      for (i = y1; i <= y2; i++) {
          if (i < 0 || i >= h)
              continue;
          line = data + i * wpl;
          for (j = x1; j <= x2; j++) {
              if (j < 0 || j >= w)
                  continue;
              val32 = *(line + j);
              aveval = ((val32 >> 24) + ((val32 >> 16) & 0xff) +
                        ((val32 >> 8) & 0xff)) / 3;
              if (type == L_PAINT_LIGHT) {
                  if (aveval < thresh)  /* skip sufficiently dark pixels */
                      continue;
                  nrval = (l_int32)(rval * aveval * factor);
                  ngval = (l_int32)(gval * aveval * factor);
                  nbval = (l_int32)(bval * aveval * factor);
              } else {  /* type == L_PAINT_DARK */
                  if (aveval > thresh)  /* skip sufficiently light pixels */
                      continue;
                  nrval = rval + (l_int32)((255. - rval) * aveval * factor);
                  ngval = gval + (l_int32)((255. - gval) * aveval * factor);
                  nbval = bval + (l_int32)((255. - bval) * aveval * factor);
              }
              composeRGBPixel(nrval, ngval, nbval, &val32);
              *(line + j) = val32;
          }
      }
  
      return 0;
  }
  
  
  /*!
   * \brief   pixColorGrayMasked()
   *
   * \param[in]    pixs      8 bpp gray, rgb or colormapped image
   * \param[in]    pixm      1 bpp mask, through which to apply color
   * \param[in]    type      L_PAINT_LIGHT, L_PAINT_DARK
   * \param[in]    thresh    average value below/above which pixel is unchanged
   * \param[in]    rval, gval, bval    new color to paint
   * \return  pixd colorized, or NULL on error
   *
   * <pre>
   * Notes:
   *      (1) This generates a new image, where some of the pixels under
   *          FG in the mask are colorized.
   *      (2) See pixColorGray() for usage with %type and %thresh.  Note
   *          that %thresh is only used for rgb; it is ignored for
   *          colormapped images.  In most cases, the mask will be over
   *          the darker parts and %type == L_PAINT_DARK.
   *      (3) If pixs is colormapped this calls pixColorMaskedCmap(),
   *          which adds colors to the colormap for pixd; it only adds
   *          colors corresponding to strictly gray colors in the colormap.
   *          Otherwise, if pixs is 8 bpp gray, pixd will be 32 bpp rgb.
   *      (4) If pixs is 32 bpp rgb, for each pixel a "gray" value is
   *          found by averaging.  This average is then used with the
   *          input rgb target to generate the output pixel values.
   *      (5) This can be used in conjunction with pixHasHighlightRed() to
   *          add highlight color to a grayscale image.
   * </pre>
   */
  PIX *
  pixColorGrayMasked(PIX     *pixs,
                     PIX     *pixm,
                     l_int32  type,
                     l_int32  thresh,
                     l_int32  rval,
                     l_int32  gval,
                     l_int32  bval)
  {
  l_int32    i, j, w, h, d, wm, hm, wmin, hmin, wpl, wplm;
  l_int32    nrval, ngval, nbval, aveval;
  l_float32  factor;
  l_uint32   val32;
  l_uint32  *line, *data, *linem, *datam;
  PIX       *pixd;
  PIXCMAP   *cmap;
  
      PROCNAME("pixColorGrayMasked");
  
      if (!pixs)
          return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
      if (!pixm || pixGetDepth(pixm) != 1)
          return (PIX *)ERROR_PTR("pixm undefined or not 1 bpp", procName, NULL);
      if (type != L_PAINT_LIGHT && type != L_PAINT_DARK)
          return (PIX *)ERROR_PTR("invalid type", procName, NULL);
  
      cmap = pixGetColormap(pixs);
      pixGetDimensions(pixs, &w, &h, &d);
      if (!cmap && d != 8 && d != 32)
          return (PIX *)ERROR_PTR("pixs not cmapped, 8 bpp gray or 32 bpp",
                                  procName, NULL);
      if (cmap) {
          pixd = pixCopy(NULL, pixs);
          pixColorGrayMaskedCmap(pixd, pixm, type, rval, gval, bval);
          return pixd;
      }
  
          /* rgb or 8 bpp gray image; check the thresh */
      if (type == L_PAINT_LIGHT) {  /* thresh should be low */
          if (thresh >= 255)
              return (PIX *)ERROR_PTR(
                  "thresh must be < 255; else this is a no-op", procName, NULL);
          if (thresh > 127)
              L_WARNING("threshold set very high\n", procName);
      } else {  /* type == L_PAINT_DARK; thresh should be high */
          if (thresh <= 0)
              return (PIX *)ERROR_PTR(
                  "thresh must be > 0; else this is a no-op", procName, NULL);
          if (thresh < 128)
              L_WARNING("threshold set very low\n", procName);
      }
  
      pixGetDimensions(pixm, &wm, &hm, NULL);
      if (wm != w)
          L_WARNING("wm = %d differs from w = %d\n", procName, wm, w);
      if (hm != h)
          L_WARNING("hm = %d differs from h = %d\n", procName, hm, h);
      wmin = L_MIN(w, wm);
      hmin = L_MIN(h, hm);
      if (d == 8)
          pixd = pixConvertTo32(pixs);
      else
          pixd = pixCopy(NULL, pixs);
  
      data = pixGetData(pixd);
      wpl = pixGetWpl(pixd);
      datam = pixGetData(pixm);
      wplm = pixGetWpl(pixm);
      factor = 1. / 255.;
      for (i = 0; i < hmin; i++) {
          line = data + i * wpl;
          linem = datam + i * wplm;
          for (j = 0; j < wmin; j++) {
              if (GET_DATA_BIT(linem, j) == 0)
                  continue;
              val32 = *(line + j);
              aveval = ((val32 >> 24) + ((val32 >> 16) & 0xff) +
                        ((val32 >> 8) & 0xff)) / 3;
              if (type == L_PAINT_LIGHT) {
                  if (aveval < thresh)  /* skip sufficiently dark pixels */
                      continue;
                  nrval = (l_int32)(rval * aveval * factor);
                  ngval = (l_int32)(gval * aveval * factor);
                  nbval = (l_int32)(bval * aveval * factor);
              } else {  /* type == L_PAINT_DARK */
                  if (aveval > thresh)  /* skip sufficiently light pixels */
                      continue;
                  nrval = rval + (l_int32)((255. - rval) * aveval * factor);
                  ngval = gval + (l_int32)((255. - gval) * aveval * factor);
                  nbval = bval + (l_int32)((255. - bval) * aveval * factor);
              }
              composeRGBPixel(nrval, ngval, nbval, &val32);
              *(line + j) = val32;
          }
      }
  
      return pixd;
  }
  
  
  /*------------------------------------------------------------------*
   *            Adjusting one or more colors to a target color        *
   *------------------------------------------------------------------*/
  /*!
   * \brief   pixSnapColor()
   *
   * \param[in]    pixd     [optional]; either NULL or equal to pixs for in-place
   * \param[in]    pixs     colormapped or 8 bpp gray or 32 bpp rgb
   * \param[in]    srcval   color center to be selected for change: 0xrrggbb00
   * \param[in]    dstval   target color for pixels: 0xrrggbb00
   * \param[in]    diff     max absolute difference, applied to all components
   * \return  pixd   with all pixels within diff of pixval set to pixval,
   *                 or pixd on error
   *
   * <pre>
   * Notes:
   *      (1) For inplace operation, call it this way:
   *           pixSnapColor(pixs, pixs, ... )
   *      (2) For generating a new pixd:
   *           pixd = pixSnapColor(NULL, pixs, ...)
   *      (3) If pixs has a colormap, it is handled by pixSnapColorCmap().
   *      (4) All pixels within 'diff' of 'srcval', componentwise,
   *          will be changed to 'dstval'.
   * </pre>
   */
  PIX *
  pixSnapColor(PIX      *pixd,
               PIX      *pixs,
               l_uint32  srcval,
               l_uint32  dstval,
               l_int32   diff)
  {
  l_int32    val, sval, dval;
  l_int32    rval, gval, bval, rsval, gsval, bsval;
  l_int32    i, j, w, h, d, wpl;
  l_uint32   pixel;
  l_uint32  *line, *data;
  
      PROCNAME("pixSnapColor");
  
      if (!pixs)
          return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
      if (pixd && (pixd != pixs))
          return (PIX *)ERROR_PTR("pixd not null or == pixs", procName, pixd);
  
      if (pixGetColormap(pixs))
          return pixSnapColorCmap(pixd, pixs, srcval, dstval, diff);
  
          /* pixs does not have a colormap; it must be 8 bpp gray or
           * 32 bpp rgb. */
      if (pixGetDepth(pixs) < 8)
          return (PIX *)ERROR_PTR("pixs is < 8 bpp", procName, pixd);
  
          /* Do the work on pixd */
      if (!pixd)
          pixd = pixCopy(NULL, pixs);
  
      pixGetDimensions(pixd, &w, &h, &d);
      data = pixGetData(pixd);
      wpl = pixGetWpl(pixd);
      if (d == 8) {
          sval = srcval & 0xff;
          dval = dstval & 0xff;
          for (i = 0; i < h; i++) {
              line = data + i * wpl;
              for (j = 0; j < w; j++) {
                  val = GET_DATA_BYTE(line, j);
                  if (L_ABS(val - sval) <= diff)
                      SET_DATA_BYTE(line, j, dval);
              }
          }
      } else {  /* d == 32 */
          extractRGBValues(srcval, &rsval, &gsval, &bsval);
          for (i = 0; i < h; i++) {
              line = data + i * wpl;
              for (j = 0; j < w; j++) {
                  pixel = *(line + j);
                  extractRGBValues(pixel, &rval, &gval, &bval);
                  if ((L_ABS(rval - rsval) <= diff) &&
                      (L_ABS(gval - gsval) <= diff) &&
                      (L_ABS(bval - bsval) <= diff))
                      *(line + j) = dstval;  /* replace */
              }
          }
      }
  
      return pixd;
  }
  
  
  /*!
   * \brief   pixSnapColorCmap()
   *
   * \param[in]    pixd     [optional]; either NULL or equal to pixs for in-place
   * \param[in]    pixs     colormapped
   * \param[in]    srcval   color center to be selected for change: 0xrrggbb00
   * \param[in]    dstval   target color for pixels: 0xrrggbb00
   * \param[in]    diff     max absolute difference, applied to all components
   * \return  pixd   with all pixels within diff of srcval set to dstval,
   *                 or pixd on error
   *
   * <pre>
   * Notes:
   *      (1) For inplace operation, call it this way:
   *           pixSnapCcmap(pixs, pixs, ... )
   *      (2) For generating a new pixd:
   *           pixd = pixSnapCmap(NULL, pixs, ...)
   *      (3) pixs must have a colormap.
   *      (4) All colors within 'diff' of 'srcval', componentwise,
   *          will be changed to 'dstval'.
   * </pre>
   */
  PIX *
  pixSnapColorCmap(PIX      *pixd,
                   PIX      *pixs,
                   l_uint32  srcval,
                   l_uint32  dstval,
                   l_int32   diff)
  {
  l_int32    i, ncolors, index, found;
  l_int32    rval, gval, bval, rsval, gsval, bsval, rdval, gdval, bdval;
  l_int32   *tab;
  PIX       *pixm;
  PIXCMAP   *cmap;
  
      PROCNAME("pixSnapColorCmap");
  
      if (!pixs)
          return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
      if (!pixGetColormap(pixs))
          return (PIX *)ERROR_PTR("cmap not found", procName, pixd);
      if (pixd && (pixd != pixs))
          return (PIX *)ERROR_PTR("pixd not null or == pixs", procName, pixd);
  
      if (!pixd)
          pixd = pixCopy(NULL, pixs);
  
          /* If no free colors, look for one close to the target
           * that can be commandeered. */
      cmap = pixGetColormap(pixd);
      ncolors = pixcmapGetCount(cmap);
      extractRGBValues(srcval, &rsval, &gsval, &bsval);
      extractRGBValues(dstval, &rdval, &gdval, &bdval);
      found = FALSE;
      if (pixcmapGetFreeCount(cmap) == 0) {
          for (i = 0; i < ncolors; i++) {
              pixcmapGetColor(cmap, i, &rval, &gval, &bval);
              if ((L_ABS(rval - rsval) <= diff) &&
                  (L_ABS(gval - gsval) <= diff) &&
                  (L_ABS(bval - bsval) <= diff)) {
                  index = i;
                  pixcmapResetColor(cmap, index, rdval, gdval, bdval);
                  found = TRUE;
                  break;
              }
          }
      } else {  /* just add the new color */
          pixcmapAddColor(cmap, rdval, gdval, bdval);
          ncolors = pixcmapGetCount(cmap);
          index = ncolors - 1;  /* index of new destination color */
          found = TRUE;
      }
  
      if (!found) {
          L_INFO("nothing to do\n", procName);
          return pixd;
      }
  
          /* For each color in cmap that is close enough to srcval,
           * set the tab value to 1.  Then generate a 1 bpp mask with
           * fg pixels for every pixel in pixd that is close enough
           * to srcval (i.e., has value 1 in tab). */
      if ((tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32))) == NULL)
          return (PIX *)ERROR_PTR("tab not made", procName, pixd);
      for (i = 0; i < ncolors; i++) {
          pixcmapGetColor(cmap, i, &rval, &gval, &bval);
          if ((L_ABS(rval - rsval) <= diff) &&
              (L_ABS(gval - gsval) <= diff) &&
              (L_ABS(bval - bsval) <= diff))
              tab[i] = 1;
      }
      pixm = pixMakeMaskFromLUT(pixd, tab);
      LEPT_FREE(tab);
  
          /* Use the binary mask to set all selected pixels to
           * the dest color index. */
      pixSetMasked(pixd, pixm, dstval);
      pixDestroy(&pixm);
  
          /* Remove all unused colors from the colormap. */
      pixRemoveUnusedColors(pixd);
  
      return pixd;
  }
  
  
  /*---------------------------------------------------------------------*
   *     Piecewise linear color mapping based on a source/target pair    *
   *---------------------------------------------------------------------*/
  /*!
   * \brief   pixLinearMapToTargetColor()
   *
   * \param[in]    pixd     [optional]; either NULL or equal to pixs for in-place
   * \param[in]    pixs     32 bpp rgb
   * \param[in]    srcval   source color: 0xrrggbb00
   * \param[in]    dstval   target color: 0xrrggbb00
   * \return  pixd   with all pixels mapped based on the srcval/destval mapping,
   *                 or pixd on error
   *
   * <pre>
   * Notes:
   *      (1) For each component (r, b, g) separately, this does a piecewise
   *          linear mapping of the colors in pixs to colors in pixd.
   *          If rs and rd are the red src and dest components in %srcval and
   *          %dstval, then the range [0 ... rs] in pixs is mapped to
   *          [0 ... rd] in pixd.  Likewise, the range [rs ... 255] in pixs
   *          is mapped to [rd ... 255] in pixd.  And similarly for green
   *          and blue.
   *      (2) The mapping will in general change the hue of the pixels.
   *          However, if the src and dst targets are related by
   *          a transformation given by pixelFractionalShift(), the hue
   *          is invariant.
   *      (3) For inplace operation, call it this way:
   *            pixLinearMapToTargetColor(pixs, pixs, ... )
   *      (4) For generating a new pixd:
   *            pixd = pixLinearMapToTargetColor(NULL, pixs, ...)
   * </pre>
   */
  PIX *
  pixLinearMapToTargetColor(PIX      *pixd,
                            PIX      *pixs,
                            l_uint32  srcval,
                            l_uint32  dstval)
  {
  l_int32    i, j, w, h, wpl;
  l_int32    rval, gval, bval, rsval, gsval, bsval, rdval, gdval, bdval;
  l_int32   *rtab, *gtab, *btab;
  l_uint32   pixel;
  l_uint32  *line, *data;
  
      PROCNAME("pixLinearMapToTargetColor");
  
      if (!pixs)
          return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
      if (pixd && (pixd != pixs))
          return (PIX *)ERROR_PTR("pixd not null or == pixs", procName, pixd);
      if (pixGetDepth(pixs) != 32)
          return (PIX *)ERROR_PTR("pixs is not 32 bpp", procName, pixd);
  
          /* Do the work on pixd */
      if (!pixd)
          pixd = pixCopy(NULL, pixs);
  
      extractRGBValues(srcval, &rsval, &gsval, &bsval);
      extractRGBValues(dstval, &rdval, &gdval, &bdval);
      rsval = L_MIN(254, L_MAX(1, rsval));
      gsval = L_MIN(254, L_MAX(1, gsval));
      bsval = L_MIN(254, L_MAX(1, bsval));
      rtab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
      gtab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
      btab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
      if (!rtab || !gtab || !btab)
          return (PIX *)ERROR_PTR("calloc fail for tab", procName, pixd);
      for (i = 0; i < 256; i++) {
          if (i <= rsval)
              rtab[i] = (i * rdval) / rsval;
          else
              rtab[i] = rdval + ((255 - rdval) * (i - rsval)) / (255 - rsval);
          if (i <= gsval)
              gtab[i] = (i * gdval) / gsval;
          else
              gtab[i] = gdval + ((255 - gdval) * (i - gsval)) / (255 - gsval);
          if (i <= bsval)
              btab[i] = (i * bdval) / bsval;
          else
              btab[i] = bdval + ((255 - bdval) * (i - bsval)) / (255 - bsval);
      }
      pixGetDimensions(pixd, &w, &h, NULL);
      data = pixGetData(pixd);
      wpl = pixGetWpl(pixd);
      for (i = 0; i < h; i++) {
          line = data + i * wpl;
          for (j = 0; j < w; j++) {
              pixel = line[j];
              extractRGBValues(pixel, &rval, &gval, &bval);
              composeRGBPixel(rtab[rval], gtab[gval], btab[bval], &pixel);
              line[j] = pixel;
          }
      }
  
      LEPT_FREE(rtab);
      LEPT_FREE(gtab);
      LEPT_FREE(btab);
      return pixd;
  }
  
  
  /*!
   * \brief   pixelLinearMapToTargetColor()
   *
   * \param[in]    scolor    rgb source color: 0xrrggbb00
   * \param[in]    srcmap    source mapping color: 0xrrggbb00
   * \param[in]    dstmap    target mapping color: 0xrrggbb00
   * \param[out]   pdcolor   rgb dest color: 0xrrggbb00
   * \return  0 if OK, 1 on error
   *
   * <pre>
   * Notes:
   *      (1) This does this does a piecewise linear mapping of each
   *          component of %scolor to %dcolor, based on the relation
   *          between the components of %srcmap and %dstmap.  It is the
   *          same transformation, performed on a single color, as mapped
   *          on every pixel in a pix by pixLinearMapToTargetColor().
   *      (2) For each component, if the sval is larger than the smap,
   *          the dval will be pushed up from dmap towards white.
   *          Otherwise, dval will be pushed down from dmap towards black.
   *          This is because you can visualize the transformation as
   *          a linear stretching where smap moves to dmap, and everything
   *          else follows linearly with 0 and 255 fixed.
   *      (3) The mapping will in general change the hue of %scolor.
   *          However, if the %srcmap and %dstmap targets are related by
   *          a transformation given by pixelFractionalShift(), the hue
   *          will be invariant.
   * </pre>
   */
  l_ok
  pixelLinearMapToTargetColor(l_uint32   scolor,
                              l_uint32   srcmap,
                              l_uint32   dstmap,
                              l_uint32  *pdcolor)
  {
  l_int32    srval, sgval, sbval, drval, dgval, dbval;
  l_int32    srmap, sgmap, sbmap, drmap, dgmap, dbmap;
  
      PROCNAME("pixelLinearMapToTargetColor");
  
      if (!pdcolor)
          return ERROR_INT("&dcolor not defined", procName, 1);
      *pdcolor = 0;
  
      extractRGBValues(scolor, &srval, &sgval, &sbval);
      extractRGBValues(srcmap, &srmap, &sgmap, &sbmap);
      extractRGBValues(dstmap, &drmap, &dgmap, &dbmap);
      srmap = L_MIN(254, L_MAX(1, srmap));
      sgmap = L_MIN(254, L_MAX(1, sgmap));
      sbmap = L_MIN(254, L_MAX(1, sbmap));
  
      if (srval <= srmap)
          drval = (srval * drmap) / srmap;
      else
          drval = drmap + ((255 - drmap) * (srval - srmap)) / (255 - srmap);
      if (sgval <= sgmap)
          dgval = (sgval * dgmap) / sgmap;
      else
          dgval = dgmap + ((255 - dgmap) * (sgval - sgmap)) / (255 - sgmap);
      if (sbval <= sbmap)
          dbval = (sbval * dbmap) / sbmap;
      else
          dbval = dbmap + ((255 - dbmap) * (sbval - sbmap)) / (255 - sbmap);
  
      composeRGBPixel(drval, dgval, dbval, pdcolor);
      return 0;
  }
  
  
  /*------------------------------------------------------------------*
   *          Fractional shift of RGB towards black or white          *
   *------------------------------------------------------------------*/
  /*!
   * \brief   pixShiftByComponent()
   *
   * \param[in]    pixd     [optional]; either NULL or equal to pixs for in-place
   * \param[in]    pixs     32 bpp rgb
   * \param[in]    srcval   source color: 0xrrggbb00
   * \param[in]    dstval   target color: 0xrrggbb00
   * \return  pixd   with all pixels mapped based on the srcval/destval mapping,
   *                 or pixd on error
   *
   * <pre>
   * Notes:
   *      (1) For each component (r, b, g) separately, this does a linear
   *          mapping of the colors in pixs to colors in pixd.
   *          Let rs and rd be the red src and dest components in %srcval and
   *          %dstval, and rval is the red component of the src pixel.
   *          Then for all pixels in pixs, the mapping for the red
   *          component from pixs to pixd is:
   *             if (rd <= rs)   (shift toward black)
   *                 rval --> (rd/rs) * rval
   *             if (rd > rs)    (shift toward white)
   *                (255 - rval) --> ((255 - rs)/(255 - rd)) * (255 - rval)
   *          Thus if rd <= rs, the red component of all pixels is
   *          mapped by the same fraction toward white, and if rd > rs,
   *          they are mapped by the same fraction toward black.
   *          This is essentially a different linear TRC (gamma = 1)
   *          for each component.  The source and target color inputs are
   *          just used to generate the three fractions.
   *      (2) Note that this mapping differs from that in
   *          pixLinearMapToTargetColor(), which maps rs --> rd and does
   *          a piecewise stretching in between.
   *      (3) For inplace operation, call it this way:
   *            pixFractionalShiftByComponent(pixs, pixs, ... )
   *      (4) For generating a new pixd:
   *            pixd = pixLinearMapToTargetColor(NULL, pixs, ...)
   *      (5) A simple application is to color a grayscale image.
   *          A light background can be colored using srcval = 0xffffff00
   *          and picking a target background color for dstval.
   *          A dark foreground can be colored by using srcval = 0x0
   *          and choosing a target foreground color for dstval.
   * </pre>
   */
  PIX *
  pixShiftByComponent(PIX      *pixd,
                      PIX      *pixs,
                      l_uint32  srcval,
                      l_uint32  dstval)
  {
  l_int32    i, j, w, h, wpl;
  l_int32    rval, gval, bval, rsval, gsval, bsval, rdval, gdval, bdval;
  l_int32   *rtab, *gtab, *btab;
  l_uint32   pixel;
  l_uint32  *line, *data;
  PIXCMAP   *cmap;
  
      PROCNAME("pixShiftByComponent");
  
      if (!pixs)
          return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
      if (pixd && (pixd != pixs))
          return (PIX *)ERROR_PTR("pixd not null or == pixs", procName, pixd);
      if (pixGetDepth(pixs) != 32 && !pixGetColormap(pixs))
          return (PIX *)ERROR_PTR("pixs not cmapped or 32 bpp", procName, pixd);
  
          /* Do the work on pixd */
      if (!pixd)
          pixd = pixCopy(NULL, pixs);
  
          /* If colormapped, just modify it */
      if ((cmap = pixGetColormap(pixd)) != NULL) {
          pixcmapShiftByComponent(cmap, srcval, dstval);
          return pixd;
      }
  
      extractRGBValues(srcval, &rsval, &gsval, &bsval);
      extractRGBValues(dstval, &rdval, &gdval, &bdval);
      rtab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
      gtab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
      btab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
      if (!rtab || !gtab || !btab) {
          L_ERROR("calloc fail for tab\n", procName);
          goto cleanup;
      }
      for (i = 0; i < 256; i++) {
          if (rdval == rsval)
              rtab[i] = i;
          else if (rdval < rsval)
              rtab[i] = (i * rdval) / rsval;
          else
              rtab[i] = 255 - (255 - rdval) * (255 - i) / (255 - rsval);
          if (gdval == gsval)
              gtab[i] = i;
          else if (gdval < gsval)
              gtab[i] = (i * gdval) / gsval;
          else
              gtab[i] = 255 - (255 - gdval) * (255 - i) / (255 - gsval);
          if (bdval == bsval)
              btab[i] = i;
          else if (bdval < bsval)
              btab[i] = (i * bdval) / bsval;
          else
              btab[i] = 255 - (255 - bdval) * (255 - i) / (255 - bsval);
      }
      pixGetDimensions(pixd, &w, &h, NULL);
      data = pixGetData(pixd);
      wpl = pixGetWpl(pixd);
      for (i = 0; i < h; i++) {
          line = data + i * wpl;
          for (j = 0; j < w; j++) {
              pixel = line[j];
              extractRGBValues(pixel, &rval, &gval, &bval);
              composeRGBPixel(rtab[rval], gtab[gval], btab[bval], &pixel);
              line[j] = pixel;
          }
      }
  
  cleanup:
      LEPT_FREE(rtab);
      LEPT_FREE(gtab);
      LEPT_FREE(btab);
      return pixd;
  }
  
  
  /*!
   * \brief   pixelShiftByComponent()
   *
   * \param[in]    rval, gval, bval
   * \param[in]    srcval    source color: 0xrrggbb00
   * \param[in]    dstval    target color: 0xrrggbb00
   * \param[out]   ppixel    rgb value
   * \return  0 if OK, 1 on error
   *
   * <pre>
   * Notes:
   *      (1) This is a linear transformation that gives the same result
   *          on a single pixel as pixShiftByComponent() gives
   *          on a pix.  Each component is handled separately.  If
   *          the dest component is larger than the src, then the
   *          component is pushed toward 255 by the same fraction as
   *          the src --> dest shift.
   * </pre>
   */
  l_ok
  pixelShiftByComponent(l_int32    rval,
                        l_int32    gval,
                        l_int32    bval,
                        l_uint32   srcval,
                        l_uint32   dstval,
                        l_uint32  *ppixel)
  {
  l_int32  rsval, rdval, gsval, gdval, bsval, bdval, rs, gs, bs;
  
      PROCNAME("pixelShiftByComponent");
  
      if (!ppixel)
          return ERROR_INT("&pixel defined", procName, 1);
  
      extractRGBValues(srcval, &rsval, &gsval, &bsval);
      extractRGBValues(dstval, &rdval, &gdval, &bdval);
      if (rdval == rsval)
          rs = rval;
      else if (rdval < rsval)
          rs = (rval * rdval) / rsval;
      else
          rs = 255 - (255 - rdval) * (255 - rval) / (255 - rsval);
      if (gdval == gsval)
          gs = gval;
      else if (gdval < gsval)
          gs = (gval * gdval) / gsval;
      else
          gs = 255 - (255 - gdval) * (255 - gval) / (255 - gsval);
      if (bdval == bsval)
          bs = bval;
      else if (bdval < bsval)
          bs = (bval * bdval) / bsval;
      else
          bs = 255 - (255 - bdval) * (255 - bval) / (255 - bsval);
      composeRGBPixel(rs, gs, bs, ppixel);
      return 0;
  }
  
  
  /*!
   * \brief   pixelFractionalShift()
   *
   * \param[in]    rval, gval, bval
   * \param[in]    fraction   negative toward black; positive toward white
   * \param[out]   ppixel     rgb value
   * \return  0 if OK, 1 on error
   *
   * <pre>
   * Notes:
   *      (1) This transformation leaves the hue invariant, while changing
   *          the saturation and intensity.  It can be used for that
   *          purpose in pixLinearMapToTargetColor().
   *      (2) %fraction is in the range [-1 .... +1].  If %fraction < 0,
   *          saturation is increased and brightness is reduced.  The
   *          opposite results if %fraction > 0.  If %fraction == -1,
   *          the resulting pixel is black; %fraction == 1 results in white.
   * </pre>
   */
  l_ok
  pixelFractionalShift(l_int32    rval,
                       l_int32    gval,
                       l_int32    bval,
                       l_float32  fraction,
                       l_uint32  *ppixel)
  {
  l_int32  nrval, ngval, nbval;
  
      PROCNAME("pixelFractionalShift");
  
      if (!ppixel)
          return ERROR_INT("&pixel defined", procName, 1);
      if (fraction < -1.0 || fraction > 1.0)
          return ERROR_INT("fraction not in [-1 ... +1]", procName, 1);
  
      nrval = (fraction < 0) ? (l_int32)((1.0 + fraction) * rval + 0.5) :
              rval + (l_int32)(fraction * (255 - rval) + 0.5);
      ngval = (fraction < 0) ? (l_int32)((1.0 + fraction) * gval + 0.5) :
              gval + (l_int32)(fraction * (255 - gval) + 0.5);
      nbval = (fraction < 0) ? (l_int32)((1.0 + fraction) * bval + 0.5) :
              bval + (l_int32)(fraction * (255 - bval) + 0.5);
      composeRGBPixel(nrval, ngval, nbval, ppixel);
      return 0;
  }
  