/*====================================================================* - 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 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()
 *
 *      Adding an alpha layer for blending
 *           PIX             *pixAddAlphaToBlend()
 *
 *      Setting a transparent alpha component over a white background
 *           PIX             *pixSetAlphaOverWhite()
 *
 *      Fading from the edge
 *           l_int32          pixLinearEdgeFade()
 *
 *  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.
 * 
*/ #ifdef HAVE_CONFIG_H #include #endif /* HAVE_CONFIG_H */ #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 * *-------------------------------------------------------------*/ /*! * \brief pixBlend() * * \param[in] pixs1 blendee * \param[in] pixs2 blender; typ. smaller * \param[in] x,y origin [UL corner] of pixs2 relative to * the origin of pixs1; can be < 0 * \param[in] 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\n", procName); pixDestroy(&pixt); 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; } /*! * \brief pixBlendMask() * * \param[in] pixd [optional]; either NULL or equal to pixs1 for in-place * \param[in] pixs1 blendee, depth > 1 * \param[in] pixs2 blender, 1 bpp; typ. smaller in size than pixs1 * \param[in] x,y origin [UL corner] of pixs2 relative to * the origin of pixs1; can be < 0 * \param[in] fract blending fraction * \param[in] type L_BLEND_WITH_INVERSE, L_BLEND_TO_WHITE, * L_BLEND_TO_BLACK * \return pixd if OK; null on error * *
 * Notes:
 *      (1) Clipping of pixs2 to pixs1 is done in the inner pixel loop.
 *      (2) If pixs1 has a colormap, it is removed.
 *      (3) For inplace operation (pixs1 not cmapped), call it this way:
 *            pixBlendMask(pixs1, pixs1, pixs2, ...)
 *      (4) For generating a new pixd:
 *            pixd = pixBlendMask(NULL, pixs1, pixs2, ...)
 *      (5) Only call in-place if pixs1 does not have a colormap.
 *      (6) Invalid %fract defaults to 0.5 with a warning.
 *          Invalid %type defaults to L_BLEND_WITH_INVERSE with a warning.
 * 
*/ 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, *pix1, *pix2; PROCNAME("pixBlendMask"); if (!pixs1) return (PIX *)ERROR_PTR("pixs1 not defined", procName, NULL); if (!pixs2) return (PIX *)ERROR_PTR("pixs2 not defined", procName, NULL); if (pixGetDepth(pixs1) == 1) return (PIX *)ERROR_PTR("pixs1 is 1 bpp", procName, NULL); if (pixGetDepth(pixs2) != 1) return (PIX *)ERROR_PTR("pixs2 not 1 bpp", procName, NULL); if (pixd == pixs1 && pixGetColormap(pixs1)) return (PIX *)ERROR_PTR("inplace; pixs1 has colormap", procName, NULL); if (pixd && (pixd != pixs1)) return (PIX *)ERROR_PTR("pixd must be NULL or pixs1", procName, NULL); if (fract < 0.0 || fract > 1.0) { L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5\n", 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\n", 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) { pix1 = pixRemoveColormap(pixs1, REMOVE_CMAP_BASED_ON_SRC); if (pixGetDepth(pix1) < 8) pix2 = pixConvertTo8(pix1, FALSE); else pix2 = pixClone(pix1); pixd = pixCopy(NULL, pix2); pixDestroy(&pix1); pixDestroy(&pix2); } 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\n", 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\n", 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\n", procName); } } } } break; default: L_WARNING("invalid binary mask blend type\n", procName); break; } pixDestroy(&pixc); return pixd; } /*! * \brief pixBlendGray() * * \param[in] pixd [optional] either equal to pixs1 for in-place, * or NULL * \param[in] pixs1 blendee, depth > 1 * \param[in] pixs2 blender, any depth; typically, the area of * pixs2 is smaller than pixs1 * \param[in] x,y origin [UL corner] of pixs2 relative to * the origin of pixs1; can be < 0 * \param[in] fract blending fraction * \param[in] type L_BLEND_GRAY, L_BLEND_GRAY_WITH_INVERSE * \param[in] transparent 1 to use transparency; 0 otherwise * \param[in] transpix pixel grayval in pixs2 that is to be transparent * \return pixd if OK; pixs1 on error * *
 * Notes:
 *      (1) For inplace operation (pixs1 not cmapped), call it this way:
 *            pixBlendGray(pixs1, pixs1, pixs2, ...)
 *      (2) For generating a new pixd:
 *            pixd = pixBlendGray(NULL, pixs1, pixs2, ...)
 *      (3) Clipping of pixs2 to pixs1 is done in the inner pixel loop.
 *      (4) If pixs1 has a colormap, it is removed; otherwise, if pixs1
 *          has depth < 8, it is unpacked to generate a 8 bpp pix.
 *      (5) If transparent = 0, the blending fraction (fract) is
 *          applied equally to all pixels.
 *      (6) If transparent = 1, all pixels of value transpix (typically
 *          either 0 or 0xff) in pixs2 are transparent in the blend.
 *      (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) 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().
 *      (9) Invalid %fract defaults to 0.5 with a warning.
 *          Invalid %type defaults to L_BLEND_GRAY with a warning.
 * 
*/ 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, *pix1, *pix2; 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 (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\n", 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\n", 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) { pix1 = pixRemoveColormap(pixs1, REMOVE_CMAP_BASED_ON_SRC); if (pixGetDepth(pix1) < 8) pix2 = pixConvertTo8(pix1, FALSE); else pix2 = pixClone(pix1); pixd = pixCopy(NULL, pix2); pixDestroy(&pix1); pixDestroy(&pix2); } pixGetDimensions(pixd, &w, &h, &d); /* 8 or 32 bpp */ wpld = pixGetWpl(pixd); datad = pixGetData(pixd); pixc = pixConvertTo8(pixs2, 0); 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 || 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 || 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 || 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 || 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; } /*! * \brief pixBlendGrayInverse() * * \param[in] pixd [optional] either equal to pixs1 for in-place, or NULL * \param[in] pixd [optional] either NULL or equal to pixs1 for in-place * \param[in] pixs1 blendee, depth > 1 * \param[in] pixs2 blender, any depth; typ. smaller in size than pixs1 * \param[in] x,y origin [UL corner] of pixs2 relative to * the origin of pixs1; can be < 0 * \param[in] fract blending fraction * \return pixd if OK; pixs1 on error * *
 * Notes:
 *      (1) For inplace operation (pixs1 not cmapped), call it this way:
 *            pixBlendGrayInverse(pixs1, pixs1, pixs2, ...)
 *      (2) For generating a new pixd:
 *            pixd = pixBlendGrayInverse(NULL, pixs1, pixs2, ...)
 *      (3) Clipping of pixs2 to pixs1 is done in the inner pixel loop.
 *      (4) If pixs1 has a colormap, it is removed; otherwise if pixs1
 *          has depth < 8, it is unpacked to generate a 8 bpp pix.
 *      (5) 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.
 *      (6) 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, *pix1, *pix2; 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 (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\n", 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) { pix1 = pixRemoveColormap(pixs1, REMOVE_CMAP_BASED_ON_SRC); if (pixGetDepth(pix1) < 8) pix2 = pixConvertTo8(pix1, FALSE); else pix2 = pixClone(pix1); pixd = pixCopy(NULL, pix2); pixDestroy(&pix1); pixDestroy(&pix2); } pixGetDimensions(pixd, &w, &h, &d); /* 8 or 32 bpp */ wpld = pixGetWpl(pixd); datad = pixGetData(pixd); pixc = pixConvertTo8(pixs2, 0); 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; } /*! * \brief pixBlendColor() * * \param[in] pixd [optional] either equal to pixs1 for in-place, * or NULL * \param[in] pixs1 blendee; depth > 1 * \param[in] pixs2 blender, any depth; typically, the area of * pixs2 is smaller than pixs1 * \param[in] x,y origin [UL corner] of pixs2 relative to * the origin of pixs1 * \param[in] fract blending fraction * \param[in] transparent 1 to use transparency; 0 otherwise * \param[in] transpix pixel color in pixs2 that is to be transparent * \return pixd, or null on error * *
 * Notes:
 *      (1) For inplace operation (pixs1 must be 32 bpp), call it this way:
 *            pixBlendColor(pixs1, pixs1, pixs2, ...)
 *      (2) For generating a new pixd:
 *            pixd = pixBlendColor(NULL, pixs1, pixs2, ...)
 *      (3) If pixs2 is not 32 bpp rgb, it is converted.
 *      (4) Clipping of pixs2 to pixs1 is done in the inner pixel loop.
 *      (5) If pixs1 has a colormap, it is removed to generate a 32 bpp pix.
 *      (6) If pixs1 has depth < 32, it is unpacked to generate a 32 bpp pix.
 *      (7) If transparent = 0, the blending fraction (fract) is
 *          applied equally to all pixels.
 *      (8) 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; PROCNAME("pixBlendColor"); if (!pixs1) return (PIX *)ERROR_PTR("pixs1 not defined", procName, NULL); if (!pixs2) return (PIX *)ERROR_PTR("pixs2 not defined", procName, NULL); if (pixGetDepth(pixs1) == 1) return (PIX *)ERROR_PTR("pixs1 is 1 bpp", procName, NULL); if (pixd == pixs1 && pixGetDepth(pixs1) != 32) return (PIX *)ERROR_PTR("inplace; pixs1 not 32 bpp", procName, NULL); if (pixd && (pixd != pixs1)) return (PIX *)ERROR_PTR("pixd must be NULL or pixs1", procName, NULL); if (fract < 0.0 || fract > 1.0) { L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5\n", 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, pixConvertTo32() will remove a * 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) pixd = pixConvertTo32(pixs1); pixGetDimensions(pixd, &w, &h, NULL); wpld = pixGetWpl(pixd); datad = pixGetData(pixd); pixc = pixConvertTo32(pixs2); /* blend with 32 bpp rgb */ 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 || ((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; } /* * \brief pixBlendColorByChannel() * * \param[in] pixd [optional] either equal to pixs1 for in-place, * or NULL * \param[in] pixs1 blendee; depth > 1 * \param[in] pixs2 blender, any depth; typically, the area of * pixs2 is smaller than pixs1 * \param[in] x,y origin [UL corner] of pixs2 relative to * the origin of pixs1 * \param[in] rfract blending fraction in red channel * \param[in] gfract blending fraction in green channel * \param[in] bfract blending fraction in blue channel * \param[in] transparent 1 to use transparency; 0 otherwise * \param[in] transpix pixel color in pixs2 that is to be transparent * \return pixd if OK; pixd 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; 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 (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, pixConvertTo32() will remove a * 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) pixd = pixConvertTo32(pixs1); pixGetDimensions(pixd, &w, &h, NULL); wpld = pixGetWpl(pixd); datad = pixGetData(pixd); pixc = pixConvertTo32(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 || ((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); } /*! * \brief pixBlendGrayAdapt() * * \param[in] pixd [optional] either equal to pixs1 for in-place, or NULL * \param[in] pixs1 blendee; depth > 1 * \param[in] pixs2 blender, any depth; typically, the area of * pixs2 is smaller than pixs1 * \param[in] x,y origin [UL corner] of pixs2 relative to * the origin of pixs1; can be < 0 * \param[in] fract blending fraction * \param[in] 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) For inplace operation (pixs1 not cmapped), call it this way:
 *            pixBlendGrayAdapt(pixs1, pixs1, pixs2, ...)
 *          For generating a new pixd:
 *            pixd = pixBlendGrayAdapt(NULL, pixs1, pixs2, ...)
 *      (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) 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.
 *      (6) 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.
 *      (7) 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, *pix1, *pix2; 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 (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\n", procName); fract = 0.5; } if (shift == -1) shift = 64; /* default value */ if (shift < 0 || shift > 127) { L_WARNING("invalid shift; setting to 64\n", 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) { pix1 = pixRemoveColormap(pixs1, REMOVE_CMAP_BASED_ON_SRC); if (pixGetDepth(pix1) < 8) pix2 = pixConvertTo8(pix1, FALSE); else pix2 = pixClone(pix1); pixd = pixCopy(NULL, pix2); pixDestroy(&pix1); pixDestroy(&pix2); } /* Get the median value in the region of blending */ pix1 = pixClipRectangle(pixd, box, NULL); pix2 = pixConvertTo8(pix1, 0); pixGetRankValueMasked(pix2, NULL, 0, 0, 1, 0.5, &fmedian, NULL); median = (l_int32)(fmedian + 0.5); if (median < 128) pivot = median + shift; else pivot = median - shift; pixDestroy(&pix1); pixDestroy(&pix2); boxDestroy(&box); /* Process over src2; clip to src1. */ d = pixGetDepth(pixd); wpld = pixGetWpl(pixd); datad = pixGetData(pixd); pixc = pixConvertTo8(pixs2, 0); 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; } /*! * \brief pixFadeWithGray() * * \param[in] pixs colormapped or 8 bpp or 32 bpp * \param[in] pixb 8 bpp blender * \param[in] factor multiplicative factor to apply to blender value * \param[in] 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 applied 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; PROCNAME("pixFadeWithGray"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (!pixb) return (PIX *)ERROR_PTR("pixb not defined", procName, NULL); if (pixGetDepth(pixs) == 1) return (PIX *)ERROR_PTR("pixs is 1 bpp", procName, NULL); pixGetDimensions(pixb, &wb, &hb, &db); if (db != 8) return (PIX *)ERROR_PTR("pixb not 8 bpp", procName, NULL); if (factor < 0.0 || factor > 255.0) return (PIX *)ERROR_PTR("factor not in [0.0...255.0]", procName, NULL); if (type != L_BLEND_TO_WHITE && type != L_BLEND_TO_BLACK) return (PIX *)ERROR_PTR("invalid fade type", procName, NULL); /* Remove colormap if it exists; otherwise copy */ pixd = pixRemoveColormapGeneral(pixs, REMOVE_CMAP_BASED_ON_SRC, L_COPY); 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; } /* * \brief pixBlendHardLight() * * \param[in] pixd either NULL or equal to pixs1 for in-place * \param[in] pixs1 blendee; depth > 1, may be cmapped * \param[in] pixs2 blender, 8 or 32 bpp; may be colormapped; * typ. smaller in size than pixs1 * \param[in] x,y origin [UL corner] of pixs2 relative to * the origin of pixs1 * \param[in] 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\n", procName); fract = 0.5; } /* If pixs2 has a colormap, remove it */ pixc = pixRemoveColormap(pixs2, REMOVE_CMAP_BASED_ON_SRC); /* clone ok */ 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; } /* * \brief blendHardLightComponents() * * \param[in] a 8 bpp blendee component * \param[in] b 8 bpp blender component * \param[in] 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 * *-------------------------------------------------------------*/ /*! * \brief pixBlendCmap() * * \param[in] pixs 2, 4 or 8 bpp, with colormap * \param[in] pixb colormapped blender * \param[in] x, y UL corner of blender relative to pixs * \param[in] sindex colormap index of pixels in pixs to be changed * \return 0 if OK, 1 on error * *
 * Notes:
 *      (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_ok 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); pixGetDimensions(pixs, &w, &h, &d); if (d != 2 && d != 4 && d != 8) return ERROR_INT("depth not in {2,4,8}", procName, 1); /* 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); /* 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 * *---------------------------------------------------------------------*/ /*! * \brief pixBlendWithGrayMask() * * \param[in] pixs1 8 bpp gray, rgb, rgba or colormapped * \param[in] pixs2 8 bpp gray, rgb, rgba or colormapped * \param[in] pixg [optional] 8 bpp gray, for transparency of pixs2; * can be null * \param[in] x, y UL corner of pixs2 and 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 rgba,
 *          in which case we use the alpha component of pixs2.
 *      (3) If pixg exists, it need not be the same size as pixs2.
 *          However, we assume their UL corners are aligned with each other,
 *          and placed 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, spp, 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, *pixg2, *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); pixg2 = pixClone(pixg); } else { /* use the alpha component of pixs2 */ spp = pixGetSpp(pixs2); if (d2 != 32 || spp != 4) return (PIX *)ERROR_PTR("no alpha; pixs2 not rgba", procName, NULL); wmin = w2; hmin = h2; pixg2 = pixGetRGBComponent(pixs2, L_ALPHA_CHANNEL); } /* Remove colormaps if they exist; clones are OK */ pixr1 = pixRemoveColormap(pixs1, REMOVE_CMAP_BASED_ON_SRC); pixr2 = pixRemoveColormap(pixs2, REMOVE_CMAP_BASED_ON_SRC); /* 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: both either 8 or 32 bpp */ d1 = pixGetDepth(pix1); d2 = pixGetDepth(pix2); if (d1 != d2 || (d1 != 8 && d1 != 32)) { pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pixg2); return (PIX *)ERROR_PTR("depths not regularized! bad!", procName, NULL); } /* Start with a copy of pix1 */ pixd = pixCopy(NULL, pix1); pixDestroy(&pix1); /* Blend pix2 onto pixd, using pixg2. * Let the normalized pixel value of pixg2 be f = pixval / 255, * and the pixel values of pixd and pix2 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 pix2 and pixg2, clipping to pixd where necessary. */ datad = pixGetData(pixd); datas = pixGetData(pix2); datag = pixGetData(pixg2); wpld = pixGetWpl(pixd); wpls = pixGetWpl(pix2); wplg = pixGetWpl(pixg2); 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.; if (d1 == 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); } else { /* 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; } } } pixDestroy(&pixg2); pixDestroy(&pix2); return pixd; } /*---------------------------------------------------------------------* * Blending background to a specific color * *---------------------------------------------------------------------*/ /*! * \brief pixBlendBackgroundToColor() * * \param[in] pixd can be NULL or pixs * \param[in] pixs 32 bpp rgb * \param[in] box region for blending; can be NULL) * \param[in] color 32 bit color in 0xrrggbb00 format * \param[in] 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 * *---------------------------------------------------------------------*/ /*! * \brief pixMultiplyByColor() * * \param[in] pixd can be NULL or pixs * \param[in] pixs 32 bpp rgb * \param[in] box region for filtering; can be NULL) * \param[in] 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 * *---------------------------------------------------------------------*/ /*! * \brief pixAlphaBlendUniform() * * \param[in] pixs 32 bpp rgba, with alpha * \param[in] color 32 bit color in 0xrrggbb00 format * \return pixd 32 bpp rgb: pixs blended over uniform color %color, * a clone of pixs if no alpha, and 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.  The result is an RGB image.
 *      (2) If pixs does not have an alpha channel, it returns a clone
 *          of pixs.
 * 
*/ 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); if (pixGetSpp(pixs) != 4) { L_WARNING("no alpha channel; returning clone\n", procName); return pixClone(pixs); } pixt = pixCreateTemplate(pixs); pixSetAllArbitrary(pixt, color); pixSetSpp(pixt, 3); /* not required */ pixd = pixBlendWithGrayMask(pixt, pixs, NULL, 0, 0); pixDestroy(&pixt); return pixd; } /*---------------------------------------------------------------------* * Adding an alpha layer for blending * *---------------------------------------------------------------------*/ /*! * \brief pixAddAlphaToBlend() * * \param[in] pixs any depth * \param[in] fract fade fraction in the alpha component * \param[in] invert 1 to photometrically invert pixs * \return pixd 32 bpp with alpha, or null on error * *
 * Notes:
 *      (1) This is a simple alpha layer generator, where typically white has
 *          maximum transparency and black has minimum.
 *      (2) If %invert == 1, generate the same alpha layer but invert
 *          the input image photometrically.  This is useful for blending
 *          over dark images, where you want dark regions in pixs, such
 *          as text, to be lighter in the blended image.
 *      (3) The fade %fract gives the minimum transparency (i.e.,
 *          maximum opacity).  A small fraction is useful for adding
 *          a watermark to an image.
 *      (4) If pixs has a colormap, it is removed to rgb.
 *      (5) If pixs already has an alpha layer, it is overwritten.
 * 
*/ PIX * pixAddAlphaToBlend(PIX *pixs, l_float32 fract, l_int32 invert) { PIX *pixd, *pix1, *pix2; PROCNAME("pixAddAlphaToBlend"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (fract < 0.0 || fract > 1.0) return (PIX *)ERROR_PTR("invalid fract", procName, NULL); /* Convert to 32 bpp */ if (pixGetColormap(pixs)) pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR); else pix1 = pixClone(pixs); pixd = pixConvertTo32(pix1); /* new */ /* Use an inverted image if this will be blended with a dark image */ if (invert) pixInvert(pixd, pixd); /* Generate alpha layer */ pix2 = pixConvertTo8(pix1, 0); /* new */ pixInvert(pix2, pix2); pixMultConstantGray(pix2, fract); pixSetRGBComponent(pixd, pix2, L_ALPHA_CHANNEL); pixDestroy(&pix1); pixDestroy(&pix2); return pixd; } /*---------------------------------------------------------------------* * Setting a transparent alpha component over a white background * *---------------------------------------------------------------------*/ /*! * \brief pixSetAlphaOverWhite() * * \param[in] 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.  The 2 pixel gradation is useful
 *          to blur the boundary between the transparent region
 *          (that will render entirely from a backing image) and
 *          the remainder which renders from pixs.
 *      (2) All alpha component bits in pixs are overwritten.
 * 
*/ PIX * pixSetAlphaOverWhite(PIX *pixs) { PIX *pixd, *pix1, *pix2, *pix3, *pix4; 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; otherwise copy */ pixd = pixRemoveColormapGeneral(pixs, REMOVE_CMAP_TO_FULL_COLOR, L_COPY); /* Generate a 1 bpp image where a white pixel in pixd is 0. * In the comments below, a "white" pixel refers to pixd. * pix1 is rgb, pix2 is 8 bpp gray, pix3 is 1 bpp. */ pix1 = pixInvert(NULL, pixd); /* send white (255) to 0 for each sample */ pix2 = pixConvertRGBToGrayMinMax(pix1, L_CHOOSE_MAX); /* 0 if white */ pix3 = pixThresholdToBinary(pix2, 1); /* sets white pixels to 1 */ pixInvert(pix3, pix3); /* sets white pixels to 0 */ /* Generate the alpha component using the distance transform, * which measures the distance to the nearest bg (0) pixel in pix3. * After multiplying by 128, its value is 0 (transparent) * over white pixels, and goes to opaque (255) two pixels away * from the nearest white pixel. */ pix4 = pixDistanceFunction(pix3, 8, 8, L_BOUNDARY_FG); pixMultConstantGray(pix4, 128.0); pixSetRGBComponent(pixd, pix4, L_ALPHA_CHANNEL); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); pixDestroy(&pix4); return pixd; } /*---------------------------------------------------------------------* * Fading from the edge * *---------------------------------------------------------------------*/ /*! * \brief pixLinearEdgeFade() * * \param[in] pixs 8 or 32 bpp; no colormap * \param[in] dir L_FROM_LEFT, L_FROM_RIGHT, L_FROM_TOP, L_FROM_BOT * \param[in] fadeto L_BLEND_TO_WHITE, L_BLEND_TO_BLACK * \param[in] distfract fraction of width or height over which fading occurs * \param[in] maxfade fraction of fading at the edge, <= 1.0 * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) In-place operation.
 *      (2) Maximum fading fraction %maxfade occurs at the edge of the image,
 *          and the fraction goes to 0 at the fractional distance %distfract
 *          from the edge.  %maxfade must be in [0, 1].
 *      (3) %distrfact must be in [0, 1], and typically it would be <= 0.5.
 * 
*/ l_ok pixLinearEdgeFade(PIX *pixs, l_int32 dir, l_int32 fadeto, l_float32 distfract, l_float32 maxfade) { l_int32 i, j, w, h, d, wpl, xmin, ymin, range, val, rval, gval, bval; l_float32 slope, limit, del; l_uint32 *data, *line; PROCNAME("pixLinearEdgeFade"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (pixGetColormap(pixs) != NULL) return ERROR_INT("pixs has a colormap", procName, 1); pixGetDimensions(pixs, &w, &h, &d); if (d != 8 && d != 32) return ERROR_INT("pixs not 8 or 32 bpp", procName, 1); if (dir != L_FROM_LEFT && dir != L_FROM_RIGHT && dir != L_FROM_TOP && dir != L_FROM_BOT) return ERROR_INT("invalid fade direction from edge", procName, 1); if (fadeto != L_BLEND_TO_WHITE && fadeto != L_BLEND_TO_BLACK) return ERROR_INT("invalid fadeto photometry", procName, 1); if (maxfade <= 0) return 0; if (maxfade > 1.0) return ERROR_INT("invalid maxfade", procName, 1); if (distfract <= 0 || distfract * L_MIN(w, h) < 1.0) { L_INFO("distfract is too small\n", procName); return 0; } if (distfract > 1.0) return ERROR_INT("invalid distfract", procName, 1); /* Set up parameters */ if (dir == L_FROM_LEFT) { range = (l_int32)(distfract * w); xmin = 0; slope = maxfade / (l_float32)range; } else if (dir == L_FROM_RIGHT) { range = (l_int32)(distfract * w); xmin = w - range; slope = maxfade / (l_float32)range; } else if (dir == L_FROM_TOP) { range = (l_int32)(distfract * h); ymin = 0; slope = maxfade / (l_float32)range; } else if (dir == L_FROM_BOT) { range = (l_int32)(distfract * h); ymin = h - range; slope = maxfade / (l_float32)range; } limit = (fadeto == L_BLEND_TO_WHITE) ? 255.0 : 0.0; data = pixGetData(pixs); wpl = pixGetWpl(pixs); if (dir == L_FROM_LEFT || dir == L_FROM_RIGHT) { for (j = 0; j < range; j++) { del = (dir == L_FROM_LEFT) ? maxfade - slope * j : maxfade - slope * (range - j); for (i = 0; i < h; i++) { line = data + i * wpl; if (d == 8) { val = GET_DATA_BYTE(line, xmin + j); val += (limit - val) * del + 0.5; SET_DATA_BYTE(line, xmin + j, val); } else { /* rgb */ extractRGBValues(*(line + xmin + j), &rval, &gval, &bval); rval += (limit - rval) * del + 0.5; gval += (limit - gval) * del + 0.5; bval += (limit - bval) * del + 0.5; composeRGBPixel(rval, gval, bval, line + xmin + j); } } } } else { /* dir == L_FROM_TOP || L_FROM_BOT */ for (i = 0; i < range; i++) { del = (dir == L_FROM_TOP) ? maxfade - slope * i : maxfade - slope * (range - i); line = data + (ymin + i) * wpl; for (j = 0; j < w; j++) { if (d == 8) { val = GET_DATA_BYTE(line, j); val += (limit - val) * del + 0.5; SET_DATA_BYTE(line, j, val); } else { /* rgb */ extractRGBValues(*(line + j), &rval, &gval, &bval); rval += (limit - rval) * del + 0.5; gval += (limit - gval) * del + 0.5; bval += (limit - bval) * del + 0.5; composeRGBPixel(rval, gval, bval, line + j); } } } } return 0; }