/*====================================================================*
 -  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.
 *====================================================================*/

/*
 *  blend.c
 *
 *      Blending two images that are not colormapped
 *           PIX             *pixBlend()
 *           PIX             *pixBlendMask()
 *           PIX             *pixBlendGray()
 *           PIX             *pixBlendGrayInverse()
 *           PIX             *pixBlendColor()
 *           PIX             *pixBlendColorByChannel()
 *           PIX             *pixBlendGrayAdapt()
 *           static l_int32   blendComponents()
 *           PIX             *pixFadeWithGray()
 *           PIX             *pixBlendHardLight()
 *           static l_int32   blendHardLightComponents()
 *
 *      Blending two colormapped images
 *           l_int32          pixBlendCmap()
 *
 *      Blending two images using a third (alpha mask)
 *           PIX             *pixBlendWithGrayMask()
 *
 *      Blending background to a specific color
 *           PIX             *pixBlendBackgroundToColor()
 *
 *      Multiplying by a specific color
 *           PIX             *pixMultiplyByColor()
 *
 *      Rendering with alpha blending over a uniform background
 *           PIX             *pixAlphaBlendUniform()
 *
 *      Setting a transparent alpha component over a white background
 *           PIX             *pixSetAlphaOverWhite()
 *
 *  In blending operations a new pix is produced where typically
 *  a subset of pixels in src1 are changed by the set of pixels
 *  in src2, when src2 is located in a given position relative
 *  to src1.  This is similar to rasterop, except that the
 *  blending operations we allow are more complex, and typically
 *  result in dest pixels that are a linear combination of two
 *  pixels, such as src1 and its inverse.  I find it convenient
 *  to think of src2 as the "blender" (the one that takes the action)
 *  and src1 as the "blendee" (the one that changes).
 *
 *  Blending works best when src1 is 8 or 32 bpp.  We also allow
 *  src1 to be colormapped, but the colormap is removed before blending,
 *  so if src1 is colormapped, we can't allow in-place blending.
 *
 *  Because src2 is typically smaller than src1, we can implement by
 *  clipping src2 to src1 and then transforming some of the dest
 *  pixels that are under the support of src2.  In practice, we
 *  do the clipping in the inner pixel loop.  For grayscale and
 *  color src2, we also allow a simple form of transparency, where
 *  pixels of a particular value in src2 are transparent; for those pixels,
 *  no blending is done.
 *
 *  The blending functions are categorized by the depth of src2,
 *  the blender, and not that of src1, the blendee.
 *
 *   - If src2 is 1 bpp, we can do one of three things:
 *     (1) L_BLEND_WITH_INVERSE: Blend a given fraction of src1 with its
 *         inverse color for those pixels in src2 that are fg (ON),
 *         and leave the dest pixels unchanged for pixels in src2 that
 *         are bg (OFF).
 *     (2) L_BLEND_TO_WHITE: Fade the src1 pixels toward white by a
 *         given fraction for those pixels in src2 that are fg (ON),
 *         and leave the dest pixels unchanged for pixels in src2 that
 *         are bg (OFF).
 *     (3) L_BLEND_TO_BLACK: Fade the src1 pixels toward black by a
 *         given fraction for those pixels in src2 that are fg (ON),
 *         and leave the dest pixels unchanged for pixels in src2 that
 *         are bg (OFF).
 *     The blending function is pixBlendMask().
 *
 *   - If src2 is 8 bpp grayscale, we can do one of two things
 *     (but see pixFadeWithGray() below):
 *     (1) L_BLEND_GRAY: If src1 is 8 bpp, mix the two values, using
 *         a fraction of src2 and (1 - fraction) of src1.
 *         If src1 is 32 bpp (rgb), mix the fraction of src2 with
 *         each of the color components in src1.
 *     (2) L_BLEND_GRAY_WITH_INVERSE: Use the grayscale value in src2
 *         to determine how much of the inverse of a src1 pixel is
 *         to be combined with the pixel value.  The input fraction
 *         further acts to scale the change in the src1 pixel.
 *     The blending function is pixBlendGray().
 *
 *   - If src2 is color, we blend a given fraction of src2 with
 *     src1.  If src1 is 8 bpp, the resulting image is 32 bpp.
 *     The blending function is pixBlendColor().
 *
 *   - For all three blending functions -- pixBlendMask(), pixBlendGray()
 *     and pixBlendColor() -- you can apply the blender to the blendee
 *     either in-place or generating a new pix.  For the in-place
 *     operation, this requires that the depth of the resulting pix
 *     must equal that of the input pixs1.
 *
 *   - We remove colormaps from src1 and src2 before blending.
 *     Any quantization would have to be done after blending.
 *
 *  We include another function, pixFadeWithGray(), that blends
 *  a gray or color src1 with a gray src2.  It does one of these things:
 *     (1) L_BLEND_TO_WHITE: Fade the src1 pixels toward white by
 *         a number times the value in src2.
 *     (2) L_BLEND_TO_BLACK: Fade the src1 pixels toward black by
 *         a number times the value in src2.
 *
 *  Also included is a generalization of the so-called "hard light"
 *  blending: pixBlendHardLight().  We generalize by allowing a fraction < 1.0
 *  of the blender to be admixed with the blendee.  The standard function
 *  does full mixing.
 */


#include "allheaders.h"

static l_int32 blendComponents(l_int32 a, l_int32 b, l_float32 fract);
static l_int32 blendHardLightComponents(l_int32 a, l_int32 b, l_float32 fract);


/*-------------------------------------------------------------*
 *         Blending two images that are not colormapped        *
 *-------------------------------------------------------------*/
/*!
 *  pixBlend()
 *
 *      Input:  pixs1 (blendee)
 *              pixs2 (blender; typ. smaller)
 *              x,y  (origin (UL corner) of pixs2 relative to
 *                    the origin of pixs1; can be < 0)
 *              fract (blending fraction)
 *      Return: pixd (blended image), or null on error
 *
 *  Notes:
 *      (1) This is a simple top-level interface.  For more flexibility,
 *          call directly into pixBlendMask(), etc.
 */
PIX *
pixBlend(PIX       *pixs1,
         PIX       *pixs2,
         l_int32    x,
         l_int32    y,
         l_float32  fract)
{
l_int32    w1, h1, d1, d2;
BOX       *box;
PIX       *pixc, *pixt, *pixd;

    PROCNAME("pixBlend");

    if (!pixs1)
        return (PIX *)ERROR_PTR("pixs1 not defined", procName, NULL);
    if (!pixs2)
        return (PIX *)ERROR_PTR("pixs2 not defined", procName, NULL);

        /* check relative depths */
    d1 = pixGetDepth(pixs1);
    d2 = pixGetDepth(pixs2);
    if (d1 == 1 && d2 > 1)
        return (PIX *)ERROR_PTR("mixing gray or color with 1 bpp",
                                procName, NULL);

        /* remove colormap from pixs2 if necessary */
    pixt = pixRemoveColormap(pixs2, REMOVE_CMAP_BASED_ON_SRC);
    d2 = pixGetDepth(pixt);

        /* Check if pixs2 is clipped by its position with respect
         * to pixs1; if so, clip it and redefine x and y if necessary.
         * This actually isn't necessary, as the specific blending
         * functions do the clipping directly in the pixel loop
         * over pixs2, but it's included here to show how it can
         * easily be done on pixs2 first. */
    pixGetDimensions(pixs1, &w1, &h1, NULL);
    box = boxCreate(-x, -y, w1, h1);  /* box of pixs1 relative to pixs2 */
    pixc = pixClipRectangle(pixt, box, NULL);
    boxDestroy(&box);
    if (!pixc) {
        L_WARNING("box doesn't overlap pix", procName);
        return NULL;
    }
    x = L_MAX(0, x);
    y = L_MAX(0, y);

    if (d2 == 1)
        pixd = pixBlendMask(NULL, pixs1, pixc, x, y, fract,
                            L_BLEND_WITH_INVERSE);
    else if (d2 == 8)
        pixd = pixBlendGray(NULL, pixs1, pixc, x, y, fract,
                            L_BLEND_GRAY, 0, 0);
    else  /* d2 == 32 */
        pixd = pixBlendColor(NULL, pixs1, pixc, x, y, fract, 0, 0);

    pixDestroy(&pixc);
    pixDestroy(&pixt);
    return pixd;
}


/*!
 *  pixBlendMask()
 *
 *      Input:  pixd (<optional>; either NULL or equal to pixs1 for in-place)
 *              pixs1 (blendee; depth > 1)
 *              pixs2 (blender; typ. smaller in size than pixs1)
 *              x,y  (origin (UL corner) of pixs2 relative to
 *                    the origin of pixs1; can be < 0)
 *              fract (blending fraction)
 *              type (L_BLEND_WITH_INVERSE, L_BLEND_TO_WHITE, L_BLEND_TO_BLACK)
 *      Return: pixd if OK; pixs1 on error
 *
 *  Notes:
 *      (1) pixs2 must be 1 bpp
 *      (2) Clipping of pixs2 to pixs1 is done in the inner pixel loop.
 *      (3) If pixs1 has a colormap, it is removed.
 *      (4) For inplace operation, call it this way:
 *            pixBlendMask(pixs1, pixs1, pixs2, ...)
 *      (5) For generating a new pixd:
 *            pixd = pixBlendMask(NULL, pixs1, pixs2, ...)
 *      (6) Only call in-place if pixs1 does not have a colormap.
 */
PIX *
pixBlendMask(PIX       *pixd,
             PIX       *pixs1,
             PIX       *pixs2,
             l_int32    x,
             l_int32    y,
             l_float32  fract,
             l_int32    type)
{
l_int32    i, j, d, wc, hc, w, h, wplc;
l_int32    val, rval, gval, bval;
l_uint32   pixval;
l_uint32  *linec, *datac;
PIX       *pixc, *pixt1, *pixt2;

    PROCNAME("pixBlendMask");

    if (!pixs1)
        return (PIX *)ERROR_PTR("pixs1 not defined", procName, pixd);
    if (!pixs2)
        return (PIX *)ERROR_PTR("pixs2 not defined", procName, pixd);
    if (pixGetDepth(pixs1) == 1)
        return (PIX *)ERROR_PTR("pixs1 is 1 bpp", procName, pixd);
    if (pixGetDepth(pixs2) != 1)
        return (PIX *)ERROR_PTR("pixs2 not 1 bpp", procName, pixd);
    if (pixd == pixs1 && pixGetColormap(pixs1))
        return (PIX *)ERROR_PTR("inplace; pixs1 has colormap", procName, pixd);
    if (pixd && (pixd != pixs1))
        return (PIX *)ERROR_PTR("pixd must be NULL or pixs1", procName, pixd);
    if (fract < 0.0 || fract > 1.0) {
        L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5", procName);
        fract = 0.5;
    }
    if (type != L_BLEND_WITH_INVERSE && type != L_BLEND_TO_WHITE &&
        type != L_BLEND_TO_BLACK) {
        L_WARNING("invalid blend type; setting to L_BLEND_WITH_INVERSE",
                  procName);
        type = L_BLEND_WITH_INVERSE;
    }


        /* If pixd != NULL, we know that it is equal to pixs1 and
         * that pixs1 does not have a colormap, so that an in-place operation
         * can be done.  Otherwise, remove colormap from pixs1 if
         * it exists and unpack to at least 8 bpp if necessary,
         * to do the blending on a new pix. */
    if (!pixd) {
        pixt1 = pixRemoveColormap(pixs1, REMOVE_CMAP_BASED_ON_SRC);
        if (pixGetDepth(pixt1) < 8)
            pixt2 = pixConvertTo8(pixt1, FALSE);
        else
            pixt2 = pixClone(pixt1);
        pixd = pixCopy(NULL, pixt2);
        pixDestroy(&pixt1);
        pixDestroy(&pixt2);
    }

    pixGetDimensions(pixd, &w, &h, &d);  /* d must be either 8 or 32 bpp */
    pixc = pixClone(pixs2);
    wc = pixGetWidth(pixc);
    hc = pixGetHeight(pixc);
    datac = pixGetData(pixc);
    wplc = pixGetWpl(pixc);

        /* Check limits for src1, in case clipping was not done. */
    switch (type)
    {
    case L_BLEND_WITH_INVERSE:
            /*
             * The basic logic for this blending is:
             *      p -->  (1 - f) * p + f * (1 - p)
             * where p is a normalized value: p = pixval / 255.
             * Thus,
             *      p -->  p + f * (1 - 2 * p)
             */
        for (i = 0; i < hc; i++) {
            if (i + y < 0  || i + y >= h) continue;
            linec = datac + i * wplc;
            for (j = 0; j < wc; j++) {
                if (j + x < 0  || j + x >= w) continue;
                bval = GET_DATA_BIT(linec, j);
                if (bval) {
                    switch (d)
                    {
                    case 8:
                        pixGetPixel(pixd, x + j, y + i, &pixval);
                        val = (l_int32)(pixval + fract * (255 - 2 * pixval));
                        pixSetPixel(pixd, x + j, y + i, val);
                        break;
                    case 32:
                        pixGetPixel(pixd, x + j, y + i, &pixval);
                        extractRGBValues(pixval, &rval, &gval, &bval);
                        rval = (l_int32)(rval + fract * (255 - 2 * rval));
                        gval = (l_int32)(gval + fract * (255 - 2 * gval));
                        bval = (l_int32)(bval + fract * (255 - 2 * bval));
                        composeRGBPixel(rval, gval, bval, &pixval);
                        pixSetPixel(pixd, x + j, y + i, pixval);
                        break;
                    default:
                        L_WARNING("d neither 8 nor 32 bpp; no blend", procName);
                    }
                }
            }
        }
        break;
    case L_BLEND_TO_WHITE:
            /*
             * The basic logic for this blending is:
             *      p -->  p + f * (1 - p)    (p normalized to [0...1])
             */
        for (i = 0; i < hc; i++) {
            if (i + y < 0  || i + y >= h) continue;
            linec = datac + i * wplc;
            for (j = 0; j < wc; j++) {
                if (j + x < 0  || j + x >= w) continue;
                bval = GET_DATA_BIT(linec, j);
                if (bval) {
                    switch (d)
                    {
                    case 8:
                        pixGetPixel(pixd, x + j, y + i, &pixval);
                        val = (l_int32)(pixval + fract * (255 - pixval));
                        pixSetPixel(pixd, x + j, y + i, val);
                        break;
                    case 32:
                        pixGetPixel(pixd, x + j, y + i, &pixval);
                        extractRGBValues(pixval, &rval, &gval, &bval);
                        rval = (l_int32)(rval + fract * (255 - rval));
                        gval = (l_int32)(gval + fract * (255 - gval));
                        bval = (l_int32)(bval + fract * (255 - bval));
                        composeRGBPixel(rval, gval, bval, &pixval);
                        pixSetPixel(pixd, x + j, y + i, pixval);
                        break;
                    default:
                        L_WARNING("d neither 8 nor 32 bpp; no blend", procName);
                    }
                }
            }
        }
        break;
    case L_BLEND_TO_BLACK:
            /*
             * The basic logic for this blending is:
             *      p -->  (1 - f) * p     (p normalized to [0...1])
             */
        for (i = 0; i < hc; i++) {
            if (i + y < 0  || i + y >= h) continue;
            linec = datac + i * wplc;
            for (j = 0; j < wc; j++) {
                if (j + x < 0  || j + x >= w) continue;
                bval = GET_DATA_BIT(linec, j);
                if (bval) {
                    switch (d)
                    {
                    case 8:
                        pixGetPixel(pixd, x + j, y + i, &pixval);
                        val = (l_int32)((1. - fract) * pixval);
                        pixSetPixel(pixd, x + j, y + i, val);
                        break;
                    case 32:
                        pixGetPixel(pixd, x + j, y + i, &pixval);
                        extractRGBValues(pixval, &rval, &gval, &bval);
                        rval = (l_int32)((1. - fract) * rval);
                        gval = (l_int32)((1. - fract) * gval);
                        bval = (l_int32)((1. - fract) * bval);
                        composeRGBPixel(rval, gval, bval, &pixval);
                        pixSetPixel(pixd, x + j, y + i, pixval);
                        break;
                    default:
                        L_WARNING("d neither 8 nor 32 bpp; no blend", procName);
                    }
                }
            }
        }
        break;
    default:
        L_WARNING("invalid binary mask blend type", procName);
        break;
    }

    pixDestroy(&pixc);
    return pixd;
}


/*!
 *  pixBlendGray()
 *
 *      Input:  pixd (<optional>; either NULL or equal to pixs1 for in-place)
 *              pixs1 (blendee; depth > 1)
 *              pixs2 (blender, 8 bpp; typ. smaller in size than pixs1)
 *              x,y  (origin (UL corner) of pixs2 relative to
 *                    the origin of pixs1; can be < 0)
 *              fract (blending fraction)
 *              type (L_BLEND_GRAY, L_BLEND_GRAY_WITH_INVERSE)
 *              transparent (1 to use transparency; 0 otherwise)
 *              transpix (pixel grayval in pixs2 that is to be transparent)
 *      Return: pixd if OK; pixs1 on error
 *
 *  Notes:
 *      (1) pixs2 must be 8 bpp, and have no colormap.
 *      (2) Clipping of pixs2 to pixs1 is done in the inner pixel loop.
 *      (3) If pixs1 has a colormap, it is removed.
 *      (4) If pixs1 has depth < 8, it is unpacked to generate a 8 bpp pix.
 *      (5) For inplace operation, call it this way:
 *            pixBlendGray(pixs1, pixs1, pixs2, ...)
 *      (6) For generating a new pixd:
 *            pixd = pixBlendGray(NULL, pixs1, pixs2, ...)
 *      (7) Only call in-place if pixs1 does not have a colormap;
 *          otherwise it is an error.
 *      (8) If transparent = 0, the blending fraction (fract) is
 *          applied equally to all pixels.
 *      (9) If transparent = 1, all pixels of value transpix (typically
 *          either 0 or 0xff) in pixs2 are transparent in the blend.
 *      (10) After processing pixs1, it is either 8 bpp or 32 bpp:
 *          - if 8 bpp, the fraction of pixs2 is mixed with pixs1.
 *          - if 32 bpp, each component of pixs1 is mixed with
 *            the same fraction of pixs2.
 *      (11) For L_BLEND_GRAY_WITH_INVERSE, the white values of the blendee
 *           (cval == 255 in the code below) result in a delta of 0.
 *           Thus, these pixels are intrinsically transparent!
 *           The "pivot" value of the src, at which no blending occurs, is
 *           128.  Compare with the adaptive pivot in pixBlendGrayAdapt().
 */
PIX *
pixBlendGray(PIX       *pixd,
             PIX       *pixs1,
             PIX       *pixs2,
             l_int32    x,
             l_int32    y,
             l_float32  fract,
             l_int32    type,
             l_int32    transparent,
             l_uint32   transpix)
{
l_int32    i, j, d, wc, hc, w, h, wplc, wpld, delta;
l_int32    ival, irval, igval, ibval, cval, dval;
l_uint32   val32;
l_uint32  *linec, *lined, *datac, *datad;
PIX       *pixc, *pixt1, *pixt2;

    PROCNAME("pixBlendGray");

    if (!pixs1)
        return (PIX *)ERROR_PTR("pixs1 not defined", procName, pixd);
    if (!pixs2)
        return (PIX *)ERROR_PTR("pixs2 not defined", procName, pixd);
    if (pixGetDepth(pixs1) == 1)
        return (PIX *)ERROR_PTR("pixs1 is 1 bpp", procName, pixd);
    if (pixGetDepth(pixs2) != 8)
        return (PIX *)ERROR_PTR("pixs2 not 8 bpp", procName, pixd);
    if (pixGetColormap(pixs2))
        return (PIX *)ERROR_PTR("pixs2 has a colormap", procName, pixd);
    if (pixd == pixs1 && pixGetColormap(pixs1))
        return (PIX *)ERROR_PTR("can't do in-place with cmap", procName, pixd);
    if (pixd && (pixd != pixs1))
        return (PIX *)ERROR_PTR("pixd must be NULL or pixs1", procName, pixd);
    if (fract < 0.0 || fract > 1.0) {
        L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5", procName);
        fract = 0.5;
    }
    if (type != L_BLEND_GRAY && type != L_BLEND_GRAY_WITH_INVERSE) {
        L_WARNING("invalid blend type; setting to L_BLEND_GRAY", procName);
        type = L_BLEND_GRAY;
    }

        /* If pixd != NULL, we know that it is equal to pixs1 and
         * that pixs1 does not have a colormap, so that an in-place operation
         * can be done.  Otherwise, remove colormap from pixs1 if
         * it exists and unpack to at least 8 bpp if necessary,
         * to do the blending on a new pix. */
    if (!pixd) {
        pixt1 = pixRemoveColormap(pixs1, REMOVE_CMAP_BASED_ON_SRC);
        if (pixGetDepth(pixt1) < 8)
            pixt2 = pixConvertTo8(pixt1, FALSE);
        else
            pixt2 = pixClone(pixt1);
        pixd = pixCopy(NULL, pixt2);
        pixDestroy(&pixt1);
        pixDestroy(&pixt2);
    }

    pixGetDimensions(pixd, &w, &h, &d);  /* 8 or 32 bpp */
    wpld = pixGetWpl(pixd);
    datad = pixGetData(pixd);
    pixc = pixClone(pixs2);
    pixGetDimensions(pixc, &wc, &hc, NULL);
    datac = pixGetData(pixc);
    wplc = pixGetWpl(pixc);

        /* Check limits for src1, in case clipping was not done */
    if (type == L_BLEND_GRAY) {
        /*
         * The basic logic for this blending is:
         *      p -->  (1 - f) * p + f * c
         * where c is the 8 bpp blender.  All values are normalized to [0...1].
         */
        for (i = 0; i < hc; i++) {
            if (i + y < 0  || i + y >= h) continue;
            linec = datac + i * wplc;
            lined = datad + (i + y) * wpld;
            switch (d)
            {
            case 8:
                for (j = 0; j < wc; j++) {
                    if (j + x < 0  || j + x >= w) continue;
                    cval = GET_DATA_BYTE(linec, j);
                    if (transparent == 0 ||
                        (transparent != 0 && cval != transpix)) {
                        dval = GET_DATA_BYTE(lined, j + x);
                        ival = (l_int32)((1. - fract) * dval + fract * cval);
                        SET_DATA_BYTE(lined, j + x, ival);
                    }
                }
                break;
            case 32:
                for (j = 0; j < wc; j++) {
                    if (j + x < 0  || j + x >= w) continue;
                    cval = GET_DATA_BYTE(linec, j);
                    if (transparent == 0 ||
                        (transparent != 0 && cval != transpix)) {
                        val32 = *(lined + j + x);
                        extractRGBValues(val32, &irval, &igval, &ibval);
                        irval = (l_int32)((1. - fract) * irval + fract * cval);
                        igval = (l_int32)((1. - fract) * igval + fract * cval);
                        ibval = (l_int32)((1. - fract) * ibval + fract * cval);
                        composeRGBPixel(irval, igval, ibval, &val32);
                        *(lined + j + x) = val32;
                    }
                }
                break;
            default:
                break;   /* shouldn't happen */
            }
        }
    }
    else {  /* L_BLEND_GRAY_WITH_INVERSE */
        for (i = 0; i < hc; i++) {
            if (i + y < 0  || i + y >= h) continue;
            linec = datac + i * wplc;
            lined = datad + (i + y) * wpld;
            switch (d)
            {
            case 8:
                /*
                 * For 8 bpp, the dest pix is shifted by a signed amount
                 * proportional to the distance from 128 (the pivot value),
                 * and to the darkness of src2.  If the dest is darker
                 * than 128, it becomes lighter, and v.v.
                 * The basic logic is:
                 *     d  -->  d + f * (0.5 - d) * (1 - c)
                 * where d and c are normalized pixel values for src1 and
                 * src2, respectively, with 8 bit normalization to [0...1].
                 */
                for (j = 0; j < wc; j++) {
                    if (j + x < 0  || j + x >= w) continue;
                    cval = GET_DATA_BYTE(linec, j);
                    if (transparent == 0 ||
                        (transparent != 0 && cval != transpix)) {
                        ival = GET_DATA_BYTE(lined, j + x);
                        delta = (128 - ival) * (255 - cval) / 256;
                        ival += (l_int32)(fract * delta + 0.5);
                        SET_DATA_BYTE(lined, j + x, ival);
                    }
                }
                break;
            case 32:
                /* Each component is shifted by the same formula for 8 bpp */
                for (j = 0; j < wc; j++) {
                    if (j + x < 0  || j + x >= w) continue;
                    cval = GET_DATA_BYTE(linec, j);
                    if (transparent == 0 ||
                        (transparent != 0 && cval != transpix)) {
                        val32 = *(lined + j + x);
                        extractRGBValues(val32, &irval, &igval, &ibval);
                        delta = (128 - irval) * (255 - cval) / 256;
                        irval += (l_int32)(fract * delta + 0.5);
                        delta = (128 - igval) * (255 - cval) / 256;
                        igval += (l_int32)(fract * delta + 0.5);
                        delta = (128 - ibval) * (255 - cval) / 256;
                        ibval += (l_int32)(fract * delta + 0.5);
                        composeRGBPixel(irval, igval, ibval, &val32);
                        *(lined + j + x) = val32;
                    }
                }
                break;
            default:
                break;   /* shouldn't happen */
            }
        }
    }

    pixDestroy(&pixc);
    return pixd;
}


/*!
 *  pixBlendGrayInverse()
 *
 *      Input:  pixd (<optional>; either NULL or equal to pixs1 for in-place)
 *              pixs1 (blendee; depth > 1)
 *              pixs2 (blender, 8 bpp; typ. smaller in size than pixs1)
 *              x,y  (origin (UL corner) of pixs2 relative to
 *                    the origin of pixs1; can be < 0)
 *              fract (blending fraction)
 *      Return: pixd if OK; pixs1 on error
 *
 *  Notes:
 *      (1) pixs2 must be 8 bpp, and have no colormap.
 *      (2) Clipping of pixs2 to pixs1 is done in the inner pixel loop.
 *      (3) If pixs1 has a colormap, it is removed.
 *      (4) If pixs1 has depth < 8, it is unpacked to generate a 8 bpp pix.
 *      (5) For inplace operation, call it this way:
 *            pixBlendGray(pixs1, pixs1, pixs2, ...)
 *      (6) For generating a new pixd:
 *            pixd = pixBlendGrayInverse(NULL, pixs1, pixs2, ...)
 *      (7) Only call in-place if pixs1 does not have a colormap;
 *          otherwise it is an error.
 *      (8) This is a no-nonsense blender.  It changes the src1 pixel except
 *          when the src1 pixel is midlevel gray.  Use fract == 1 for the most
 *          aggressive blending, where, if the gray pixel in pixs2 is 0,
 *          we get a complete inversion of the color of the src pixel in pixs1.
 *      (9) The basic logic is that each component transforms by:
                 d  -->  c * d + (1 - c ) * (f * (1 - d) + d * (1 - f))
 *          where c is the blender pixel from pixs2,
 *                f is @fract,
 *                c and d are normalized to [0...1]
 *          This has the property that for f == 0 (no blend) or c == 1 (white):
 *               d  -->  d
 *          For c == 0 (black) we get maximum inversion:
 *               d  -->  f * (1 - d) + d * (1 - f)   [inversion by fraction f]
 */
PIX *
pixBlendGrayInverse(PIX       *pixd,
                    PIX       *pixs1,
                    PIX       *pixs2,
                    l_int32    x,
                    l_int32    y,
                    l_float32  fract)
{
l_int32    i, j, d, wc, hc, w, h, wplc, wpld;
l_int32    irval, igval, ibval, cval, dval;
l_float32  a;
l_uint32   val32;
l_uint32  *linec, *lined, *datac, *datad;
PIX       *pixc, *pixt1, *pixt2;

    PROCNAME("pixBlendGrayInverse");

    if (!pixs1)
        return (PIX *)ERROR_PTR("pixs1 not defined", procName, pixd);
    if (!pixs2)
        return (PIX *)ERROR_PTR("pixs2 not defined", procName, pixd);
    if (pixGetDepth(pixs1) == 1)
        return (PIX *)ERROR_PTR("pixs1 is 1 bpp", procName, pixd);
    if (pixGetDepth(pixs2) != 8)
        return (PIX *)ERROR_PTR("pixs2 not 8 bpp", procName, pixd);
    if (pixGetColormap(pixs2))
        return (PIX *)ERROR_PTR("pixs2 has a colormap", procName, pixd);
    if (pixd == pixs1 && pixGetColormap(pixs1))
        return (PIX *)ERROR_PTR("can't do in-place with cmap", procName, pixd);
    if (pixd && (pixd != pixs1))
        return (PIX *)ERROR_PTR("pixd must be NULL or pixs1", procName, pixd);
    if (fract < 0.0 || fract > 1.0) {
        L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5", procName);
        fract = 0.5;
    }

        /* If pixd != NULL, we know that it is equal to pixs1 and
         * that pixs1 does not have a colormap, so that an in-place operation
         * can be done.  Otherwise, remove colormap from pixs1 if
         * it exists and unpack to at least 8 bpp if necessary,
         * to do the blending on a new pix. */
    if (!pixd) {
        pixt1 = pixRemoveColormap(pixs1, REMOVE_CMAP_BASED_ON_SRC);
        if (pixGetDepth(pixt1) < 8)
            pixt2 = pixConvertTo8(pixt1, FALSE);
        else
            pixt2 = pixClone(pixt1);
        pixd = pixCopy(NULL, pixt2);
        pixDestroy(&pixt1);
        pixDestroy(&pixt2);
    }

    pixGetDimensions(pixd, &w, &h, &d);  /* 8 or 32 bpp */
    wpld = pixGetWpl(pixd);
    datad = pixGetData(pixd);
    pixc = pixClone(pixs2);
    pixGetDimensions(pixc, &wc, &hc, NULL);
    datac = pixGetData(pixc);
    wplc = pixGetWpl(pixc);

        /* Check limits for src1, in case clipping was not done */
    for (i = 0; i < hc; i++) {
        if (i + y < 0  || i + y >= h) continue;
        linec = datac + i * wplc;
        lined = datad + (i + y) * wpld;
        switch (d)
        {
        case 8:
            for (j = 0; j < wc; j++) {
                if (j + x < 0  || j + x >= w) continue;
                cval = GET_DATA_BYTE(linec, j);
                dval = GET_DATA_BYTE(lined, j + x);
                a = (1.0 - fract) * dval + fract * (255.0 - dval);
                dval = (l_int32)(cval * dval / 255.0 +
                                  a * (255.0 - cval) / 255.0);
                SET_DATA_BYTE(lined, j + x, dval);
            }
            break;
        case 32:
            for (j = 0; j < wc; j++) {
                if (j + x < 0  || j + x >= w) continue;
                cval = GET_DATA_BYTE(linec, j);
                val32 = *(lined + j + x);
                extractRGBValues(val32, &irval, &igval, &ibval);
                a = (1.0 - fract) * irval + fract * (255.0 - irval);
                irval = (l_int32)(cval * irval / 255.0 +
                                  a * (255.0 - cval) / 255.0);
                a = (1.0 - fract) * igval + fract * (255.0 - igval);
                igval = (l_int32)(cval * igval / 255.0 +
                                  a * (255.0 - cval) / 255.0);
                a = (1.0 - fract) * ibval + fract * (255.0 - ibval);
                ibval = (l_int32)(cval * ibval / 255.0 +
                                  a * (255.0 - cval) / 255.0);
                composeRGBPixel(irval, igval, ibval, &val32);
                *(lined + j + x) = val32;
            }
            break;
        default:
            break;   /* shouldn't happen */
        }
    }

    pixDestroy(&pixc);
    return pixd;
}


/*!
 *  pixBlendColor()
 *
 *      Input:  pixd (<optional>; either NULL or equal to pixs1 for in-place)
 *              pixs1 (blendee; depth > 1)
 *              pixs2 (blender, 32 bpp; typ. smaller in size than pixs1)
 *              x,y  (origin (UL corner) of pixs2 relative to
 *                    the origin of pixs1)
 *              fract (blending fraction)
 *              transparent (1 to use transparency; 0 otherwise)
 *              transpix (pixel color in pixs2 that is to be transparent)
 *      Return: pixd if OK; pixs1 on error
 *
 *  Notes:
 *      (1) pixs2 must be 32 bpp, and have no colormap.
 *      (2) Clipping of pixs2 to pixs1 is done in the inner pixel loop.
 *      (3) If pixs1 has a colormap, it is removed to generate a 32 bpp pix.
 *      (4) If pixs1 has depth < 32, it is unpacked to generate a 32 bpp pix.
 *      (5) For inplace operation, call it this way:
 *            pixBlendColor(pixs1, pixs1, pixs2, ...)
 *      (6) For generating a new pixd:
 *            pixd = pixBlendColor(NULL, pixs1, pixs2, ...)
 *      (7) Only call in-place if pixs1 is 32 bpp; otherwise it is an error.
 *      (8) If transparent = 0, the blending fraction (fract) is
 *          applied equally to all pixels.
 *      (9) If transparent = 1, all pixels of value transpix (typically
 *          either 0 or 0xffffff00) in pixs2 are transparent in the blend.
 */
PIX *
pixBlendColor(PIX       *pixd,
              PIX       *pixs1,
              PIX       *pixs2,
              l_int32    x,
              l_int32    y,
              l_float32  fract,
              l_int32    transparent,
              l_uint32   transpix)
{
l_int32    i, j, wc, hc, w, h, wplc, wpld;
l_int32    rval, gval, bval, rcval, gcval, bcval;
l_uint32   cval32, val32;
l_uint32  *linec, *lined, *datac, *datad;
PIX       *pixc, *pixt1, *pixt2;

    PROCNAME("pixBlendColor");

    if (!pixs1)
        return (PIX *)ERROR_PTR("pixs1 not defined", procName, pixd);
    if (!pixs2)
        return (PIX *)ERROR_PTR("pixs2 not defined", procName, pixd);
    if (pixGetDepth(pixs1) == 1)
        return (PIX *)ERROR_PTR("pixs1 is 1 bpp", procName, pixd);
    if (pixGetDepth(pixs2) != 32)
        return (PIX *)ERROR_PTR("pixs2 not 32 bpp", procName, pixd);
    if (pixd == pixs1 && pixGetDepth(pixs1) != 32)
        return (PIX *)ERROR_PTR("inplace; pixs1 not 32 bpp", procName, pixd);
    if (pixd && (pixd != pixs1))
        return (PIX *)ERROR_PTR("pixd must be NULL or pixs1", procName, pixd);
    if (fract < 0.0 || fract > 1.0) {
        L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5", procName);
        fract = 0.5;
    }

        /* If pixd != NULL, we know that it is equal to pixs1 and
         * that pixs1 is 32 bpp rgb, so that an in-place operation
         * can be done.  Otherwise, remove colormap from pixs1 if
         * it exists and unpack to 32 bpp if necessary, to do the
         * blending on a new 32 bpp Pix. */
    if (!pixd) {
        pixt1 = pixRemoveColormap(pixs1, REMOVE_CMAP_TO_FULL_COLOR);
        if (pixGetDepth(pixt1) < 32)
            pixt2 = pixConvertTo32(pixt1);
        else
            pixt2 = pixClone(pixt1);
        pixd = pixCopy(NULL, pixt2);
        pixDestroy(&pixt1);
        pixDestroy(&pixt2);
    }

    pixGetDimensions(pixd, &w, &h, NULL);
    wpld = pixGetWpl(pixd);
    datad = pixGetData(pixd);
    pixc = pixClone(pixs2);
    pixGetDimensions(pixc, &wc, &hc, NULL);
    datac = pixGetData(pixc);
    wplc = pixGetWpl(pixc);

        /* Check limits for src1, in case clipping was not done */
    for (i = 0; i < hc; i++) {
        /*
         * The basic logic for this blending is:
         *      p -->  (1 - f) * p + f * c
         * for each color channel.  c is a color component of the blender.
         * All values are normalized to [0...1].
         */
        if (i + y < 0  || i + y >= h) continue;
        linec = datac + i * wplc;
        lined = datad + (i + y) * wpld;
        for (j = 0; j < wc; j++) {
            if (j + x < 0  || j + x >= w) continue;
            cval32 = *(linec + j);
            if (transparent == 0 ||
                (transparent != 0 &&
                     ((cval32 & 0xffffff00) != (transpix & 0xffffff00)))) {
                val32 = *(lined + j + x);
                extractRGBValues(cval32, &rcval, &gcval, &bcval);
                extractRGBValues(val32, &rval, &gval, &bval);
                rval = (l_int32)((1. - fract) * rval + fract * rcval);
                gval = (l_int32)((1. - fract) * gval + fract * gcval);
                bval = (l_int32)((1. - fract) * bval + fract * bcval);
                composeRGBPixel(rval, gval, bval, &val32);
                *(lined + j + x) = val32;
            }
        }
    }

    pixDestroy(&pixc);
    return pixd;
}


/*
 *  pixBlendColorByChannel()
 *
 *      Input:  pixd (<optional>; either NULL or equal to pixs1 for in-place)
 *              pixs1 (blendee; depth > 1)
 *              pixs2 (blender, 32 bpp; typ. smaller in size than pixs1)
 *              x,y  (origin (UL corner) of pixs2 relative to
 *                    the origin of pixs1)
 *              rfract, gfract, bfract (blending fractions by channel)
 *              transparent (1 to use transparency; 0 otherwise)
 *              transpix (pixel color in pixs2 that is to be transparent)
 *      Return: pixd if OK; pixs1 on error
 *
 * Notes:
 *     (1) This generalizes pixBlendColor() in two ways:
 *         (a) The mixing fraction is specified per channel.
 *         (b) The mixing fraction may be < 0 or > 1, in which case,
 *             the min or max of two images are taken, respectively.
 *     (2) Specifically,
 *         for p = pixs1[i], c = pixs2[i], f = fract[i], i = 1, 2, 3:
 *             f < 0.0:          p --> min(p, c)
 *             0.0 <= f <= 1.0:  p --> (1 - f) * p + f * c
 *             f > 1.0:          p --> max(a, c)
 *         Special cases:
 *             f = 0:   p --> p
 *             f = 1:   p --> c
 *     (3) See usage notes in pixBlendColor()
 *     (4) pixBlendColor() would be equivalent to
 *           pixBlendColorChannel(..., fract, fract, fract, ...);
 *         at a small cost of efficiency.
 */
PIX *
pixBlendColorByChannel(PIX       *pixd,
                       PIX       *pixs1,
                       PIX       *pixs2,
                       l_int32    x,
                       l_int32    y,
                       l_float32  rfract,
                       l_float32  gfract,
                       l_float32  bfract,
                       l_int32    transparent,
                       l_uint32   transpix)
{
l_int32    i, j, wc, hc, w, h, wplc, wpld;
l_int32    rval, gval, bval, rcval, gcval, bcval;
l_uint32   cval32, val32;
l_uint32  *linec, *lined, *datac, *datad;
PIX       *pixc, *pixt1, *pixt2;

    PROCNAME("pixBlendColorByChannel");

    if (!pixs1)
        return (PIX *)ERROR_PTR("pixs1 not defined", procName, pixd);
    if (!pixs2)
        return (PIX *)ERROR_PTR("pixs2 not defined", procName, pixd);
    if (pixGetDepth(pixs1) == 1)
        return (PIX *)ERROR_PTR("pixs1 is 1 bpp", procName, pixd);
    if (pixGetDepth(pixs2) != 32)
        return (PIX *)ERROR_PTR("pixs2 not 32 bpp", procName, pixd);
    if (pixd == pixs1 && pixGetDepth(pixs1) != 32)
        return (PIX *)ERROR_PTR("inplace; pixs1 not 32 bpp", procName, pixd);
    if (pixd && (pixd != pixs1))
        return (PIX *)ERROR_PTR("pixd must be NULL or pixs1", procName, pixd);

        /* If pixd != NULL, we know that it is equal to pixs1 and
         * that pixs1 is 32 bpp rgb, so that an in-place operation
         * can be done.  Otherwise, remove colormap from pixs1 if
         * it exists and unpack to 32 bpp if necessary, to do the
         * blending on a new 32 bpp Pix. */
    if (!pixd) {
        pixt1 = pixRemoveColormap(pixs1, REMOVE_CMAP_TO_FULL_COLOR);
        if (pixGetDepth(pixt1) < 32)
            pixt2 = pixConvertTo32(pixt1);
        else
            pixt2 = pixClone(pixt1);
        pixd = pixCopy(NULL, pixt2);
        pixDestroy(&pixt1);
        pixDestroy(&pixt2);
    }

    pixGetDimensions(pixd, &w, &h, NULL);
    wpld = pixGetWpl(pixd);
    datad = pixGetData(pixd);
    pixc = pixClone(pixs2);
    pixGetDimensions(pixc, &wc, &hc, NULL);
    datac = pixGetData(pixc);
    wplc = pixGetWpl(pixc);

        /* Check limits for src1, in case clipping was not done */
    for (i = 0; i < hc; i++) {
        if (i + y < 0  || i + y >= h) continue;
        linec = datac + i * wplc;
        lined = datad + (i + y) * wpld;
        for (j = 0; j < wc; j++) {
            if (j + x < 0  || j + x >= w) continue;
            cval32 = *(linec + j);
            if (transparent == 0 ||
                (transparent != 0 &&
                     ((cval32 & 0xffffff00) != (transpix & 0xffffff00)))) {
                val32 = *(lined + j + x);
                extractRGBValues(cval32, &rcval, &gcval, &bcval);
                extractRGBValues(val32, &rval, &gval, &bval);
                rval = blendComponents(rval, rcval, rfract);
                gval = blendComponents(gval, gcval, gfract);
                bval = blendComponents(bval, bcval, bfract);
                composeRGBPixel(rval, gval, bval, &val32);
                *(lined + j + x) = val32;
            }
        }
    }

    pixDestroy(&pixc);
    return pixd;
}


static l_int32
blendComponents(l_int32    a,
                l_int32    b,
                l_float32  fract)
{
    if (fract < 0.)
        return ((a < b) ? a : b);
    if (fract > 1.)
        return ((a > b) ? a : b);
    return (l_int32)((1. - fract) * a + fract * b);
}


/*!
 *  pixBlendGrayAdapt()
 *
 *      Input:  pixd (<optional>; either NULL or equal to pixs1 for in-place)
 *              pixs1 (blendee; depth > 1)
 *              pixs2 (blender, 8 bpp; typ. smaller in size than pixs1)
 *              x,y  (origin (UL corner) of pixs2 relative to
 *                    the origin of pixs1; can be < 0)
 *              fract (blending fraction)
 *              shift (>= 0 but <= 128: shift of zero blend value from
 *                     median source; use -1 for default value; )
 *      Return: pixd if OK; pixs1 on error
 *
 *  Notes:
 *      (1) pixs2 must be 8 bpp, and have no colormap.
 *      (2) Clipping of pixs2 to pixs1 is done in the inner pixel loop.
 *      (3) If pixs1 has a colormap, it is removed.
 *      (4) If pixs1 has depth < 8, it is unpacked to generate a 8 bpp pix.
 *      (5) For inplace operation, call it this way:
 *            pixBlendGray(pixs1, pixs1, pixs2, ...)
 *          For generating a new pixd:
 *            pixd = pixBlendGray(NULL, pixs1, pixs2, ...)
 *          Only call in-place if pixs1 does not have a colormap;
 *          otherwise it is an error.
 *      (6) This does a blend with inverse.  Whereas in pixGlendGray(), the
 *          zero blend point is where the blendee pixel is 128, here
 *          the zero blend point is found adaptively, with respect to the
 *          median of the blendee region.  If the median is < 128,
 *          the zero blend point is found from
 *              median + shift.
 *          Otherwise, if the median >= 128, the zero blend point is
 *              median - shift.
 *          The purpose of shifting the zero blend point away from the
 *          median is to prevent a situation in pixBlendGray() where
 *          the median is 128 and the blender is not visible.
 *          The default value of shift is 64.
 *      (7) After processing pixs1, it is either 8 bpp or 32 bpp:
 *          - if 8 bpp, the fraction of pixs2 is mixed with pixs1.
 *          - if 32 bpp, each component of pixs1 is mixed with
 *            the same fraction of pixs2.
 *      (8) The darker the blender, the more it mixes with the blendee.
 *          A blender value of 0 has maximum mixing; a value of 255
 *          has no mixing and hence is transparent.
 */
PIX *
pixBlendGrayAdapt(PIX       *pixd,
                  PIX       *pixs1,
                  PIX       *pixs2,
                  l_int32    x,
                  l_int32    y,
                  l_float32  fract,
                  l_int32    shift)
{
l_int32    i, j, d, wc, hc, w, h, wplc, wpld, delta, overlap;
l_int32    rval, gval, bval, cval, dval, mval, median, pivot;
l_uint32   val32;
l_uint32  *linec, *lined, *datac, *datad;
l_float32  fmedian, factor;
BOX       *box, *boxt;
PIX       *pixc, *pixt1, *pixt2;

    PROCNAME("pixBlendGrayAdapt");

    if (!pixs1)
        return (PIX *)ERROR_PTR("pixs1 not defined", procName, pixd);
    if (!pixs2)
        return (PIX *)ERROR_PTR("pixs2 not defined", procName, pixd);
    if (pixGetDepth(pixs1) == 1)
        return (PIX *)ERROR_PTR("pixs1 is 1 bpp", procName, pixd);
    if (pixGetDepth(pixs2) != 8)
        return (PIX *)ERROR_PTR("pixs2 not 8 bpp", procName, pixd);
    if (pixGetColormap(pixs2))
        return (PIX *)ERROR_PTR("pixs2 has a colormap", procName, pixd);
    if (pixd == pixs1 && pixGetColormap(pixs1))
        return (PIX *)ERROR_PTR("can't do in-place with cmap", procName, pixd);
    if (pixd && (pixd != pixs1))
        return (PIX *)ERROR_PTR("pixd must be NULL or pixs1", procName, pixd);
    if (fract < 0.0 || fract > 1.0) {
        L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5", procName);
        fract = 0.5;
    }
    if (shift == -1) shift = 64;   /* default value */
    if (shift < 0 || shift > 127) {
        L_WARNING("invalid shift; setting to 64", procName);
        shift = 64;
    }

        /* Test for overlap */
    pixGetDimensions(pixs1, &w, &h, NULL);
    pixGetDimensions(pixs2, &wc, &hc, NULL);
    box = boxCreate(x, y, wc, hc);
    boxt = boxCreate(0, 0, w, h);
    boxIntersects(box, boxt, &overlap);
    boxDestroy(&boxt);
    if (!overlap) {
        boxDestroy(&box);
        return (PIX *)ERROR_PTR("no image overlap", procName, pixd);
    }

        /* If pixd != NULL, we know that it is equal to pixs1 and
         * that pixs1 does not have a colormap, so that an in-place operation
         * can be done.  Otherwise, remove colormap from pixs1 if
         * it exists and unpack to at least 8 bpp if necessary,
         * to do the blending on a new pix. */
    if (!pixd) {
        pixt1 = pixRemoveColormap(pixs1, REMOVE_CMAP_BASED_ON_SRC);
        if (pixGetDepth(pixt1) < 8)
            pixt2 = pixConvertTo8(pixt1, FALSE);
        else
            pixt2 = pixClone(pixt1);
        pixd = pixCopy(NULL, pixt2);
        pixDestroy(&pixt1);
        pixDestroy(&pixt2);
    }

        /* Get the median value in the region of blending */
    pixt1 = pixClipRectangle(pixd, box, NULL);
    pixt2 = pixConvertTo8(pixt1, 0);
    pixGetRankValueMasked(pixt2, NULL, 0, 0, 1, 0.5, &fmedian, NULL);
    median = (l_int32)(fmedian + 0.5);
    if (median < 128)
        pivot = median + shift;
    else
        pivot = median - shift;
/*    L_INFO_INT2("median = %d, pivot = %d", procName, median, pivot); */
    pixDestroy(&pixt1);
    pixDestroy(&pixt2);
    boxDestroy(&box);

        /* Process over src2; clip to src1. */
    d = pixGetDepth(pixd);
    wpld = pixGetWpl(pixd);
    datad = pixGetData(pixd);
    pixc = pixClone(pixs2);
    datac = pixGetData(pixc);
    wplc = pixGetWpl(pixc);
    for (i = 0; i < hc; i++) {
        if (i + y < 0  || i + y >= h) continue;
        linec = datac + i * wplc;
        lined = datad + (i + y) * wpld;
        switch (d)
        {
        case 8:
                /*
                 * For 8 bpp, the dest pix is shifted by an amount
                 * proportional to the distance from the pivot value,
                 * and to the darkness of src2.  In no situation will it
                 * pass the pivot value in intensity.
                 * The basic logic is:
                 *     d  -->  d + f * (np - d) * (1 - c)
                 * where np, d and c are normalized pixel values for
                 * the pivot, src1 and src2, respectively, with normalization
                 * to 255.
                 */
            for (j = 0; j < wc; j++) {
                if (j + x < 0  || j + x >= w) continue;
                dval = GET_DATA_BYTE(lined, j + x);
                cval = GET_DATA_BYTE(linec, j);
                delta = (pivot - dval) * (255 - cval) / 256;
                dval += (l_int32)(fract * delta + 0.5);
                SET_DATA_BYTE(lined, j + x, dval);
            }
            break;
        case 32:
                /*
                 * For 32 bpp, the dest pix is shifted by an amount
                 * proportional to the max component distance from the
                 * pivot value, and to the darkness of src2.  Each component
                 * is shifted by the same fraction, either up or down,
                 * depending on the shift direction (which is toward the
                 * pivot).   The basic logic for the red component is:
                 *     r  -->  r + f * (np - m) * (1 - c) * (r / m)
                 * where np, r, m and c are normalized pixel values for
                 * the pivot, the r component of src1, the max component
                 * of src1, and src2, respectively, again with normalization
                 * to 255.  Likewise for the green and blue components.
                 */
            for (j = 0; j < wc; j++) {
                if (j + x < 0  || j + x >= w) continue;
                cval = GET_DATA_BYTE(linec, j);
                val32 = *(lined + j + x);
                extractRGBValues(val32, &rval, &gval, &bval);
                mval = L_MAX(rval, gval);
                mval = L_MAX(mval, bval);
                mval = L_MAX(mval, 1);
                delta = (pivot - mval) * (255 - cval) / 256;
                factor = fract * delta / mval;
                rval += (l_int32)(factor * rval + 0.5);
                gval += (l_int32)(factor * gval + 0.5);
                bval += (l_int32)(factor * bval + 0.5);
                composeRGBPixel(rval, gval, bval, &val32);
                *(lined + j + x) = val32;
            }
            break;
        default:
            break;   /* shouldn't happen */
        }
    }

    pixDestroy(&pixc);
    return pixd;
}


/*!
 *  pixFadeWithGray()
 *
 *      Input:  pixs (colormapped or 8 bpp or 32 bpp)
 *              pixb (8 bpp blender)
 *              factor (multiplicative factor to apply to blender value)
 *              type (L_BLEND_TO_WHITE, L_BLEND_TO_BLACK)
 *      Return: pixd, or null on error
 *
 *  Notes:
 *      (1) This function combines two pix aligned to the UL corner; they
 *          need not be the same size.
 *      (2) Each pixel in pixb is multiplied by 'factor' divided by 255, and
 *          clipped to the range [0 ... 1].  This gives the fade fraction
 *          to be appied to pixs.  Fade either to white (L_BLEND_TO_WHITE)
 *          or to black (L_BLEND_TO_BLACK).
 */
PIX *
pixFadeWithGray(PIX       *pixs,
                PIX       *pixb,
                l_float32  factor,
                l_int32    type)
{
l_int32    i, j, w, h, d, wb, hb, db, wd, hd, wplb, wpld;
l_int32    valb, vald, nvald, rval, gval, bval, nrval, ngval, nbval;
l_float32  nfactor, fract;
l_uint32   val32, nval32;
l_uint32  *lined, *datad, *lineb, *datab;
PIX       *pixd;
PIXCMAP   *cmap;

    PROCNAME("pixFadeWithGray");

    if (!pixs)
        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
    if (!pixb)
        return (PIX *)ERROR_PTR("pixb not defined", procName, NULL);
    cmap = pixGetColormap(pixs);
    d = pixGetDepth(pixs);
    if (d < 8 && !cmap)
        return (PIX *)ERROR_PTR("pixs not cmapped and < 8 bpp", procName, NULL);
    pixGetDimensions(pixb, &wb, &hb, &db);
    if (db != 8)
        return (PIX *)ERROR_PTR("pixb not 8 bpp", procName, NULL);
    if (type != L_BLEND_TO_WHITE && type != L_BLEND_TO_BLACK)
        return (PIX *)ERROR_PTR("invalid fade type", procName, NULL);

    if (cmap)
        pixd = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
    else
        pixd = pixCopy(NULL, pixs);
    pixGetDimensions(pixd, &wd, &hd, &d);
    w = L_MIN(wb, wd);
    h = L_MIN(hb, hd);
    datad = pixGetData(pixd);
    wpld = pixGetWpl(pixd);
    datab = pixGetData(pixb);
    wplb = pixGetWpl(pixb);

        /*
         * The basic logic for this blending is, for each component p of pixs:
         *   fade-to-white:   p -->  p + (f * c) * (1 - p)
         *   fade-to-black:   p -->  p - (f * c) * p
         * with c being the 8 bpp blender pixel of pixb, and with both
         * p and c normalized to [0...1].
         */
    nfactor = factor / 255.;
    for (i = 0; i < h; i++) {
        lineb = datab + i * wplb;
        lined = datad + i * wpld;
        for (j = 0; j < w; j++) {
            valb = GET_DATA_BYTE(lineb, j);
            fract = nfactor * (l_float32)valb;
            fract = L_MIN(fract, 1.0);
            if (d == 8) {
                vald = GET_DATA_BYTE(lined, j);
                if (type == L_BLEND_TO_WHITE)
                    nvald = vald + (l_int32)(fract * (255. - (l_float32)vald));
                else  /* L_BLEND_TO_BLACK */
                    nvald = vald - (l_int32)(fract * (l_float32)vald);
                SET_DATA_BYTE(lined, j, nvald);
            }
            else {  /* d == 32 */
                val32 = lined[j];
                extractRGBValues(val32, &rval, &gval, &bval);
                if (type == L_BLEND_TO_WHITE) {
                    nrval = rval + (l_int32)(fract * (255. - (l_float32)rval));
                    ngval = gval + (l_int32)(fract * (255. - (l_float32)gval));
                    nbval = bval + (l_int32)(fract * (255. - (l_float32)bval));
                }
                else {
                    nrval = rval - (l_int32)(fract * (l_float32)rval);
                    ngval = gval - (l_int32)(fract * (l_float32)gval);
                    nbval = bval - (l_int32)(fract * (l_float32)bval);
                }
                composeRGBPixel(nrval, ngval, nbval, &nval32);
                lined[j] = nval32;
            }
        }
    }

    return pixd;
}


/*
 *  pixBlendHardLight()
 *
 *      Input:  pixd (<optional>; either NULL or equal to pixs1 for in-place)
 *              pixs1 (blendee; depth > 1, may be cmapped)
 *              pixs2 (blender, 8 or 32 bpp; may be colormapped;
 *                     typ. smaller in size than pixs1)
 *              x,y  (origin (UL corner) of pixs2 relative to
 *                    the origin of pixs1)
 *              fract (blending fraction, or 'opacity factor')
 *      Return: pixd if OK; pixs1 on error
 *
 *  Notes:
 *      (1) pixs2 must be 8 or 32 bpp; either may have a colormap.
 *      (2) Clipping of pixs2 to pixs1 is done in the inner pixel loop.
 *      (3) Only call in-place if pixs1 is not colormapped.
 *      (4) If pixs1 has a colormap, it is removed to generate either an
 *          8 or 32 bpp pix, depending on the colormap.
 *      (5) For inplace operation, call it this way:
 *            pixBlendHardLight(pixs1, pixs1, pixs2, ...)
 *      (6) For generating a new pixd:
 *            pixd = pixBlendHardLight(NULL, pixs1, pixs2, ...)
 *      (7) This is a generalization of the usual hard light blending,
 *          where fract == 1.0.
 *      (8) "Overlay" blending is the same as hard light blending, with
 *          fract == 1.0, except that the components are switched
 *          in the test.  (Note that the result is symmetric in the
 *          two components.)
 *      (9) See, e.g.:
 *           http://www.pegtop.net/delphi/articles/blendmodes/hardlight.htm
 *           http://www.digitalartform.com/imageArithmetic.htm
 *      (10) This function was built by Paco Galanes.
 */
PIX *
pixBlendHardLight(PIX       *pixd,
                  PIX       *pixs1,
                  PIX       *pixs2,
                  l_int32    x,
                  l_int32    y,
                  l_float32  fract)
{
l_int32    i, j, w, h, d, wc, hc, dc, wplc, wpld;
l_int32    cval, dval, rcval, gcval, bcval, rdval, gdval, bdval;
l_uint32   cval32, dval32;
l_uint32  *linec, *lined, *datac, *datad;
PIX       *pixc, *pixt;

    PROCNAME("pixBlendHardLight");

    if (!pixs1)
        return (PIX *)ERROR_PTR("pixs1 not defined", procName, pixd);
    if (!pixs2)
        return (PIX *)ERROR_PTR("pixs2 not defined", procName, pixd);
    pixGetDimensions(pixs1, &w, &h, &d);
    pixGetDimensions(pixs2, &wc, &hc, &dc);
    if (d == 1)
        return (PIX *)ERROR_PTR("pixs1 is 1 bpp", procName, pixd);
    if (dc != 8 && dc != 32)
        return (PIX *)ERROR_PTR("pixs2 not 8 or 32 bpp", procName, pixd);
    if (pixd && (pixd != pixs1))
        return (PIX *)ERROR_PTR("inplace and pixd != pixs1", procName, pixd);
    if (pixd == pixs1 && pixGetColormap(pixs1))
        return (PIX *)ERROR_PTR("inplace and pixs1 cmapped", procName, pixd);
    if (pixd && d != 8 && d != 32)
        return (PIX *)ERROR_PTR("inplace and not 8 or 32 bpp", procName, pixd);

    if (fract < 0.0 || fract > 1.0) {
        L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5", procName);
        fract = 0.5;
    }

        /* If pixs2 has a colormap, remove it */
    if (pixGetColormap(pixs2))
        pixc = pixRemoveColormap(pixs2, REMOVE_CMAP_BASED_ON_SRC);
    else
        pixc = pixClone(pixs2);
    dc = pixGetDepth(pixc);

        /* There are 4 cases:
         *    * pixs1 has or doesn't have a colormap
         *    * pixc is either 8 or 32 bpp
         * In all situations, if pixs has a colormap it must be removed,
         * and pixd must have a depth that is equal to or greater than pixc. */
    if (dc == 32) {
        if (pixGetColormap(pixs1))  /* pixd == NULL */
            pixd = pixRemoveColormap(pixs1, REMOVE_CMAP_TO_FULL_COLOR);
        else {
            if (!pixd)
                pixd = pixConvertTo32(pixs1);
            else {
                pixt = pixConvertTo32(pixs1);
                pixCopy(pixd, pixt);
                pixDestroy(&pixt);
            }
        }
        d = 32;
    } else {  /* dc == 8 */
        if (pixGetColormap(pixs1))   /* pixd == NULL */
            pixd = pixRemoveColormap(pixs1, REMOVE_CMAP_BASED_ON_SRC);
        else
            pixd = pixCopy(pixd, pixs1);
        d = pixGetDepth(pixd);
    }

    if (!(d == 8 && dc == 8) &&   /* 3 cases only */
        !(d == 32 && dc == 8) &&
        !(d == 32 && dc == 32)) {
        pixDestroy(&pixc);
        return (PIX *)ERROR_PTR("bad! -- invalid depth combo!", procName, pixd);
    }

    wpld = pixGetWpl(pixd);
    datad = pixGetData(pixd);
    datac = pixGetData(pixc);
    wplc = pixGetWpl(pixc);
    for (i = 0; i < hc; i++) {
        if (i + y < 0  || i + y >= h) continue;
        linec = datac + i * wplc;
        lined = datad + (i + y) * wpld;
        for (j = 0; j < wc; j++) {
            if (j + x < 0  || j + x >= w) continue;
            if (d == 8 && dc == 8) {
                dval = GET_DATA_BYTE(lined, x + j);
                cval = GET_DATA_BYTE(linec, j);
                dval = blendHardLightComponents(dval, cval, fract);
                SET_DATA_BYTE(lined, x + j, dval);
            } else if (d == 32 && dc == 8) {
                dval32 = *(lined + x + j);
                extractRGBValues(dval32, &rdval, &gdval, &bdval);
                cval = GET_DATA_BYTE(linec, j);
                rdval = blendHardLightComponents(rdval, cval, fract);
                gdval = blendHardLightComponents(gdval, cval, fract);
                bdval = blendHardLightComponents(bdval, cval, fract);
                composeRGBPixel(rdval, gdval, bdval, &dval32);
                *(lined + x + j) = dval32;
            } else if (d == 32 && dc == 32) {
                dval32 = *(lined + x + j);
                extractRGBValues(dval32, &rdval, &gdval, &bdval);
                cval32 = *(linec + j);
                extractRGBValues(cval32, &rcval, &gcval, &bcval);
                rdval = blendHardLightComponents(rdval, rcval, fract);
                gdval = blendHardLightComponents(gdval, gcval, fract);
                bdval = blendHardLightComponents(bdval, bcval, fract);
                composeRGBPixel(rdval, gdval, bdval, &dval32);
                *(lined + x + j) = dval32;
            }
        }
    }

    pixDestroy(&pixc);
    return pixd;
}


/*
 *  blendHardLightComponents()
 *      Input:  a (8 bpp blendee component)
 *              b (8 bpp blender component)
 *              fract (fraction of blending; use 1.0 for usual definition)
 *      Return: blended 8 bpp component
 *
 *  Notes:
 *
 *    The basic logic for this blending is:
 *      b < 0.5:
 *          a --> 2 * a * (0.5 - f * (0.5 - b))
 *      b >= 0.5:
 *          a --> 1 - 2 * (1 - a) * (1 - (0.5 - f * (0.5 - b)))
 *
 *    In the limit that f == 1 (standard hardlight blending):
 *      b < 0.5:   a --> 2 * a * b
 *                     or
 *                 a --> a - a * (1 - 2 * b)
 *      b >= 0.5:  a --> 1 - 2 * (1 - a) * (1 - b)
 *                     or
 *                 a --> a + (1 - a) * (2 * b - 1)
 *
 *    You can see that for standard hardlight blending:
 *      b < 0.5:   a is pushed linearly with b down to 0
 *      b >= 0.5:  a is pushed linearly with b up to 1
 *    a is unchanged if b = 0.5
 *
 *    Our opacity factor f reduces the deviation of b from 0.5:
 *      f == 0:  b -->  0.5, so no blending occurs
 *      f == 1:  b -->  b, so we get full conventional blending
 *
 *    There is a variant of hardlight blending called "softlight" blending:
 *    (e.g., http://jswidget.com/blog/tag/hard-light/)
 *      b < 0.5:
 *          a --> a - a * (0.5 - b) * (1 - Abs(2 * a - 1))
 *      b >= 0.5:
 *          a --> a + (1 - a) * (b - 0.5) * (1 - Abs(2 * a - 1))
 *    which limits the amount that 'a' can be moved to a maximum of
 *    halfway toward 0 or 1, and further reduces it as 'a' moves
 *    away from 0.5.
 *    As you can see, there are a nearly infinite number of different
 *    blending formulas that can be conjured up.
 */
static l_int32 blendHardLightComponents(l_int32    a,
                                        l_int32    b,
                                        l_float32  fract)
{
    if (b < 0x80) {
        b = 0x80 - (l_int32)(fract * (0x80 - b));
        return (a * b) >> 7;
    } else {
        b = 0x80 + (l_int32)(fract * (b - 0x80));
        return  0xff - (((0xff - b) * (0xff - a)) >> 7);
    }
}


/*-------------------------------------------------------------*
 *               Blending two colormapped images               *
 *-------------------------------------------------------------*/
/*!
 *  pixBlendCmap()
 *
 *      Input:  pixs (2, 4 or 8 bpp, with colormap)
 *              pixb (colormapped blender)
 *              x, y (UL corner of blender relative to pixs)
 *              sindex (colormap index of pixels in pixs to be changed)
 *      Return: 0 if OK, 1 on error
 *
 *  Note:
 *      (1) This function combines two colormaps, and replaces the pixels
 *          in pixs that have a specified color value with those in pixb.
 *      (2) sindex must be in the existing colormap; otherwise an
 *          error is returned.  In use, sindex will typically be the index
 *          for white (255, 255, 255).
 *      (3) Blender colors that already exist in the colormap are used;
 *          others are added.  If any blender colors cannot be
 *          stored in the colormap, an error is returned.
 *      (4) In the implementation, a mapping is generated from each
 *          original blender colormap index to the corresponding index
 *          in the expanded colormap for pixs.  Then for each pixel in
 *          pixs with value sindex, and which is covered by a blender pixel,
 *          the new index corresponding to the blender pixel is substituted
 *          for sindex.
 */
l_int32
pixBlendCmap(PIX     *pixs,
             PIX     *pixb,
             l_int32  x,
             l_int32  y,
             l_int32  sindex)
{
l_int32    rval, gval, bval;
l_int32    i, j, w, h, d, ncb, wb, hb, wpls;
l_int32    index, val, nadded;
l_int32    lut[256];
l_uint32   pval;
l_uint32  *lines, *datas;
PIXCMAP   *cmaps, *cmapb, *cmapsc;

    PROCNAME("pixBlendCmap");

    if (!pixs)
        return ERROR_INT("pixs not defined", procName, 1);
    if (!pixb)
        return ERROR_INT("pixb not defined", procName, 1);
    if ((cmaps = pixGetColormap(pixs)) == NULL)
        return ERROR_INT("no colormap in pixs", procName, 1);
    if ((cmapb = pixGetColormap(pixb)) == NULL)
        return ERROR_INT("no colormap in pixb", procName, 1);
    ncb = pixcmapGetCount(cmapb);

        /* Make a copy of cmaps; we'll add to this if necessary
         * and substitute at the end if we found there was enough room
         * to hold all the new colors. */
    cmapsc = pixcmapCopy(cmaps);

    pixGetDimensions(pixs, &w, &h, &d);
    if (d != 2 && d != 4 && d != 8)
        return ERROR_INT("depth not in {2,4,8}", procName, 1);

        /* Add new colors if necessary; get mapping array between
         * cmaps and cmapb. */
    for (i = 0, nadded = 0; i < ncb; i++) {
        pixcmapGetColor(cmapb, i, &rval, &gval, &bval);
        if (pixcmapGetIndex(cmapsc, rval, gval, bval, &index)) { /* not found */
            if (pixcmapAddColor(cmapsc, rval, gval, bval)) {
                pixcmapDestroy(&cmapsc);
                return ERROR_INT("not enough room in cmaps", procName, 1);
            }
            lut[i] = pixcmapGetCount(cmapsc) - 1;
            nadded++;
        }
        else
            lut[i] = index;
    }

        /* Replace cmaps if colors have been added. */
    if (nadded == 0)
        pixcmapDestroy(&cmapsc);
    else
        pixSetColormap(pixs, cmapsc);

        /* Replace each pixel value sindex by mapped colormap index when
         * a blender pixel in pixbc overlays it. */
    datas = pixGetData(pixs);
    wpls = pixGetWpl(pixs);
    pixGetDimensions(pixb, &wb, &hb, NULL);
    for (i = 0; i < hb; i++) {
        if (i + y < 0  || i + y >= h) continue;
        lines = datas + (y + i) * wpls;
        for (j = 0; j < wb; j++) {
            if (j + x < 0  || j + x >= w) continue;
            switch (d) {
            case 2:
                val = GET_DATA_DIBIT(lines, x + j);
                if (val == sindex) {
                    pixGetPixel(pixb, j, i, &pval);
                    SET_DATA_DIBIT(lines, x + j, lut[pval]);
                }
                break;
            case 4:
                val = GET_DATA_QBIT(lines, x + j);
                if (val == sindex) {
                    pixGetPixel(pixb, j, i, &pval);
                    SET_DATA_QBIT(lines, x + j, lut[pval]);
                }
                break;
            case 8:
                val = GET_DATA_BYTE(lines, x + j);
                if (val == sindex) {
                    pixGetPixel(pixb, j, i, &pval);
                    SET_DATA_BYTE(lines, x + j, lut[pval]);
                }
                break;
            default:
                return ERROR_INT("depth not in {2,4,8}", procName, 1);
            }
        }
    }

    return 0;
}


/*---------------------------------------------------------------------*
 *                  Blending two images using a third                  *
 *---------------------------------------------------------------------*/
/*!
 *  pixBlendWithGrayMask()
 *
 *      Input:  pixs1 (8 bpp gray, rgb or colormapped)
 *              pixs2 (8 bpp gray, rgb or colormapped)
 *              pixg (8 bpp gray, for transparency of pixs2; can be null)
 *              x, y (UL corner of pixg with respect to pixs1)
 *      Return: pixd (blended image), or null on error
 *
 *  Notes:
 *      (1) The result is 8 bpp grayscale if both pixs1 and pixs2 are
 *          8 bpp gray.  Otherwise, the result is 32 bpp rgb.
 *      (2) pixg is an 8 bpp transparency image, where 0 is transparent
 *          and 255 is opaque.  It determines the transparency of pixs2
 *          when applied over pixs1.  It can be null if pixs2 is rgb,
 *          in which case we use the alpha component of pixs2.
 *      (3) If pixg exists, both it and pixs2 must be the same size,
 *          and they are applied with both their UL corners at the
 *          location (x, y) in pixs1.
 *      (4) The pixels in pixd are a combination of those in pixs1
 *          and pixs2, where the amount from pixs2 is proportional to
 *          the value of the pixel (p) in pixg, and the amount from pixs1
 *          is proportional to (255 - p).  Thus pixg is a transparency
 *          image (usually called an alpha blender) where each pixel
 *          can be associated with a pixel in pixs2, and determines
 *          the amount of the pixs2 pixel in the final result.
 *          For example, if pixg is all 0, pixs2 is transparent and
 *          the result in pixd is simply pixs1.
 *      (5) A typical use is for the pixs2/pixg combination to be
 *          a small watermark that is applied to pixs1.
 */
PIX *
pixBlendWithGrayMask(PIX     *pixs1,
                     PIX     *pixs2,
                     PIX     *pixg,
                     l_int32  x,
                     l_int32  y)
{
l_int32    w1, h1, d1, w2, h2, d2, wg, hg, wmin, hmin, wpld, wpls, wplg;
l_int32    i, j, val, dval, sval;
l_int32    drval, dgval, dbval, srval, sgval, sbval;
l_uint32   dval32, sval32;
l_uint32  *datad, *datas, *datag, *lined, *lines, *lineg;
l_float32  fract;
PIX       *pixr1, *pixr2, *pix1, *pix2, *pixalpha, *pixd;

    PROCNAME("pixBlendWithGrayMask");

    if (!pixs1)
        return (PIX *)ERROR_PTR("pixs1 not defined", procName, NULL);
    if (!pixs2)
        return (PIX *)ERROR_PTR("pixs2 not defined", procName, NULL);
    pixGetDimensions(pixs1, &w1, &h1, &d1);
    pixGetDimensions(pixs2, &w2, &h2, &d2);
    if (d1 == 1 || d2 == 1)
        return (PIX *)ERROR_PTR("pixs1 or pixs2 is 1 bpp", procName, NULL);
    if (pixg) {
        if (pixGetDepth(pixg) != 8)
            return (PIX *)ERROR_PTR("pixg not 8 bpp", procName, NULL);
        pixGetDimensions(pixg, &wg, &hg, NULL);
        wmin = L_MIN(w2, wg);
        hmin = L_MIN(h2, hg);
        pixalpha = pixClone(pixg);
    }
    else {  /* use the alpha component of pixs2 */
        if (d2 != 32)
            return (PIX *)ERROR_PTR("no alpha; pixs2 not rgba", procName, NULL);
        wmin = w2;
        hmin = h2;
        pixalpha = pixGetRGBComponent(pixs2, L_ALPHA_CHANNEL);
    }

        /* Remove colormaps if they exist */
    if (pixGetColormap(pixs1))
        pixr1 = pixRemoveColormap(pixs1, REMOVE_CMAP_BASED_ON_SRC);
    else
        pixr1 = pixClone(pixs1);
    if (pixGetColormap(pixs2))
        pixr2 = pixRemoveColormap(pixs2, REMOVE_CMAP_BASED_ON_SRC);
    else
        pixr2 = pixClone(pixs2);

        /* Regularize to the same depth if necessary */
    d1 = pixGetDepth(pixr1);
    d2 = pixGetDepth(pixr2);
    if (d1 == 32) {  /* convert d2 to rgb if necessary */
        pix1 = pixClone(pixr1);
        if (d2 != 32)
            pix2 = pixConvertTo32(pixr2);
        else
            pix2 = pixClone(pixr2);
    }
    else if (d2 == 32) {   /* and d1 != 32; convert to 32 */
        pix2 = pixClone(pixr2);
        pix1 = pixConvertTo32(pixr1);
    }
    else {  /* both are 8 bpp or less */
        pix1 = pixConvertTo8(pixr1, FALSE);
        pix2 = pixConvertTo8(pixr2, FALSE);
    }
    pixDestroy(&pixr1);
    pixDestroy(&pixr2);

        /* Sanity check */
    d1 = pixGetDepth(pix1);
    d2 = pixGetDepth(pix2);
    if (d1 != d2) {
        pixDestroy(&pix1);
        pixDestroy(&pix2);
        return (PIX *)ERROR_PTR("depths not regularized! bad!", procName, NULL);
    }

        /* Start off with a copy of pix1.  Then only change
         * pixels that will be blended with pix2. */
    pixd = pixCopy(NULL, pix1);
    pixDestroy(&pix1);

        /*
         * Let the normalized pixel value of pixg be f = pixval / 255,
         * and the pixel values of pixs1 and pixs2 be p1 and p2, rsp.
         * Then the blended value is:
         *      p = (1.0 - f) * p1 + f * p2
         * Blending is done component-wise if rgb.
         *
         * Scan over pixs2 and pixg, doing clipping on pixs1 where necessary.
         */
    datad = pixGetData(pixd);
    datas = pixGetData(pix2);
    datag = pixGetData(pixalpha);
    wpld = pixGetWpl(pixd);
    wpls = pixGetWpl(pix2);
    wplg = pixGetWpl(pixalpha);
    for (i = 0; i < hmin; i++) {
        if (i + y < 0  || i + y >= h1) continue;
        lined = datad + (i + y) * wpld;
        lines = datas + i * wpls;
        lineg = datag + i * wplg;
        for (j = 0; j < wmin; j++) {
            if (j + x < 0  || j + x >= w1) continue;
            val = GET_DATA_BYTE(lineg, j);
            if (val == 0) continue;  /* pix2 is transparent */
            fract = (l_float32)val / 255.;
            switch (d1)
            {
            case 8:
                dval = GET_DATA_BYTE(lined, j + x);
                sval = GET_DATA_BYTE(lines, j);
                dval = (l_int32)((1.0 - fract) * dval + fract * sval);
                SET_DATA_BYTE(lined, j + x, dval);
                break;
            case 32:
                dval32 = *(lined + j + x);
                sval32 = *(lines + j);
                extractRGBValues(dval32, &drval, &dgval, &dbval);
                extractRGBValues(sval32, &srval, &sgval, &sbval);
                drval = (l_int32)((1.0 - fract) * drval + fract * srval);
                dgval = (l_int32)((1.0 - fract) * dgval + fract * sgval);
                dbval = (l_int32)((1.0 - fract) * dbval + fract * sbval);
                composeRGBPixel(drval, dgval, dbval, &dval32);
                *(lined + j + x) = dval32;
                break;
            default:
                return (PIX *)ERROR_PTR("impossible error", procName, NULL);
            }
        }
    }

    pixDestroy(&pixalpha);
    pixDestroy(&pix2);
    return pixd;
}


/*---------------------------------------------------------------------*
 *                Blending background to a specific color              *
 *---------------------------------------------------------------------*/
/*!
 *  pixBlendBackgroundToColor()
 *
 *      Input:  pixd (can be NULL or pixs)
 *              pixs (32 bpp rgb)
 *              box (region for blending; can be NULL))
 *              color (32 bit color in 0xrrggbb00 format)
 *              gamma, minval, maxval (args for grayscale TRC mapping)
 *      Return: pixd always
 *
 *  Notes:
 *      (1) This in effect replaces light background pixels in pixs
 *          by the input color.  It does it by alpha blending so that
 *          there are no visible artifacts from hard cutoffs.
 *      (2) If pixd == pixs, this is done in-place.
 *      (3) If box == NULL, this is performed on all of pixs.
 *      (4) The alpha component for blending is derived from pixs,
 *          by converting to grayscale and enhancing with a TRC.
 *      (5) The last three arguments specify the TRC operation.
 *          Suggested values are: @gamma = 0.3, @minval = 50, @maxval = 200.
 *          To skip the TRC, use @gamma == 1, @minval = 0, @maxval = 255.
 *          See pixGammaTRC() for details.
 */
PIX *
pixBlendBackgroundToColor(PIX       *pixd,
                          PIX       *pixs,
                          BOX       *box,
                          l_uint32   color,
                          l_float32  gamma,
                          l_int32    minval,
                          l_int32    maxval)
{
l_int32  x, y, w, h;
BOX     *boxt;
PIX     *pixt, *pixc, *pixr, *pixg;

    PROCNAME("pixBlendBackgroundToColor");

    if (!pixs)
        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
    if (pixGetDepth(pixs) != 32)
        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, pixd);
    if (pixd && (pixd != pixs))
        return (PIX *)ERROR_PTR("pixd neither null nor pixs", procName, pixd);

        /* Extract the (optionally cropped) region, pixr, and generate
         * an identically sized pixc with the uniform color. */
    if (!pixd)
        pixd = pixCopy(NULL, pixs);
    if (box) {
        pixr = pixClipRectangle(pixd, box, &boxt);
        boxGetGeometry(boxt, &x, &y, &w, &h);
        pixc = pixCreate(w, h, 32);
        boxDestroy(&boxt);
    }
    else {
        pixc = pixCreateTemplate(pixs);
        pixr = pixClone(pixd);
    }
    pixSetAllArbitrary(pixc, color);

        /* Set up the alpha channel */
    pixg = pixConvertTo8(pixr, 0);
    pixGammaTRC(pixg, pixg, gamma, minval, maxval);
    pixSetRGBComponent(pixc, pixg, L_ALPHA_CHANNEL);

        /* Blend and replace in pixd */
    pixt = pixBlendWithGrayMask(pixr, pixc, NULL, 0, 0);
    if (box) {
        pixRasterop(pixd, x, y, w, h, PIX_SRC, pixt, 0, 0);
        pixDestroy(&pixt);
    }
    else
        pixTransferAllData(pixd, &pixt, 0, 0);

    pixDestroy(&pixc);
    pixDestroy(&pixr);
    pixDestroy(&pixg);
    return pixd;
}


/*---------------------------------------------------------------------*
 *                     Multiplying by a specific color                 *
 *---------------------------------------------------------------------*/
/*!
 *  pixMultiplyByColor()
 *
 *      Input:  pixd (can be NULL or pixs)
 *              pixs (32 bpp rgb)
 *              box (region for filtering; can be NULL))
 *              color (32 bit color in 0xrrggbb00 format)
 *      Return: pixd always
 *
 *  Notes:
 *      (1) This filters all pixels in the specified region by
 *          multiplying each component by the input color.
 *          This leaves black invariant and transforms white to the
 *          input color.
 *      (2) If pixd == pixs, this is done in-place.
 *      (3) If box == NULL, this is performed on all of pixs.
 */
PIX *
pixMultiplyByColor(PIX       *pixd,
                   PIX       *pixs,
                   BOX       *box,
                   l_uint32   color)
{
l_int32    i, j, bx, by, w, h, wpl;
l_int32    red, green, blue, rval, gval, bval, nrval, ngval, nbval;
l_float32  frval, fgval, fbval;
l_uint32  *data, *line;
PIX       *pixt;

    PROCNAME("pixMultiplyByColor");

    if (!pixs)
        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
    if (pixGetDepth(pixs) != 32)
        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, pixd);
    if (pixd && (pixd != pixs))
        return (PIX *)ERROR_PTR("pixd neither null nor pixs", procName, pixd);

    if (!pixd)
        pixd = pixCopy(NULL, pixs);
    if (box) {
        boxGetGeometry(box, &bx, &by, NULL, NULL);
        pixt = pixClipRectangle(pixd, box, NULL);
    }
    else
        pixt = pixClone(pixd);

        /* Multiply each pixel in pixt by the color */
    extractRGBValues(color, &red, &green, &blue);
    frval = (1. / 255.) * red;
    fgval = (1. / 255.) * green;
    fbval = (1. / 255.) * blue;
    data = pixGetData(pixt);
    wpl = pixGetWpl(pixt);
    pixGetDimensions(pixt, &w, &h, NULL);
    for (i = 0; i < h; i++) {
        line = data + i * wpl;
        for (j = 0; j < w; j++) {
            extractRGBValues(line[j], &rval, &gval, &bval);
            nrval = (l_int32)(frval * rval + 0.5);
            ngval = (l_int32)(fgval * gval + 0.5);
            nbval = (l_int32)(fbval * bval + 0.5);
            composeRGBPixel(nrval, ngval, nbval, line + j);
        }
    }

        /* Replace */
    if (box)
        pixRasterop(pixd, bx, by, w, h, PIX_SRC, pixt, 0, 0);
    pixDestroy(&pixt);
    return pixd;
}


/*---------------------------------------------------------------------*
 *       Rendering with alpha blending over a uniform background       *
 *---------------------------------------------------------------------*/
/*!
 *  pixAlphaBlendUniform()
 *
 *      Input:  pixs (32 bpp rgba, with alpha)
 *              color (32 bit color in 0xrrggbb00 format)
 *      Return: pixd (pixs blended over uniform color @color), or null on error
 *
 *  Notes:
 *      (1) This is a convenience function that renders 32 bpp RGBA images
 *          (with an alpha channel) over a uniform background of
 *          value @color.  To render over a white background,
 *          use @color = 0xffffff00.
 *      (2) If pixs does not have an alpha channel, the values of the 4th
 *          byte would typically be 0, which represents full transparency.
 *          Applying this function to such an image results in an image
 *          with only the uniformly colored background.
 */
PIX *
pixAlphaBlendUniform(PIX      *pixs,
                     l_uint32  color)
{
PIX  *pixt, *pixd;

    PROCNAME("pixAlphaBlendUniform");

    if (!pixs)
        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
    if (pixGetDepth(pixs) != 32)
        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);

    pixt = pixCreateTemplate(pixs);
    pixSetAllArbitrary(pixt, color);
    pixd = pixBlendWithGrayMask(pixt, pixs, NULL, 0, 0);

    pixDestroy(&pixt);
    return pixd;
}


/*---------------------------------------------------------------------*
 *    Setting a transparent alpha component over a white background    *
 *---------------------------------------------------------------------*/
/*!
 *  pixSetAlphaOverWhite()
 *
 *      Input:  pixs (colormapped or 32 bpp rgb; no alpha)
 *      Return: pixd (new pix with meaningful alpha component),
 *                   or null on error
 *
 *  Notes:
 *      (1) The generated alpha component is transparent over white
 *          (background) pixels in pixs, and quickly grades to opaque
 *          away from the transparent parts.  This is a cheap and
 *          dirty alpha generator.
 *      (2) The alpha component bits in pixs are ignored.
 */
PIX *
pixSetAlphaOverWhite(PIX  *pixs)
{
PIX  *pixd, *pixt1, *pixt2, *pixt3, *pixt4;

    PROCNAME("pixSetAlphaOverWhite");

    if (!pixs)
        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
    if (!(pixGetDepth(pixs) == 32 || pixGetColormap(pixs)))
        return (PIX *)ERROR_PTR("pixs not 32 bpp or cmapped", procName, NULL);

        /* Remove colormap if it exists */
    if (pixGetColormap(pixs))
        pixd = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR);
    else
        pixd = pixCopy(NULL, pixs);

        /* Generate a 1 bpp image where a '1' represents a white
         * pixel in pixd */
    pixt1 = pixInvert(NULL, pixd);  /* send white to 0 */
    pixt2 = pixConvertRGBToGrayMinMax(pixt1, L_CHOOSE_MAX);
    pixt3 = pixThresholdToBinary(pixt2, 1);  /* select only white pixels */
    pixInvert(pixt3, pixt3);  /* make white pixels FG */

        /* Generate the alpha component: transparent over white pixels
         * and moving to opaque 2 pixels away from the nearest white pixel */
    pixt4 = pixDistanceFunction(pixt3, 8, 8, L_BOUNDARY_FG);
    pixMultConstantGray(pixt4, 128.0);
    pixSetRGBComponent(pixd, pixt4, L_ALPHA_CHANNEL);

    pixDestroy(&pixt1);
    pixDestroy(&pixt2);
    pixDestroy(&pixt3);
    pixDestroy(&pixt4);
    return pixd;
}
