Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/thirdparty/leptonica/src/blend.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/thirdparty/leptonica/src/blend.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,2261 @@ +/*====================================================================* + - 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 + * <pre> + * + * 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. + * </pre> + */ + +#ifdef HAVE_CONFIG_H +#include <config_auto.h> +#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 + * + * <pre> + * Notes: + * (1) This is a simple top-level interface. For more flexibility, + * call directly into pixBlendMask(), etc. + * </pre> + */ +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; + + if (!pixs1) + return (PIX *)ERROR_PTR("pixs1 not defined", __func__, NULL); + if (!pixs2) + return (PIX *)ERROR_PTR("pixs2 not defined", __func__, 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", + __func__, 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", __func__); + 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 + * + * <pre> + * 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. + * </pre> + */ +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; + + if (!pixs1) + return (PIX *)ERROR_PTR("pixs1 not defined", __func__, NULL); + if (!pixs2) + return (PIX *)ERROR_PTR("pixs2 not defined", __func__, NULL); + if (pixGetDepth(pixs1) == 1) + return (PIX *)ERROR_PTR("pixs1 is 1 bpp", __func__, NULL); + if (pixGetDepth(pixs2) != 1) + return (PIX *)ERROR_PTR("pixs2 not 1 bpp", __func__, NULL); + if (pixd == pixs1 && pixGetColormap(pixs1)) + return (PIX *)ERROR_PTR("inplace; pixs1 has colormap", __func__, NULL); + if (pixd && (pixd != pixs1)) + return (PIX *)ERROR_PTR("pixd must be NULL or pixs1", __func__, NULL); + if (fract < 0.0 || fract > 1.0) { + L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5\n", __func__); + 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", + __func__); + 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", + __func__); + } + } + } + } + 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", + __func__); + } + } + } + } + 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", + __func__); + } + } + } + } + break; + default: + L_WARNING("invalid binary mask blend type\n", __func__); + 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 + * + * <pre> + * 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. + * </pre> + */ +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; + + if (!pixs1) + return (PIX *)ERROR_PTR("pixs1 not defined", __func__, pixd); + if (!pixs2) + return (PIX *)ERROR_PTR("pixs2 not defined", __func__, pixd); + if (pixGetDepth(pixs1) == 1) + return (PIX *)ERROR_PTR("pixs1 is 1 bpp", __func__, pixd); + if (pixd == pixs1 && pixGetColormap(pixs1)) + return (PIX *)ERROR_PTR("can't do in-place with cmap", __func__, pixd); + if (pixd && (pixd != pixs1)) + return (PIX *)ERROR_PTR("pixd must be NULL or pixs1", __func__, pixd); + if (fract < 0.0 || fract > 1.0) { + L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5\n", __func__); + 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", __func__); + 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 + * + * <pre> + * 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] + * </pre> + */ +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; + + if (!pixs1) + return (PIX *)ERROR_PTR("pixs1 not defined", __func__, pixd); + if (!pixs2) + return (PIX *)ERROR_PTR("pixs2 not defined", __func__, pixd); + if (pixGetDepth(pixs1) == 1) + return (PIX *)ERROR_PTR("pixs1 is 1 bpp", __func__, pixd); + if (pixd == pixs1 && pixGetColormap(pixs1)) + return (PIX *)ERROR_PTR("can't do in-place with cmap", __func__, pixd); + if (pixd && (pixd != pixs1)) + return (PIX *)ERROR_PTR("pixd must be NULL or pixs1", __func__, pixd); + if (fract < 0.0 || fract > 1.0) { + L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5\n", __func__); + 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 + * + * <pre> + * 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. + * </pre> + */ +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; + + if (!pixs1) + return (PIX *)ERROR_PTR("pixs1 not defined", __func__, NULL); + if (!pixs2) + return (PIX *)ERROR_PTR("pixs2 not defined", __func__, NULL); + if (pixGetDepth(pixs1) == 1) + return (PIX *)ERROR_PTR("pixs1 is 1 bpp", __func__, NULL); + if (pixd == pixs1 && pixGetDepth(pixs1) != 32) + return (PIX *)ERROR_PTR("inplace; pixs1 not 32 bpp", __func__, NULL); + if (pixd && (pixd != pixs1)) + return (PIX *)ERROR_PTR("pixd must be NULL or pixs1", __func__, NULL); + if (fract < 0.0 || fract > 1.0) { + L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5\n", __func__); + 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 + * + * <pre> + * 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. + * </pre> + */ +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; + + if (!pixs1) + return (PIX *)ERROR_PTR("pixs1 not defined", __func__, pixd); + if (!pixs2) + return (PIX *)ERROR_PTR("pixs2 not defined", __func__, pixd); + if (pixGetDepth(pixs1) == 1) + return (PIX *)ERROR_PTR("pixs1 is 1 bpp", __func__, pixd); + if (pixd == pixs1 && pixGetDepth(pixs1) != 32) + return (PIX *)ERROR_PTR("inplace; pixs1 not 32 bpp", __func__, pixd); + if (pixd && (pixd != pixs1)) + return (PIX *)ERROR_PTR("pixd must be NULL or pixs1", __func__, 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 + * + * <pre> + * 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. + * </pre> + */ +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; + + if (!pixs1) + return (PIX *)ERROR_PTR("pixs1 not defined", __func__, pixd); + if (!pixs2) + return (PIX *)ERROR_PTR("pixs2 not defined", __func__, pixd); + if (pixGetDepth(pixs1) == 1) + return (PIX *)ERROR_PTR("pixs1 is 1 bpp", __func__, pixd); + if (pixd == pixs1 && pixGetColormap(pixs1)) + return (PIX *)ERROR_PTR("can't do in-place with cmap", __func__, pixd); + if (pixd && (pixd != pixs1)) + return (PIX *)ERROR_PTR("pixd must be NULL or pixs1", __func__, pixd); + if (fract < 0.0 || fract > 1.0) { + L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5\n", __func__); + fract = 0.5; + } + if (shift == -1) shift = 64; /* default value */ + if (shift < 0 || shift > 127) { + L_WARNING("invalid shift; setting to 64\n", __func__); + 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", __func__, 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 + * + * <pre> + * 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). + * </pre> + */ +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; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); + if (!pixb) + return (PIX *)ERROR_PTR("pixb not defined", __func__, NULL); + if (pixGetDepth(pixs) == 1) + return (PIX *)ERROR_PTR("pixs is 1 bpp", __func__, NULL); + pixGetDimensions(pixb, &wb, &hb, &db); + if (db != 8) + return (PIX *)ERROR_PTR("pixb not 8 bpp", __func__, NULL); + if (factor < 0.0 || factor > 255.0) + return (PIX *)ERROR_PTR("factor not in [0.0...255.0]", __func__, NULL); + if (type != L_BLEND_TO_WHITE && type != L_BLEND_TO_BLACK) + return (PIX *)ERROR_PTR("invalid fade type", __func__, 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 + * + * <pre> + * 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. + * </pre> + */ +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; + + if (!pixs1) + return (PIX *)ERROR_PTR("pixs1 not defined", __func__, pixd); + if (!pixs2) + return (PIX *)ERROR_PTR("pixs2 not defined", __func__, pixd); + pixGetDimensions(pixs1, &w, &h, &d); + pixGetDimensions(pixs2, &wc, &hc, &dc); + if (d == 1) + return (PIX *)ERROR_PTR("pixs1 is 1 bpp", __func__, pixd); + if (dc != 8 && dc != 32) + return (PIX *)ERROR_PTR("pixs2 not 8 or 32 bpp", __func__, pixd); + if (pixd && (pixd != pixs1)) + return (PIX *)ERROR_PTR("inplace and pixd != pixs1", __func__, pixd); + if (pixd == pixs1 && pixGetColormap(pixs1)) + return (PIX *)ERROR_PTR("inplace and pixs1 cmapped", __func__, pixd); + if (pixd && d != 8 && d != 32) + return (PIX *)ERROR_PTR("inplace and not 8 or 32 bpp", __func__, pixd); + + if (fract < 0.0 || fract > 1.0) { + L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5\n", __func__); + 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!", __func__, 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 + * + * <pre> + * 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. + * </pre> + */ +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 + * + * <pre> + * 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. + * </pre> + */ +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; + + if (!pixs) + return ERROR_INT("pixs not defined", __func__, 1); + if (!pixb) + return ERROR_INT("pixb not defined", __func__, 1); + if ((cmaps = pixGetColormap(pixs)) == NULL) + return ERROR_INT("no colormap in pixs", __func__, 1); + if ((cmapb = pixGetColormap(pixb)) == NULL) + return ERROR_INT("no colormap in pixb", __func__, 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}", __func__, 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", __func__, 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}", __func__, 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 + * + * <pre> + * 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. + * </pre> + */ +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; + + if (!pixs1) + return (PIX *)ERROR_PTR("pixs1 not defined", __func__, NULL); + if (!pixs2) + return (PIX *)ERROR_PTR("pixs2 not defined", __func__, 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", __func__, NULL); + if (pixg) { + if (pixGetDepth(pixg) != 8) + return (PIX *)ERROR_PTR("pixg not 8 bpp", __func__, 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", __func__, 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); + + /* Output a copy of pix1 to avoid side-effecting input pixs1 */ + pixd = pixCopy(NULL, pix1); + pixDestroy(&pix1); + + /* Sanity check: both either 8 or 32 bpp */ + d1 = pixGetDepth(pixd); + d2 = pixGetDepth(pix2); + if (!pixd || d1 != d2 || (d1 != 8 && d1 != 32)) { + pixDestroy(&pixd); + pixDestroy(&pix2); + pixDestroy(&pixg2); + return (PIX *)ERROR_PTR("depths not regularized! bad!", __func__, NULL); + } + + /* 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 + * + * <pre> + * 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. + * </pre> + */ +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; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd); + if (pixGetDepth(pixs) != 32) + return (PIX *)ERROR_PTR("pixs not 32 bpp", __func__, pixd); + if (pixd && (pixd != pixs)) + return (PIX *)ERROR_PTR("pixd neither null nor pixs", __func__, 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 + * + * <pre> + * 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. + * </pre> + */ +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; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd); + if (pixGetDepth(pixs) != 32) + return (PIX *)ERROR_PTR("pixs not 32 bpp", __func__, pixd); + if (pixd && (pixd != pixs)) + return (PIX *)ERROR_PTR("pixd neither null nor pixs", __func__, 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 + * + * <pre> + * 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. + * </pre> + */ +PIX * +pixAlphaBlendUniform(PIX *pixs, + l_uint32 color) +{ +PIX *pixt, *pixd; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); + if (pixGetDepth(pixs) != 32) + return (PIX *)ERROR_PTR("pixs not 32 bpp", __func__, NULL); + if (pixGetSpp(pixs) != 4) { + L_WARNING("no alpha channel; returning clone\n", __func__); + 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 + * + * <pre> + * 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. + * </pre> + */ +PIX * +pixAddAlphaToBlend(PIX *pixs, + l_float32 fract, + l_int32 invert) +{ +PIX *pixd, *pix1, *pix2; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); + if (fract < 0.0 || fract > 1.0) + return (PIX *)ERROR_PTR("invalid fract", __func__, 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 + * + * <pre> + * 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. + * </pre> + */ +PIX * +pixSetAlphaOverWhite(PIX *pixs) +{ +PIX *pixd, *pix1, *pix2, *pix3, *pix4; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); + if (!(pixGetDepth(pixs) == 32 || pixGetColormap(pixs))) + return (PIX *)ERROR_PTR("pixs not 32 bpp or cmapped", __func__, 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 + * + * <pre> + * 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. + * </pre> + */ +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; + + if (!pixs) + return ERROR_INT("pixs not defined", __func__, 1); + if (pixGetColormap(pixs) != NULL) + return ERROR_INT("pixs has a colormap", __func__, 1); + pixGetDimensions(pixs, &w, &h, &d); + if (d != 8 && d != 32) + return ERROR_INT("pixs not 8 or 32 bpp", __func__, 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", __func__, 1); + if (fadeto != L_BLEND_TO_WHITE && fadeto != L_BLEND_TO_BLACK) + return ERROR_INT("invalid fadeto photometry", __func__, 1); + if (maxfade <= 0) return 0; + if (maxfade > 1.0) + return ERROR_INT("invalid maxfade", __func__, 1); + if (distfract <= 0 || distfract * L_MIN(w, h) < 1.0) { + L_INFO("distfract is too small\n", __func__); + return 0; + } + if (distfract > 1.0) + return ERROR_INT("invalid distfract", __func__, 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 { /* 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; +}
