diff options
Diffstat (limited to 'leptonica/src/pix3.c')
-rw-r--r-- | leptonica/src/pix3.c | 3716 |
1 files changed, 3716 insertions, 0 deletions
diff --git a/leptonica/src/pix3.c b/leptonica/src/pix3.c new file mode 100644 index 00000000..67f6dea4 --- /dev/null +++ b/leptonica/src/pix3.c @@ -0,0 +1,3716 @@ +/*====================================================================* + - 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 pix3.c + * <pre> + * + * This file has these operations: + * + * (1) Mask-directed operations + * (2) Full-image bit-logical operations + * (3) Foreground pixel counting operations on 1 bpp images + * (4) Average and variance of pixel values + * (5) Mirrored tiling of a smaller image + * + * + * Masked operations + * l_int32 pixSetMasked() + * l_int32 pixSetMaskedGeneral() + * l_int32 pixCombineMasked() + * l_int32 pixCombineMaskedGeneral() + * l_int32 pixPaintThroughMask() + * l_int32 pixCopyWithBoxa() -- this is boxa-directed + * PIX *pixPaintSelfThroughMask() + * PIX *pixMakeMaskFromVal() + * PIX *pixMakeMaskFromLUT() + * PIX *pixMakeArbMaskFromRGB() + * PIX *pixSetUnderTransparency() + * PIX *pixMakeAlphaFromMask() + * l_int32 pixGetColorNearMaskBoundary() + * PIX *pixDisplaySelectedPixels() -- for debugging + * + * One and two-image boolean operations on arbitrary depth images + * PIX *pixInvert() + * PIX *pixOr() + * PIX *pixAnd() + * PIX *pixXor() + * PIX *pixSubtract() + * + * Foreground pixel counting in 1 bpp images + * l_int32 pixZero() + * l_int32 pixForegroundFraction() + * NUMA *pixaCountPixels() + * l_int32 pixCountPixels() + * l_int32 pixCountPixelsInRect() + * NUMA *pixCountByRow() + * NUMA *pixCountByColumn() + * NUMA *pixCountPixelsByRow() + * NUMA *pixCountPixelsByColumn() + * l_int32 pixCountPixelsInRow() + * NUMA *pixGetMomentByColumn() + * l_int32 pixThresholdPixelSum() + * l_int32 *makePixelSumTab8() + * l_int32 *makePixelCentroidTab8() + * + * Average of pixel values in gray images + * NUMA *pixAverageByRow() + * NUMA *pixAverageByColumn() + * l_int32 pixAverageInRect() + * + * Average of pixel values in RGB images + * l_int32 pixAverageInRectRGB() + * + * Variance of pixel values in gray images + * NUMA *pixVarianceByRow() + * NUMA *pixVarianceByColumn() + * l_int32 pixVarianceInRect() + * + * Average of absolute value of pixel differences in gray images + * NUMA *pixAbsDiffByRow() + * NUMA *pixAbsDiffByColumn() + * l_int32 pixAbsDiffInRect() + * l_int32 pixAbsDiffOnLine() + * + * Count of pixels with specific value + * l_int32 pixCountArbInRect() + * + * Mirrored tiling + * PIX *pixMirroredTiling() + * + * Representative tile near but outside region + * l_int32 pixFindRepCloseTile() + * + * Static helper function + * static BOXA *findTileRegionsForSearch() + * </pre> + */ + +#ifdef HAVE_CONFIG_H +#include <config_auto.h> +#endif /* HAVE_CONFIG_H */ + +#include <string.h> +#include <math.h> +#include "allheaders.h" + +static BOXA *findTileRegionsForSearch(BOX *box, l_int32 w, l_int32 h, + l_int32 searchdir, l_int32 mindist, + l_int32 tsize, l_int32 ntiles); + +#ifndef NO_CONSOLE_IO +#define EQUAL_SIZE_WARNING 0 +#endif /* ~NO_CONSOLE_IO */ + +/*-------------------------------------------------------------* + * Masked operations * + *-------------------------------------------------------------*/ +/*! + * \brief pixSetMasked() + * + * \param[in] pixd 1, 2, 4, 8, 16 or 32 bpp; or colormapped + * \param[in] pixm [optional] 1 bpp mask; no operation if NULL + * \param[in] val value to set at each masked pixel + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) In-place operation. + * (2) NOTE: For cmapped images, this calls pixSetMaskedCmap(). + * %val must be the 32-bit color representation of the RGB pixel. + * It is not the index into the colormap! + * (2) If pixm == NULL, a warning is given. + * (3) This is an implicitly aligned operation, where the UL + * corners of pixd and pixm coincide. A warning is + * issued if the two image sizes differ significantly, + * but the operation proceeds. + * (4) Each pixel in pixd that co-locates with an ON pixel + * in pixm is set to the specified input value. + * Other pixels in pixd are not changed. + * (5) You can visualize this as painting the color through + * the mask, as a stencil. + * (6) If you do not want to have the UL corners aligned, + * use the function pixSetMaskedGeneral(), which requires + * you to input the UL corner of pixm relative to pixd. + * (7) Implementation details: see comments in pixPaintThroughMask() + * for when we use rasterop to do the painting. + * </pre> + */ +l_ok +pixSetMasked(PIX *pixd, + PIX *pixm, + l_uint32 val) +{ +l_int32 wd, hd, wm, hm, w, h, d, wpld, wplm; +l_int32 i, j, rval, gval, bval; +l_uint32 *datad, *datam, *lined, *linem; + + PROCNAME("pixSetMasked"); + + if (!pixd) + return ERROR_INT("pixd not defined", procName, 1); + if (!pixm) { + L_WARNING("no mask; nothing to do\n", procName); + return 0; + } + if (pixGetColormap(pixd)) { + extractRGBValues(val, &rval, &gval, &bval); + return pixSetMaskedCmap(pixd, pixm, 0, 0, rval, gval, bval); + } + + if (pixGetDepth(pixm) != 1) + return ERROR_INT("pixm not 1 bpp", procName, 1); + d = pixGetDepth(pixd); + if (d == 1) + val &= 1; + else if (d == 2) + val &= 3; + else if (d == 4) + val &= 0x0f; + else if (d == 8) + val &= 0xff; + else if (d == 16) + val &= 0xffff; + else if (d != 32) + return ERROR_INT("pixd not 1, 2, 4, 8, 16 or 32 bpp", procName, 1); + pixGetDimensions(pixm, &wm, &hm, NULL); + + /* If d == 1, use rasterop; it's about 25x faster */ + if (d == 1) { + if (val == 0) { + PIX *pixmi = pixInvert(NULL, pixm); + pixRasterop(pixd, 0, 0, wm, hm, PIX_MASK, pixmi, 0, 0); + pixDestroy(&pixmi); + } else { /* val == 1 */ + pixRasterop(pixd, 0, 0, wm, hm, PIX_PAINT, pixm, 0, 0); + } + return 0; + } + + /* For d < 32, use rasterop for val == 0 (black); ~3x faster. */ + if (d < 32 && val == 0) { + PIX *pixmd = pixUnpackBinary(pixm, d, 1); + pixRasterop(pixd, 0, 0, wm, hm, PIX_MASK, pixmd, 0, 0); + pixDestroy(&pixmd); + return 0; + } + + /* For d < 32, use rasterop for val == maxval (white); ~3x faster. */ + if (d < 32 && val == ((1 << d) - 1)) { + PIX *pixmd = pixUnpackBinary(pixm, d, 0); + pixRasterop(pixd, 0, 0, wm, hm, PIX_PAINT, pixmd, 0, 0); + pixDestroy(&pixmd); + return 0; + } + + pixGetDimensions(pixd, &wd, &hd, &d); + w = L_MIN(wd, wm); + h = L_MIN(hd, hm); + if (L_ABS(wd - wm) > 7 || L_ABS(hd - hm) > 7) /* allow a small tolerance */ + L_WARNING("pixd and pixm sizes differ\n", procName); + + datad = pixGetData(pixd); + datam = pixGetData(pixm); + wpld = pixGetWpl(pixd); + wplm = pixGetWpl(pixm); + for (i = 0; i < h; i++) { + lined = datad + i * wpld; + linem = datam + i * wplm; + for (j = 0; j < w; j++) { + if (GET_DATA_BIT(linem, j)) { + switch(d) + { + case 2: + SET_DATA_DIBIT(lined, j, val); + break; + case 4: + SET_DATA_QBIT(lined, j, val); + break; + case 8: + SET_DATA_BYTE(lined, j, val); + break; + case 16: + SET_DATA_TWO_BYTES(lined, j, val); + break; + case 32: + *(lined + j) = val; + break; + default: + return ERROR_INT("shouldn't get here", procName, 1); + } + } + } + } + + return 0; +} + + +/*! + * \brief pixSetMaskedGeneral() + * + * \param[in] pixd 8, 16 or 32 bpp + * \param[in] pixm [optional] 1 bpp mask; no operation if null + * \param[in] val value to set at each masked pixel + * \param[in] x, y location of UL corner of pixm relative to pixd; + * can be negative + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) This is an in-place operation. + * (2) Alignment is explicit. If you want the UL corners of + * the two images to be aligned, use pixSetMasked(). + * (3) A typical use would be painting through the foreground + * of a small binary mask pixm, located somewhere on a + * larger pixd. Other pixels in pixd are not changed. + * (4) You can visualize this as painting the color through + * the mask, as a stencil. + * (5) This uses rasterop to handle clipping and different depths of pixd. + * (6) If pixd has a colormap, you should call pixPaintThroughMask(). + * (7) Why is this function here, if pixPaintThroughMask() does the + * same thing, and does it more generally? I've retained it here + * to show how one can paint through a mask using only full + * image rasterops, rather than pixel peeking in pixm and poking + * in pixd. It's somewhat baroque, but I found it amusing. + * </pre> + */ +l_ok +pixSetMaskedGeneral(PIX *pixd, + PIX *pixm, + l_uint32 val, + l_int32 x, + l_int32 y) +{ +l_int32 wm, hm, d; +PIX *pixmu, *pixc; + + PROCNAME("pixSetMaskedGeneral"); + + if (!pixd) + return ERROR_INT("pixd not defined", procName, 1); + if (!pixm) /* nothing to do */ + return 0; + + d = pixGetDepth(pixd); + if (d != 8 && d != 16 && d != 32) + return ERROR_INT("pixd not 8, 16 or 32 bpp", procName, 1); + if (pixGetDepth(pixm) != 1) + return ERROR_INT("pixm not 1 bpp", procName, 1); + + /* Unpack binary to depth d, with inversion: 1 --> 0, 0 --> 0xff... */ + if ((pixmu = pixUnpackBinary(pixm, d, 1)) == NULL) + return ERROR_INT("pixmu not made", procName, 1); + + /* Clear stenciled pixels in pixd */ + pixGetDimensions(pixm, &wm, &hm, NULL); + pixRasterop(pixd, x, y, wm, hm, PIX_SRC & PIX_DST, pixmu, 0, 0); + + /* Generate image with requisite color */ + if ((pixc = pixCreateTemplate(pixmu)) == NULL) { + pixDestroy(&pixmu); + return ERROR_INT("pixc not made", procName, 1); + } + pixSetAllArbitrary(pixc, val); + + /* Invert stencil mask, and paint color color into stencil */ + pixInvert(pixmu, pixmu); + pixAnd(pixmu, pixmu, pixc); + + /* Finally, repaint stenciled pixels, with val, in pixd */ + pixRasterop(pixd, x, y, wm, hm, PIX_SRC | PIX_DST, pixmu, 0, 0); + + pixDestroy(&pixmu); + pixDestroy(&pixc); + return 0; +} + + +/*! + * \brief pixCombineMasked() + * + * \param[in] pixd 1 bpp, 8 bpp gray or 32 bpp rgb; no cmap + * \param[in] pixs 1 bpp, 8 bpp gray or 32 bpp rgb; no cmap + * \param[in] pixm [optional] 1 bpp mask; no operation if NULL + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) In-place operation; pixd is changed. + * (2) This sets each pixel in pixd that co-locates with an ON + * pixel in pixm to the corresponding value of pixs. + * (3) pixs and pixd must be the same depth and not colormapped. + * (4) All three input pix are aligned at the UL corner, and the + * operation is clipped to the intersection of all three images. + * (5) If pixm == NULL, it's a no-op. + * (6) Implementation: see notes in pixCombineMaskedGeneral(). + * For 8 bpp selective masking, you might guess that it + * would be faster to generate an 8 bpp version of pixm, + * using pixConvert1To8(pixm, 0, 255), and then use a + * general combine operation + * d = (d & ~m) | (s & m) + * on a word-by-word basis. Not always. The word-by-word + * combine takes a time that is independent of the mask data. + * If the mask is relatively sparse, the byte-check method + * is actually faster! + * </pre> + */ +l_ok +pixCombineMasked(PIX *pixd, + PIX *pixs, + PIX *pixm) +{ +l_int32 w, h, d, ws, hs, ds, wm, hm, dm, wmin, hmin; +l_int32 wpl, wpls, wplm, i, j, val; +l_uint32 *data, *datas, *datam, *line, *lines, *linem; +PIX *pixt; + + PROCNAME("pixCombineMasked"); + + if (!pixm) /* nothing to do */ + return 0; + if (!pixd) + return ERROR_INT("pixd not defined", procName, 1); + if (!pixs) + return ERROR_INT("pixs not defined", procName, 1); + pixGetDimensions(pixd, &w, &h, &d); + pixGetDimensions(pixs, &ws, &hs, &ds); + pixGetDimensions(pixm, &wm, &hm, &dm); + if (d != ds) + return ERROR_INT("pixs and pixd depths differ", procName, 1); + if (dm != 1) + return ERROR_INT("pixm not 1 bpp", procName, 1); + if (d != 1 && d != 8 && d != 32) + return ERROR_INT("pixd not 1, 8 or 32 bpp", procName, 1); + if (pixGetColormap(pixd) || pixGetColormap(pixs)) + return ERROR_INT("pixs and/or pixd is cmapped", procName, 1); + + /* For d = 1, use rasterop. pixt is the part from pixs, under + * the fg of pixm, that is to be combined with pixd. We also + * use pixt to remove all fg of pixd that is under the fg of pixm. + * Then pixt and pixd are combined by ORing. */ + wmin = L_MIN(w, L_MIN(ws, wm)); + hmin = L_MIN(h, L_MIN(hs, hm)); + if (d == 1) { + pixt = pixAnd(NULL, pixs, pixm); + pixRasterop(pixd, 0, 0, wmin, hmin, PIX_DST & PIX_NOT(PIX_SRC), + pixm, 0, 0); + pixRasterop(pixd, 0, 0, wmin, hmin, PIX_SRC | PIX_DST, pixt, 0, 0); + pixDestroy(&pixt); + return 0; + } + + data = pixGetData(pixd); + datas = pixGetData(pixs); + datam = pixGetData(pixm); + wpl = pixGetWpl(pixd); + wpls = pixGetWpl(pixs); + wplm = pixGetWpl(pixm); + if (d == 8) { + for (i = 0; i < hmin; i++) { + line = data + i * wpl; + lines = datas + i * wpls; + linem = datam + i * wplm; + for (j = 0; j < wmin; j++) { + if (GET_DATA_BIT(linem, j)) { + val = GET_DATA_BYTE(lines, j); + SET_DATA_BYTE(line, j, val); + } + } + } + } else { /* d == 32 */ + for (i = 0; i < hmin; i++) { + line = data + i * wpl; + lines = datas + i * wpls; + linem = datam + i * wplm; + for (j = 0; j < wmin; j++) { + if (GET_DATA_BIT(linem, j)) + line[j] = lines[j]; + } + } + } + + return 0; +} + + +/*! + * \brief pixCombineMaskedGeneral() + * + * \param[in] pixd 1 bpp, 8 bpp gray or 32 bpp rgb + * \param[in] pixs 1 bpp, 8 bpp gray or 32 bpp rgb + * \param[in] pixm [optional] 1 bpp mask + * \param[in] x, y origin of pixs and pixm relative to pixd; can be negative + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) In-place operation; pixd is changed. + * (2) This is a generalized version of pixCombinedMasked(), where + * the source and mask can be placed at the same (arbitrary) + * location relative to pixd. + * (3) pixs and pixd must be the same depth and not colormapped. + * (4) The UL corners of both pixs and pixm are aligned with + * the point (x, y) of pixd, and the operation is clipped to + * the intersection of all three images. + * (5) If pixm == NULL, it's a no-op. + * (6) Implementation. There are two ways to do these. In the first, + * we use rasterop, ORing the part of pixs under the mask + * with pixd (which has been appropriately cleared there first). + * In the second, the mask is used one pixel at a time to + * selectively replace pixels of pixd with those of pixs. + * Here, we use rasterop for 1 bpp and pixel-wise replacement + * for 8 and 32 bpp. To use rasterop for 8 bpp, for example, + * we must first generate an 8 bpp version of the mask. + * The code is simple: + * + * Pix *pixm8 = pixConvert1To8(NULL, pixm, 0, 255); + * Pix *pixt = pixAnd(NULL, pixs, pixm8); + * pixRasterop(pixd, x, y, wmin, hmin, PIX_DST & PIX_NOT(PIX_SRC), + * pixm8, 0, 0); + * pixRasterop(pixd, x, y, wmin, hmin, PIX_SRC | PIX_DST, + * pixt, 0, 0); + * pixDestroy(&pixt); + * pixDestroy(&pixm8); + * </pre> + */ +l_ok +pixCombineMaskedGeneral(PIX *pixd, + PIX *pixs, + PIX *pixm, + l_int32 x, + l_int32 y) +{ +l_int32 d, w, h, ws, hs, ds, wm, hm, dm, wmin, hmin; +l_int32 wpl, wpls, wplm, i, j, val; +l_uint32 *data, *datas, *datam, *line, *lines, *linem; +PIX *pixt; + + PROCNAME("pixCombineMaskedGeneral"); + + if (!pixm) /* nothing to do */ + return 0; + if (!pixd) + return ERROR_INT("pixd not defined", procName, 1); + if (!pixs) + return ERROR_INT("pixs not defined", procName, 1); + pixGetDimensions(pixd, &w, &h, &d); + pixGetDimensions(pixs, &ws, &hs, &ds); + pixGetDimensions(pixm, &wm, &hm, &dm); + if (d != ds) + return ERROR_INT("pixs and pixd depths differ", procName, 1); + if (dm != 1) + return ERROR_INT("pixm not 1 bpp", procName, 1); + if (d != 1 && d != 8 && d != 32) + return ERROR_INT("pixd not 1, 8 or 32 bpp", procName, 1); + if (pixGetColormap(pixd) || pixGetColormap(pixs)) + return ERROR_INT("pixs and/or pixd is cmapped", procName, 1); + + /* For d = 1, use rasterop. pixt is the part from pixs, under + * the fg of pixm, that is to be combined with pixd. We also + * use pixt to remove all fg of pixd that is under the fg of pixm. + * Then pixt and pixd are combined by ORing. */ + wmin = L_MIN(ws, wm); + hmin = L_MIN(hs, hm); + if (d == 1) { + pixt = pixAnd(NULL, pixs, pixm); + pixRasterop(pixd, x, y, wmin, hmin, PIX_DST & PIX_NOT(PIX_SRC), + pixm, 0, 0); + pixRasterop(pixd, x, y, wmin, hmin, PIX_SRC | PIX_DST, pixt, 0, 0); + pixDestroy(&pixt); + return 0; + } + + wpl = pixGetWpl(pixd); + data = pixGetData(pixd); + wpls = pixGetWpl(pixs); + datas = pixGetData(pixs); + wplm = pixGetWpl(pixm); + datam = pixGetData(pixm); + + for (i = 0; i < hmin; i++) { + if (y + i < 0 || y + i >= h) continue; + line = data + (y + i) * wpl; + lines = datas + i * wpls; + linem = datam + i * wplm; + for (j = 0; j < wmin; j++) { + if (x + j < 0 || x + j >= w) continue; + if (GET_DATA_BIT(linem, j)) { + switch (d) + { + case 8: + val = GET_DATA_BYTE(lines, j); + SET_DATA_BYTE(line, x + j, val); + break; + case 32: + *(line + x + j) = *(lines + j); + break; + default: + return ERROR_INT("shouldn't get here", procName, 1); + } + } + } + } + + return 0; +} + + +/*! + * \brief pixPaintThroughMask() + * + * \param[in] pixd 1, 2, 4, 8, 16 or 32 bpp; or colormapped + * \param[in] pixm [optional] 1 bpp mask + * \param[in] x, y origin of pixm relative to pixd; can be negative + * \param[in] val pixel value to set at each masked pixel + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) In-place operation. Calls pixSetMaskedCmap() for colormapped + * images. + * (2) For 1, 2, 4, 8 and 16 bpp gray, we take the appropriate + * number of least significant bits of val. + * (3) If pixm == NULL, it's a no-op. + * (4) The mask origin is placed at (x,y) on pixd, and the + * operation is clipped to the intersection of rectangles. + * (5) For rgb, the components in val are in the canonical locations, + * with red in location COLOR_RED, etc. + * (6) Implementation detail 1: + * For painting with val == 0 or val == maxval, you can use rasterop. + * If val == 0, invert the mask so that it's 0 over the region + * into which you want to write, and use PIX_SRC & PIX_DST to + * clear those pixels. To write with val = maxval (all 1's), + * use PIX_SRC | PIX_DST to set all bits under the mask. + * (7) Implementation detail 2: + * The rasterop trick can be used for depth > 1 as well. + * For val == 0, generate the mask for depth d from the binary + * mask using + * pixmd = pixUnpackBinary(pixm, d, 1); + * and use pixRasterop() with PIX_MASK. For val == maxval, + * pixmd = pixUnpackBinary(pixm, d, 0); + * and use pixRasterop() with PIX_PAINT. + * But note that if d == 32 bpp, it is about 3x faster to use + * the general implementation (not pixRasterop()). + * (8) Implementation detail 3: + * It might be expected that the switch in the inner loop will + * cause large branching delays and should be avoided. + * This is not the case, because the entrance is always the + * same and the compiler can correctly predict the jump. + * </pre> + */ +l_ok +pixPaintThroughMask(PIX *pixd, + PIX *pixm, + l_int32 x, + l_int32 y, + l_uint32 val) +{ +l_int32 d, w, h, wm, hm, wpl, wplm, i, j, rval, gval, bval; +l_uint32 *data, *datam, *line, *linem; + + PROCNAME("pixPaintThroughMask"); + + if (!pixm) /* nothing to do */ + return 0; + if (!pixd) + return ERROR_INT("pixd not defined", procName, 1); + if (pixGetColormap(pixd)) { + extractRGBValues(val, &rval, &gval, &bval); + return pixSetMaskedCmap(pixd, pixm, x, y, rval, gval, bval); + } + + if (pixGetDepth(pixm) != 1) + return ERROR_INT("pixm not 1 bpp", procName, 1); + d = pixGetDepth(pixd); + if (d == 1) + val &= 1; + else if (d == 2) + val &= 3; + else if (d == 4) + val &= 0x0f; + else if (d == 8) + val &= 0xff; + else if (d == 16) + val &= 0xffff; + else if (d != 32) + return ERROR_INT("pixd not 1, 2, 4, 8, 16 or 32 bpp", procName, 1); + pixGetDimensions(pixm, &wm, &hm, NULL); + + /* If d == 1, use rasterop; it's about 25x faster. */ + if (d == 1) { + if (val == 0) { + PIX *pixmi = pixInvert(NULL, pixm); + pixRasterop(pixd, x, y, wm, hm, PIX_MASK, pixmi, 0, 0); + pixDestroy(&pixmi); + } else { /* val == 1 */ + pixRasterop(pixd, x, y, wm, hm, PIX_PAINT, pixm, 0, 0); + } + return 0; + } + + /* For d < 32, use rasterop if val == 0 (black); ~3x faster. */ + if (d < 32 && val == 0) { + PIX *pixmd = pixUnpackBinary(pixm, d, 1); + pixRasterop(pixd, x, y, wm, hm, PIX_MASK, pixmd, 0, 0); + pixDestroy(&pixmd); + return 0; + } + + /* For d < 32, use rasterop if val == maxval (white); ~3x faster. */ + if (d < 32 && val == ((1 << d) - 1)) { + PIX *pixmd = pixUnpackBinary(pixm, d, 0); + pixRasterop(pixd, x, y, wm, hm, PIX_PAINT, pixmd, 0, 0); + pixDestroy(&pixmd); + return 0; + } + + /* All other cases */ + pixGetDimensions(pixd, &w, &h, NULL); + wpl = pixGetWpl(pixd); + data = pixGetData(pixd); + wplm = pixGetWpl(pixm); + datam = pixGetData(pixm); + for (i = 0; i < hm; i++) { + if (y + i < 0 || y + i >= h) continue; + line = data + (y + i) * wpl; + linem = datam + i * wplm; + for (j = 0; j < wm; j++) { + if (x + j < 0 || x + j >= w) continue; + if (GET_DATA_BIT(linem, j)) { + switch (d) + { + case 2: + SET_DATA_DIBIT(line, x + j, val); + break; + case 4: + SET_DATA_QBIT(line, x + j, val); + break; + case 8: + SET_DATA_BYTE(line, x + j, val); + break; + case 16: + SET_DATA_TWO_BYTES(line, x + j, val); + break; + case 32: + *(line + x + j) = val; + break; + default: + return ERROR_INT("shouldn't get here", procName, 1); + } + } + } + } + + return 0; +} + + +/*! + * \brief pixCopyWithBoxa() + * + * \param[in] pixs all depths; cmap ok + * \param[in] boxa e.g., from components of a photomask + * \param[in] background L_SET_WHITE or L_SET_BLACK + * \return pixd or NULL on error + * + * <pre> + * Notes: + * (1) Pixels from pixs are copied ("blitted") through each box into pixd. + * (2) Pixels not copied are preset to either white or black. + * (3) This fast and simple implementation can use rasterop because + * each region to be copied is rectangular. + * (4) A much slower implemention that doesn't use rasterop would make + * a 1 bpp mask from the boxa and then copy, pixel by pixel, + * through the mask: + * pixGetDimensions(pixs, &w, &h, NULL); + * pixm = pixCreate(w, h, 1); + * pixm = pixMaskBoxa(pixm, pixm, boxa); + * pixd = pixCreateTemplate(pixs); + * pixSetBlackOrWhite(pixd, background); + * pixCombineMasked(pixd, pixs, pixm); + * pixDestroy(&pixm); + * </pre> + */ +PIX * +pixCopyWithBoxa(PIX *pixs, + BOXA *boxa, + l_int32 background) +{ +l_int32 i, n, x, y, w, h; +PIX *pixd; + + PROCNAME("pixCopyWithBoxa"); + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); + if (!boxa) + return (PIX *)ERROR_PTR("boxa not defined", procName, NULL); + if (background != L_SET_WHITE && background != L_SET_BLACK) + return (PIX *)ERROR_PTR("invalid background", procName, NULL); + + pixd = pixCreateTemplate(pixs); + pixSetBlackOrWhite(pixd, background); + n = boxaGetCount(boxa); + for (i = 0; i < n; i++) { + boxaGetBoxGeometry(boxa, i, &x, &y, &w, &h); + pixRasterop(pixd, x, y, w, h, PIX_SRC, pixs, x, y); + } + return pixd; +} + + +/*! + * \brief pixPaintSelfThroughMask() + * + * \param[in] pixd 8 bpp gray or 32 bpp rgb; not colormapped + * \param[in] pixm 1 bpp mask + * \param[in] x, y origin of pixm relative to pixd; must not be negative + * \param[in] searchdir L_HORIZ, L_VERT or L_BOTH_DIRECTIONS + * \param[in] mindist min distance of nearest tile edge to box; >= 0 + * \param[in] tilesize requested size for tiling; may be reduced + * \param[in] ntiles number of tiles tested in each row/column + * \param[in] distblend distance outside the fg used for blending with pixs + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) In-place operation; pixd is changed. + * (2) If pixm == NULL, it's a no-op. + * (3) The mask origin is placed at (x,y) on pixd, and the + * operation is clipped to the intersection of pixd and the + * fg of the mask. + * (4) %tsize is the the requested size for tiling. The actual + * actual size for each c.c. will be bounded by the minimum + * dimension of the c.c. + * (5) For %mindist, %searchdir and %ntiles, see pixFindRepCloseTile(). + * They determine the set of possible tiles that can be used + * to build a larger mirrored tile to paint onto pixd through + * the c.c. of pixm. + * (6) %distblend is used for alpha blending. It is only applied + * if there is exactly one c.c. in the mask. Use distblend == 0 + * to skip blending and just paint through the 1 bpp mask. + * (7) To apply blending to more than 1 component, call this function + * repeatedly with %pixm, %x and %y representing one component of + * the mask each time. This would be done as follows, for an + * underlying image pixs and mask pixm of components to fill: + * Boxa *boxa = pixConnComp(pixm, &pixa, 8); + * n = boxaGetCount(boxa); + * for (i = 0; i < n; i++) { + * Pix *pix = pixaGetPix(pixa, i, L_CLONE); + * Box *box = pixaGetBox(pixa, i, L_CLONE); + * boxGetGeometry(box, &bx, &by, &bw, &bh); + * pixPaintSelfThroughMask(pixs, pix, bx, by, searchdir, + * mindist, tilesize, ntiles, distblend); + * pixDestroy(&pix); + * boxDestroy(&box); + * } + * pixaDestroy(&pixa); + * boxaDestroy(&boxa); + * (8) If no tiles can be found, this falls back to estimating the + * color near the boundary of the region to be textured. + * (9) This can be used to replace the pixels in some regions of + * an image by selected neighboring pixels. The mask represents + * the pixels to be replaced. For each connected component in + * the mask, this function selects up to two tiles of neighboring + * pixels to be used for replacement of pixels represented by + * the component (i.e., under the FG of that component in the mask). + * After selection, mirror replication is used to generate an + * image that is large enough to cover the component. Alpha + * blending can also be used outside of the component, but near the + * edge, to blur the transition between painted and original pixels. + * </pre> + */ +l_ok +pixPaintSelfThroughMask(PIX *pixd, + PIX *pixm, + l_int32 x, + l_int32 y, + l_int32 searchdir, + l_int32 mindist, + l_int32 tilesize, + l_int32 ntiles, + l_int32 distblend) +{ +l_int32 w, h, d, wm, hm, dm, i, n, bx, by, bw, bh, edgeblend, retval, minside; +l_uint32 pixval; +BOX *box, *boxv, *boxh; +BOXA *boxa; +PIX *pixf, *pixv, *pixh, *pix1, *pix2, *pix3, *pix4, *pix5; +PIXA *pixa; + + PROCNAME("pixPaintSelfThroughMask"); + + if (!pixm) /* nothing to do */ + return 0; + if (!pixd) + return ERROR_INT("pixd not defined", procName, 1); + if (pixGetColormap(pixd) != NULL) + return ERROR_INT("pixd has colormap", procName, 1); + pixGetDimensions(pixd, &w, &h, &d); + if (d != 8 && d != 32) + return ERROR_INT("pixd not 8 or 32 bpp", procName, 1); + pixGetDimensions(pixm, &wm, &hm, &dm); + if (dm != 1) + return ERROR_INT("pixm not 1 bpp", procName, 1); + if (x < 0 || y < 0) + return ERROR_INT("x and y must be non-negative", procName, 1); + if (searchdir != L_HORIZ && searchdir != L_VERT && + searchdir != L_BOTH_DIRECTIONS) + return ERROR_INT("invalid searchdir", procName, 1); + if (tilesize < 2) + return ERROR_INT("tilesize must be >= 2", procName, 1); + if (distblend < 0) + return ERROR_INT("distblend must be >= 0", procName, 1); + + /* Embed mask in full sized mask */ + if (wm < w || hm < h) { + pixf = pixCreate(w, h, 1); + pixRasterop(pixf, x, y, wm, hm, PIX_SRC, pixm, 0, 0); + } else { + pixf = pixCopy(NULL, pixm); + } + + /* Get connected components of mask */ + boxa = pixConnComp(pixf, &pixa, 8); + if ((n = pixaGetCount(pixa)) == 0) { + L_WARNING("no fg in mask\n", procName); + pixDestroy(&pixf); + pixaDestroy(&pixa); + boxaDestroy(&boxa); + return 1; + } + boxaDestroy(&boxa); + + /* For each c.c., generate one or two representative tiles for + * texturizing and apply through the mask. The input 'tilesize' + * is the requested value. Note that if there is exactly one + * component, and blending at the edge is requested, an alpha mask + * is generated, which is larger than the bounding box of the c.c. */ + edgeblend = (n == 1 && distblend > 0) ? 1 : 0; + if (distblend > 0 && n > 1) + L_WARNING("%d components; can not blend at edges\n", procName, n); + retval = 0; + for (i = 0; i < n; i++) { + if (edgeblend) { + pix1 = pixMakeAlphaFromMask(pixf, distblend, &box); + } else { + pix1 = pixaGetPix(pixa, i, L_CLONE); + box = pixaGetBox(pixa, i, L_CLONE); + } + boxGetGeometry(box, &bx, &by, &bw, &bh); + minside = L_MIN(bw, bh); + + boxh = boxv = NULL; + if (searchdir == L_HORIZ || searchdir == L_BOTH_DIRECTIONS) { + pixFindRepCloseTile(pixd, box, L_HORIZ, mindist, + L_MIN(minside, tilesize), ntiles, &boxh, 0); + } + if (searchdir == L_VERT || searchdir == L_BOTH_DIRECTIONS) { + pixFindRepCloseTile(pixd, box, L_VERT, mindist, + L_MIN(minside, tilesize), ntiles, &boxv, 0); + } + if (!boxh && !boxv) { + L_WARNING("tile region not selected; paint color near boundary\n", + procName); + pixDestroy(&pix1); + pix1 = pixaGetPix(pixa, i, L_CLONE); + pixaGetBoxGeometry(pixa, i, &bx, &by, NULL, NULL); + retval = pixGetColorNearMaskBoundary(pixd, pixm, box, distblend, + &pixval, 0); + pixSetMaskedGeneral(pixd, pix1, pixval, bx, by); + pixDestroy(&pix1); + boxDestroy(&box); + continue; + } + + /* Extract the selected squares from pixd */ + pixh = (boxh) ? pixClipRectangle(pixd, boxh, NULL) : NULL; + pixv = (boxv) ? pixClipRectangle(pixd, boxv, NULL) : NULL; + if (pixh && pixv) + pix2 = pixBlend(pixh, pixv, 0, 0, 0.5); + else if (pixh) + pix2 = pixClone(pixh); + else /* pixv */ + pix2 = pixClone(pixv); + pixDestroy(&pixh); + pixDestroy(&pixv); + boxDestroy(&boxh); + boxDestroy(&boxv); + + /* Generate an image the size of the b.b. of the c.c., + * possibly extended by the blending distance, which + * is then either painted through the c.c. mask or + * blended using the alpha mask for that c.c. */ + pix3 = pixMirroredTiling(pix2, bw, bh); + if (edgeblend) { + pix4 = pixClipRectangle(pixd, box, NULL); + pix5 = pixBlendWithGrayMask(pix4, pix3, pix1, 0, 0); + pixRasterop(pixd, bx, by, bw, bh, PIX_SRC, pix5, 0, 0); + pixDestroy(&pix4); + pixDestroy(&pix5); + } else { + pixCombineMaskedGeneral(pixd, pix3, pix1, bx, by); + } + pixDestroy(&pix1); + pixDestroy(&pix2); + pixDestroy(&pix3); + boxDestroy(&box); + } + + pixaDestroy(&pixa); + pixDestroy(&pixf); + return retval; +} + + +/*! + * \brief pixMakeMaskFromVal() + * + * \param[in] pixs 2, 4 or 8 bpp; can be colormapped + * \param[in] val pixel value + * \return pixd 1 bpp mask, or NULL on error + * + * <pre> + * Notes: + * (1) This generates a 1 bpp mask image, where a 1 is written in + * the mask for each pixel in pixs that has a value %val. + * (2) If no pixels have the value, an empty mask is generated. + * </pre> + */ +PIX * +pixMakeMaskFromVal(PIX *pixs, + l_int32 val) +{ +l_int32 w, h, d, i, j, sval, wpls, wpld; +l_uint32 *datas, *datad, *lines, *lined; +PIX *pixd; + + PROCNAME("pixMakeMaskFromVal"); + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); + pixGetDimensions(pixs, &w, &h, &d); + if (d != 2 && d != 4 && d != 8) + return (PIX *)ERROR_PTR("pix not 2, 4 or 8 bpp", procName, NULL); + + pixd = pixCreate(w, h, 1); + pixCopyResolution(pixd, pixs); + pixCopyInputFormat(pixd, pixs); + datas = pixGetData(pixs); + datad = pixGetData(pixd); + wpls = pixGetWpl(pixs); + wpld = pixGetWpl(pixd); + for (i = 0; i < h; i++) { + lines = datas + i * wpls; + lined = datad + i * wpld; + for (j = 0; j < w; j++) { + if (d == 2) + sval = GET_DATA_DIBIT(lines, j); + else if (d == 4) + sval = GET_DATA_QBIT(lines, j); + else /* d == 8 */ + sval = GET_DATA_BYTE(lines, j); + if (sval == val) + SET_DATA_BIT(lined, j); + } + } + + return pixd; +} + + +/*! + * \brief pixMakeMaskFromLUT() + * + * \param[in] pixs 2, 4 or 8 bpp; can be colormapped + * \param[in] tab 256-entry LUT; 1 means to write to mask + * \return pixd 1 bpp mask, or NULL on error + * + * <pre> + * Notes: + * (1) This generates a 1 bpp mask image, where a 1 is written in + * the mask for each pixel in pixs that has a value corresponding + * to a 1 in the LUT. + * (2) The LUT should be of size 256. + * </pre> + */ +PIX * +pixMakeMaskFromLUT(PIX *pixs, + l_int32 *tab) +{ +l_int32 w, h, d, i, j, val, wpls, wpld; +l_uint32 *datas, *datad, *lines, *lined; +PIX *pixd; + + PROCNAME("pixMakeMaskFromLUT"); + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); + if (!tab) + return (PIX *)ERROR_PTR("tab not defined", procName, NULL); + pixGetDimensions(pixs, &w, &h, &d); + if (d != 2 && d != 4 && d != 8) + return (PIX *)ERROR_PTR("pix not 2, 4 or 8 bpp", procName, NULL); + + pixd = pixCreate(w, h, 1); + pixCopyResolution(pixd, pixs); + pixCopyInputFormat(pixd, pixs); + datas = pixGetData(pixs); + datad = pixGetData(pixd); + wpls = pixGetWpl(pixs); + wpld = pixGetWpl(pixd); + for (i = 0; i < h; i++) { + lines = datas + i * wpls; + lined = datad + i * wpld; + for (j = 0; j < w; j++) { + if (d == 2) + val = GET_DATA_DIBIT(lines, j); + else if (d == 4) + val = GET_DATA_QBIT(lines, j); + else /* d == 8 */ + val = GET_DATA_BYTE(lines, j); + if (tab[val] == 1) + SET_DATA_BIT(lined, j); + } + } + + return pixd; +} + + +/*! + * \brief pixMakeArbMaskFromRGB() + * + * \param[in] pixs 32 bpp RGB + * \param[in] rc, gc, bc arithmetic factors; can be negative + * \param[in] thresh lower threshold on weighted sum of components + * \return pixd 1 bpp mask, or NULL on error + * + * <pre> + * Notes: + * (1) This generates a 1 bpp mask image, where a 1 is written in + * the mask for each pixel in pixs that satisfies + * rc * rval + gc * gval + bc * bval > thresh + * where rval is the red component, etc. + * (2) Unlike with pixConvertToGray(), there are no constraints + * on the color coefficients, which can be negative. For + * example, a mask that discriminates against red and in favor + * of blue will have rc < 0.0 and bc > 0.0. + * (3) To make the result independent of intensity (the 'V' in HSV), + * select coefficients so that %thresh = 0. Then the result + * is not changed when all components are multiplied by the + * same constant (as long as nothing saturates). This can be + * useful if, for example, the illumination is not uniform. + * </pre> + */ +PIX * +pixMakeArbMaskFromRGB(PIX *pixs, + l_float32 rc, + l_float32 gc, + l_float32 bc, + l_float32 thresh) +{ +PIX *pix1, *pix2; + + PROCNAME("pixMakeArbMaskFromRGB"); + + if (!pixs || pixGetDepth(pixs) != 32) + return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL); + if (thresh >= 255.0) thresh = 254.0; /* avoid 8 bit overflow */ + + if ((pix1 = pixConvertRGBToGrayArb(pixs, rc, gc, bc)) == NULL) + return (PIX *)ERROR_PTR("pix1 not made", procName, NULL); + pix2 = pixThresholdToBinary(pix1, thresh + 1); + pixInvert(pix2, pix2); + pixDestroy(&pix1); + return pix2; +} + + +/*! + * \brief pixSetUnderTransparency() + * + * \param[in] pixs 32 bpp rgba + * \param[in] val 32 bit unsigned color to use where alpha == 0 + * \param[in] debug displays layers of pixs + * \return pixd 32 bpp rgba, or NULL on error + * + * <pre> + * Notes: + * (1) This sets the r, g and b components under every fully + * transparent alpha component to %val. The alpha components + * are unchanged. + * (2) Full transparency is denoted by alpha == 0. Setting + * all pixels to a constant %val where alpha is transparent + * can improve compressibility by reducing the entropy. + * (3) The visual result depends on how the image is displayed. + * (a) For display devices that respect the use of the alpha + * layer, this will not affect the appearance. + * (b) For typical leptonica operations, alpha is ignored, + * so there will be a change in appearance because this + * resets the rgb values in the fully transparent region. + * (4) pixRead() and pixWrite() will, by default, read and write + * 4-component (rgba) pix in png format. To ignore the alpha + * component after reading, or omit it on writing, pixSetSpp(..., 3). + * (5) Here are some examples: + * * To convert all fully transparent pixels in a 4 component + * (rgba) png file to white: + * pixs = pixRead(<infile>); + * pixd = pixSetUnderTransparency(pixs, 0xffffff00, 0); + * * To write pixd with the alpha component: + * pixWrite(<outfile>, pixd, IFF_PNG); + * * To write and rgba image without the alpha component, first do: + * pixSetSpp(pixd, 3); + * If you later want to use the alpha, spp must be reset to 4. + * * (fancier) To remove the alpha by blending the image over + * a white background: + * pixRemoveAlpha() + * This changes all pixel values where the alpha component is + * not opaque (255). + * (6) Caution. rgb images in leptonica typically have value 0 in + * the alpha channel, which is fully transparent. If spp for + * such an image were changed from 3 to 4, the image becomes + * fully transparent, and this function will set each pixel to %val. + * If you really want to set every pixel to the same value, + * use pixSetAllArbitrary(). + * (7) This is useful for compressing an RGBA image where the part + * of the image that is fully transparent is random junk; compression + * is typically improved by setting that region to a constant. + * For rendering as a 3 component RGB image over a uniform + * background of arbitrary color, use pixAlphaBlendUniform(). + * </pre> + */ +PIX * +pixSetUnderTransparency(PIX *pixs, + l_uint32 val, + l_int32 debug) +{ +PIX *pixg, *pixm, *pixt, *pixd; + + PROCNAME("pixSetUnderTransparency"); + + if (!pixs || pixGetDepth(pixs) != 32) + return (PIX *)ERROR_PTR("pixs not defined or not 32 bpp", + procName, NULL); + + if (pixGetSpp(pixs) != 4) { + L_WARNING("no alpha channel; returning a copy\n", procName); + return pixCopy(NULL, pixs); + } + + /* Make a mask from the alpha component with ON pixels + * wherever the alpha component is fully transparent (0). + * The hard way: + * l_int32 *lut = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32)); + * lut[0] = 1; + * pixg = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL); + * pixm = pixMakeMaskFromLUT(pixg, lut); + * LEPT_FREE(lut); + * But there's an easier way to set pixels in a mask where + * the alpha component is 0 ... */ + pixg = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL); + pixm = pixThresholdToBinary(pixg, 1); + + if (debug) { + pixt = pixDisplayLayersRGBA(pixs, 0xffffff00, 600); + pixDisplay(pixt, 0, 0); + pixDestroy(&pixt); + } + + pixd = pixCopy(NULL, pixs); + pixSetMasked(pixd, pixm, (val & 0xffffff00)); + pixDestroy(&pixg); + pixDestroy(&pixm); + return pixd; +} + + +/*! + * \brief pixMakeAlphaFromMask() + * + * \param[in] pixs 1 bpp + * \param[in] dist blending distance; typically 10 - 30 + * \param[out] pbox [optional] use NULL to get the full size + * \return pixd (8 bpp gray, or NULL on error + * + * <pre> + * Notes: + * (1) This generates a 8 bpp alpha layer that is opaque (256) + * over the FG of pixs, and goes transparent linearly away + * from the FG pixels, decaying to 0 (transparent) is an + * 8-connected distance given by %dist. If %dist == 0, + * this does a simple conversion from 1 to 8 bpp. + * (2) If &box == NULL, this returns an alpha mask that is the + * full size of pixs. Otherwise, the returned mask pixd covers + * just the FG pixels of pixs, expanded by %dist in each + * direction (if possible), and the returned box gives the + * location of the returned mask relative to pixs. + * (3) This is useful for painting through a mask and allowing + * blending of the painted image with an underlying image + * in the mask background for pixels near foreground mask pixels. + * For example, with an underlying rgb image pix1, an overlaying + * image rgb pix2, binary mask pixm, and dist > 0, this + * blending is achieved with: + * pix3 = pixMakeAlphaFromMask(pixm, dist, &box); + * boxGetGeometry(box, &x, &y, NULL, NULL); + * pix4 = pixBlendWithGrayMask(pix1, pix2, pix3, x, y); + * </pre> + */ +PIX * +pixMakeAlphaFromMask(PIX *pixs, + l_int32 dist, + BOX **pbox) +{ +l_int32 w, h; +BOX *box1, *box2; +PIX *pix1, *pixd; + + PROCNAME("pixMakeAlphaFromMask"); + + if (pbox) *pbox = NULL; + if (!pixs || pixGetDepth(pixs) != 1) + return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); + if (dist < 0) + return (PIX *)ERROR_PTR("dist must be >= 0", procName, NULL); + + /* If requested, extract just the region to be affected by the mask */ + if (pbox) { + pixClipToForeground(pixs, NULL, &box1); + if (!box1) { + L_WARNING("no ON pixels in mask\n", procName); + return pixCreateTemplate(pixs); /* all background (0) */ + } + + boxAdjustSides(box1, box1, -dist, dist, -dist, dist); + pixGetDimensions(pixs, &w, &h, NULL); + box2 = boxClipToRectangle(box1, w, h); + *pbox = box2; + pix1 = pixClipRectangle(pixs, box2, NULL); + boxDestroy(&box1); + } else { + pix1 = pixCopy(NULL, pixs); + } + + if (dist == 0) { + pixd = pixConvert1To8(NULL, pix1, 0, 255); + pixDestroy(&pix1); + return pixd; + } + + /* Blur the boundary of the input mask */ + pixInvert(pix1, pix1); + pixd = pixDistanceFunction(pix1, 8, 8, L_BOUNDARY_FG); + pixMultConstantGray(pixd, 256.0 / dist); + pixInvert(pixd, pixd); + pixDestroy(&pix1); + return pixd; +} + + +/*! + * \brief pixGetColorNearMaskBoundary() + * + * \param[in] pixs 32 bpp rgb + * \param[in] pixm 1 bpp mask, full image + * \param[in] box region of mask; typically b.b. of a component + * \param[in] dist distance into BG from mask boundary to use + * \param[out] pval average pixel value + * \param[in] debug 1 to output mask images + * \return 0 if OK, 1 on error. + * + * <pre> + * Notes: + * (1) This finds the average color in a set of pixels that are + * roughly a distance %dist from the c.c. boundary and in the + * background of the mask image. + * </pre> + */ +l_ok +pixGetColorNearMaskBoundary(PIX *pixs, + PIX *pixm, + BOX *box, + l_int32 dist, + l_uint32 *pval, + l_int32 debug) +{ +char op[64]; +l_int32 empty, bx, by; +l_float32 rval, gval, bval; +BOX *box1, *box2; +PIX *pix1, *pix2, *pix3; + + PROCNAME("pixGetColorNearMaskBoundary"); + + if (!pval) + return ERROR_INT("&pval not defined", procName, 1); + *pval = 0xffffff00; /* white */ + if (!pixs || pixGetDepth(pixs) != 32) + return ERROR_INT("pixs undefined or not 32 bpp", procName, 1); + if (!pixm || pixGetDepth(pixm) != 1) + return ERROR_INT("pixm undefined or not 1 bpp", procName, 1); + if (!box) + return ERROR_INT("box not defined", procName, 1); + if (dist < 0) + return ERROR_INT("dist must be >= 0", procName, 1); + + /* Clip mask piece, expanded beyond %box by (%dist + 5) on each side. + * box1 is the region requested; box2 is the actual region retrieved, + * which is clipped to %pixm */ + box1 = boxAdjustSides(NULL, box, -dist - 5, dist + 5, -dist - 5, dist + 5); + pix1 = pixClipRectangle(pixm, box1, &box2); + + /* Expand FG by %dist into the BG */ + if (dist == 0) { + pix2 = pixCopy(NULL, pix1); + } else { + snprintf(op, sizeof(op), "d%d.%d", 2 * dist, 2 * dist); + pix2 = pixMorphSequence(pix1, op, 0); + } + + /* Expand again by 5 pixels on all sides (dilate 11x11) and XOR, + * getting the annulus of FG pixels between %dist and %dist + 5 */ + pix3 = pixCopy(NULL, pix2); + pixDilateBrick(pix3, pix3, 11, 11); + pixXor(pix3, pix3, pix2); + pixZero(pix3, &empty); + if (!empty) { + /* Scan the same region in %pixs, to get average under FG in pix3 */ + boxGetGeometry(box2, &bx, &by, NULL, NULL); + pixGetAverageMaskedRGB(pixs, pix3, bx, by, 1, L_MEAN_ABSVAL, + &rval, &gval, &bval); + composeRGBPixel((l_int32)(rval + 0.5), (l_int32)(gval + 0.5), + (l_int32)(bval + 0.5), pval); + } else { + L_WARNING("no pixels found\n", procName); + } + + if (debug) { + lept_rmdir("masknear"); /* erase previous images */ + lept_mkdir("masknear"); + pixWriteDebug("/tmp/masknear/input.png", pix1, IFF_PNG); + pixWriteDebug("/tmp/masknear/adjusted.png", pix2, IFF_PNG); + pixWriteDebug("/tmp/masknear/outerfive.png", pix3, IFF_PNG); + lept_stderr("Input box; with adjusted sides; clipped\n"); + boxPrintStreamInfo(stderr, box); + boxPrintStreamInfo(stderr, box1); + boxPrintStreamInfo(stderr, box2); + } + + pixDestroy(&pix1); + pixDestroy(&pix2); + pixDestroy(&pix3); + boxDestroy(&box1); + boxDestroy(&box2); + return 0; +} + + +/*! + * \brief pixDisplaySelectedPixels() + * + * \param[in] pixs [optional] any depth + * \param[in] pixm 1 bpp mask, aligned UL corner with %pixs + * \param[in] sel [optional] pattern to paint at each pixel in pixm + * \param[in] val rgb rendering of pattern + * \return pixd, or NULL on error + * + * <pre> + * Notes: + * (1) For every fg pixel in %pixm, this paints the pattern in %sel + * in color %val on a copy of %pixs. + * (2) The implementation is to dilate %pixm by %sel, and then + * paint through the dilated mask onto %pixs. + * (3) If %pixs == NULL, it paints on a white image. + * (4) If %sel == NULL, it paints only the pixels in the input %pixm. + * (5) This visualization would typically be used in debugging. + * </pre> + */ +PIX * +pixDisplaySelectedPixels(PIX *pixs, + PIX *pixm, + SEL *sel, + l_uint32 val) +{ +l_int32 w, h; +PIX *pix1, *pix2; + + PROCNAME("pixDisplaySelectedPixels"); + + if (!pixm || pixGetDepth(pixm) != 1) + return (PIX *)ERROR_PTR("pixm undefined or not 1 bpp", procName, NULL); + + if (pixs) { + pix1 = pixConvertTo32(pixs); + } else { + pixGetDimensions(pixm, &w, &h, NULL); + pix1 = pixCreate(w, h, 32); + pixSetAll(pix1); + } + + if (sel) + pix2 = pixDilate(NULL, pixm, sel); + else + pix2 = pixClone(pixm); + pixSetMasked(pix1, pix2, val); + pixDestroy(&pix2); + return pix1; +} + + +/*-------------------------------------------------------------* + * One and two-image boolean ops on arbitrary depth images * + *-------------------------------------------------------------*/ +/*! + * \brief pixInvert() + * + * \param[in] pixd [optional]; this can be null, equal to pixs, + * or different from pixs + * \param[in] pixs + * \return pixd, or NULL on error + * + * <pre> + * Notes: + * (1) This inverts pixs, for all pixel depths. + * (2) There are 3 cases: + * (a) pixd == null, ~src --> new pixd + * (b) pixd == pixs, ~src --> src (in-place) + * (c) pixd != pixs, ~src --> input pixd + * (3) For clarity, if the case is known, use these patterns: + * (a) pixd = pixInvert(NULL, pixs); + * (b) pixInvert(pixs, pixs); + * (c) pixInvert(pixd, pixs); + * </pre> + */ +PIX * +pixInvert(PIX *pixd, + PIX *pixs) +{ + PROCNAME("pixInvert"); + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); + + /* Prepare pixd for in-place operation */ + if ((pixd = pixCopy(pixd, pixs)) == NULL) + return (PIX *)ERROR_PTR("pixd not made", procName, NULL); + + pixRasterop(pixd, 0, 0, pixGetWidth(pixd), pixGetHeight(pixd), + PIX_NOT(PIX_DST), NULL, 0, 0); /* invert pixd */ + + return pixd; +} + + +/*! + * \brief pixOr() + * + * \param[in] pixd [optional]; this can be null, equal to pixs1, + * different from pixs1 + * \param[in] pixs1 can be == pixd + * \param[in] pixs2 must be != pixd + * \return pixd always + * + * <pre> + * Notes: + * (1) This gives the union of two images with equal depth, + * aligning them to the the UL corner. pixs1 and pixs2 + * need not have the same width and height. + * (2) There are 3 cases: + * (a) pixd == null, (src1 | src2) --> new pixd + * (b) pixd == pixs1, (src1 | src2) --> src1 (in-place) + * (c) pixd != pixs1, (src1 | src2) --> input pixd + * (3) For clarity, if the case is known, use these patterns: + * (a) pixd = pixOr(NULL, pixs1, pixs2); + * (b) pixOr(pixs1, pixs1, pixs2); + * (c) pixOr(pixd, pixs1, pixs2); + * (4) The size of the result is determined by pixs1. + * (5) The depths of pixs1 and pixs2 must be equal. + * (6) Note carefully that the order of pixs1 and pixs2 only matters + * for the in-place case. For in-place, you must have + * pixd == pixs1. Setting pixd == pixs2 gives an incorrect + * result: the copy puts pixs1 image data in pixs2, and + * the rasterop is then between pixs2 and pixs2 (a no-op). + * </pre> + */ +PIX * +pixOr(PIX *pixd, + PIX *pixs1, + PIX *pixs2) +{ + PROCNAME("pixOr"); + + if (!pixs1) + return (PIX *)ERROR_PTR("pixs1 not defined", procName, pixd); + if (!pixs2) + return (PIX *)ERROR_PTR("pixs2 not defined", procName, pixd); + if (pixd == pixs2) + return (PIX *)ERROR_PTR("cannot have pixs2 == pixd", procName, pixd); + if (pixGetDepth(pixs1) != pixGetDepth(pixs2)) + return (PIX *)ERROR_PTR("depths of pixs* unequal", procName, pixd); + +#if EQUAL_SIZE_WARNING + if (!pixSizesEqual(pixs1, pixs2)) + L_WARNING("pixs1 and pixs2 not equal sizes\n", procName); +#endif /* EQUAL_SIZE_WARNING */ + + /* Prepare pixd to be a copy of pixs1 */ + if ((pixd = pixCopy(pixd, pixs1)) == NULL) + return (PIX *)ERROR_PTR("pixd not made", procName, pixd); + + /* src1 | src2 --> dest */ + pixRasterop(pixd, 0, 0, pixGetWidth(pixd), pixGetHeight(pixd), + PIX_SRC | PIX_DST, pixs2, 0, 0); + + return pixd; +} + + +/*! + * \brief pixAnd() + * + * \param[in] pixd [optional]; this can be null, equal to pixs1, + * different from pixs1 + * \param[in] pixs1 can be == pixd + * \param[in] pixs2 must be != pixd + * \return pixd always + * + * <pre> + * Notes: + * (1) This gives the intersection of two images with equal depth, + * aligning them to the the UL corner. pixs1 and pixs2 + * need not have the same width and height. + * (2) There are 3 cases: + * (a) pixd == null, (src1 & src2) --> new pixd + * (b) pixd == pixs1, (src1 & src2) --> src1 (in-place) + * (c) pixd != pixs1, (src1 & src2) --> input pixd + * (3) For clarity, if the case is known, use these patterns: + * (a) pixd = pixAnd(NULL, pixs1, pixs2); + * (b) pixAnd(pixs1, pixs1, pixs2); + * (c) pixAnd(pixd, pixs1, pixs2); + * (4) The size of the result is determined by pixs1. + * (5) The depths of pixs1 and pixs2 must be equal. + * (6) Note carefully that the order of pixs1 and pixs2 only matters + * for the in-place case. For in-place, you must have + * pixd == pixs1. Setting pixd == pixs2 gives an incorrect + * result: the copy puts pixs1 image data in pixs2, and + * the rasterop is then between pixs2 and pixs2 (a no-op). + * </pre> + */ +PIX * +pixAnd(PIX *pixd, + PIX *pixs1, + PIX *pixs2) +{ + PROCNAME("pixAnd"); + + if (!pixs1) + return (PIX *)ERROR_PTR("pixs1 not defined", procName, pixd); + if (!pixs2) + return (PIX *)ERROR_PTR("pixs2 not defined", procName, pixd); + if (pixd == pixs2) + return (PIX *)ERROR_PTR("cannot have pixs2 == pixd", procName, pixd); + if (pixGetDepth(pixs1) != pixGetDepth(pixs2)) + return (PIX *)ERROR_PTR("depths of pixs* unequal", procName, pixd); + +#if EQUAL_SIZE_WARNING + if (!pixSizesEqual(pixs1, pixs2)) + L_WARNING("pixs1 and pixs2 not equal sizes\n", procName); +#endif /* EQUAL_SIZE_WARNING */ + + /* Prepare pixd to be a copy of pixs1 */ + if ((pixd = pixCopy(pixd, pixs1)) == NULL) + return (PIX *)ERROR_PTR("pixd not made", procName, pixd); + + /* src1 & src2 --> dest */ + pixRasterop(pixd, 0, 0, pixGetWidth(pixd), pixGetHeight(pixd), + PIX_SRC & PIX_DST, pixs2, 0, 0); + + return pixd; +} + + +/*! + * \brief pixXor() + * + * \param[in] pixd [optional]; this can be null, equal to pixs1, + * different from pixs1 + * \param[in] pixs1 can be == pixd + * \param[in] pixs2 must be != pixd + * \return pixd always + * + * <pre> + * Notes: + * (1) This gives the XOR of two images with equal depth, + * aligning them to the the UL corner. pixs1 and pixs2 + * need not have the same width and height. + * (2) There are 3 cases: + * (a) pixd == null, (src1 ^ src2) --> new pixd + * (b) pixd == pixs1, (src1 ^ src2) --> src1 (in-place) + * (c) pixd != pixs1, (src1 ^ src2) --> input pixd + * (3) For clarity, if the case is known, use these patterns: + * (a) pixd = pixXor(NULL, pixs1, pixs2); + * (b) pixXor(pixs1, pixs1, pixs2); + * (c) pixXor(pixd, pixs1, pixs2); + * (4) The size of the result is determined by pixs1. + * (5) The depths of pixs1 and pixs2 must be equal. + * (6) Note carefully that the order of pixs1 and pixs2 only matters + * for the in-place case. For in-place, you must have + * pixd == pixs1. Setting pixd == pixs2 gives an incorrect + * result: the copy puts pixs1 image data in pixs2, and + * the rasterop is then between pixs2 and pixs2 (a no-op). + * </pre> + */ +PIX * +pixXor(PIX *pixd, + PIX *pixs1, + PIX *pixs2) +{ + PROCNAME("pixXor"); + + if (!pixs1) + return (PIX *)ERROR_PTR("pixs1 not defined", procName, pixd); + if (!pixs2) + return (PIX *)ERROR_PTR("pixs2 not defined", procName, pixd); + if (pixd == pixs2) + return (PIX *)ERROR_PTR("cannot have pixs2 == pixd", procName, pixd); + if (pixGetDepth(pixs1) != pixGetDepth(pixs2)) + return (PIX *)ERROR_PTR("depths of pixs* unequal", procName, pixd); + +#if EQUAL_SIZE_WARNING + if (!pixSizesEqual(pixs1, pixs2)) + L_WARNING("pixs1 and pixs2 not equal sizes\n", procName); +#endif /* EQUAL_SIZE_WARNING */ + + /* Prepare pixd to be a copy of pixs1 */ + if ((pixd = pixCopy(pixd, pixs1)) == NULL) + return (PIX *)ERROR_PTR("pixd not made", procName, pixd); + + /* src1 ^ src2 --> dest */ + pixRasterop(pixd, 0, 0, pixGetWidth(pixd), pixGetHeight(pixd), + PIX_SRC ^ PIX_DST, pixs2, 0, 0); + + return pixd; +} + + +/*! + * \brief pixSubtract() + * + * \param[in] pixd [optional]; this can be null, equal to pixs1, + * equal to pixs2, or different from both pixs1 and pixs2 + * \param[in] pixs1 can be == pixd + * \param[in] pixs2 can be == pixd + * \return pixd always + * + * <pre> + * Notes: + * (1) This gives the set subtraction of two images with equal depth, + * aligning them to the the UL corner. pixs1 and pixs2 + * need not have the same width and height. + * (2) Source pixs2 is always subtracted from source pixs1. + * The result is + * pixs1 \ pixs2 = pixs1 & (~pixs2) + * (3) There are 4 cases: + * (a) pixd == null, (src1 - src2) --> new pixd + * (b) pixd == pixs1, (src1 - src2) --> src1 (in-place) + * (c) pixd == pixs2, (src1 - src2) --> src2 (in-place) + * (d) pixd != pixs1 && pixd != pixs2), + * (src1 - src2) --> input pixd + * (4) For clarity, if the case is known, use these patterns: + * (a) pixd = pixSubtract(NULL, pixs1, pixs2); + * (b) pixSubtract(pixs1, pixs1, pixs2); + * (c) pixSubtract(pixs2, pixs1, pixs2); + * (d) pixSubtract(pixd, pixs1, pixs2); + * (5) The size of the result is determined by pixs1. + * (6) The depths of pixs1 and pixs2 must be equal. + * </pre> + */ +PIX * +pixSubtract(PIX *pixd, + PIX *pixs1, + PIX *pixs2) +{ +l_int32 w, h; + + PROCNAME("pixSubtract"); + + 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) != pixGetDepth(pixs2)) + return (PIX *)ERROR_PTR("depths of pixs* unequal", procName, pixd); + +#if EQUAL_SIZE_WARNING + if (!pixSizesEqual(pixs1, pixs2)) + L_WARNING("pixs1 and pixs2 not equal sizes\n", procName); +#endif /* EQUAL_SIZE_WARNING */ + + pixGetDimensions(pixs1, &w, &h, NULL); + if (!pixd) { + pixd = pixCopy(NULL, pixs1); + pixRasterop(pixd, 0, 0, w, h, PIX_DST & PIX_NOT(PIX_SRC), + pixs2, 0, 0); /* src1 & (~src2) */ + } else if (pixd == pixs1) { + pixRasterop(pixd, 0, 0, w, h, PIX_DST & PIX_NOT(PIX_SRC), + pixs2, 0, 0); /* src1 & (~src2) */ + } else if (pixd == pixs2) { + pixRasterop(pixd, 0, 0, w, h, PIX_NOT(PIX_DST) & PIX_SRC, + pixs1, 0, 0); /* src1 & (~src2) */ + } else { /* pixd != pixs1 && pixd != pixs2 */ + pixCopy(pixd, pixs1); /* sizes pixd to pixs1 if unequal */ + pixRasterop(pixd, 0, 0, w, h, PIX_DST & PIX_NOT(PIX_SRC), + pixs2, 0, 0); /* src1 & (~src2) */ + } + + return pixd; +} + + +/*-------------------------------------------------------------* + * Pixel counting * + *-------------------------------------------------------------*/ +/*! + * \brief pixZero() + * + * \param[in] pix all depths; colormap OK + * \param[out] pempty 1 if all bits in image data field are 0; 0 otherwise + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) For a binary image, if there are no fg (black) pixels, empty = 1. + * (2) For a grayscale image, if all pixels are black (0), empty = 1. + * (3) For an RGB image, if all 4 components in every pixel is 0, + * empty = 1. + * (4) For a colormapped image, pixel values are 0. The colormap + * is ignored. + * </pre> + */ +l_ok +pixZero(PIX *pix, + l_int32 *pempty) +{ +l_int32 w, h, wpl, i, j, fullwords, endbits; +l_uint32 endmask; +l_uint32 *data, *line; + + PROCNAME("pixZero"); + + if (!pempty) + return ERROR_INT("&empty not defined", procName, 1); + *pempty = 1; + if (!pix) + return ERROR_INT("pix not defined", procName, 1); + + w = pixGetWidth(pix) * pixGetDepth(pix); /* in bits */ + h = pixGetHeight(pix); + wpl = pixGetWpl(pix); + data = pixGetData(pix); + fullwords = w / 32; + endbits = w & 31; + endmask = (endbits == 0) ? 0 : (0xffffffffU << (32 - endbits)); + + for (i = 0; i < h; i++) { + line = data + wpl * i; + for (j = 0; j < fullwords; j++) + if (*line++) { + *pempty = 0; + return 0; + } + if (endbits) { + if (*line & endmask) { + *pempty = 0; + return 0; + } + } + } + + return 0; +} + + +/*! + * \brief pixForegroundFraction() + * + * \param[in] pix 1 bpp + * \param[out] pfract fraction of ON pixels + * \return 0 if OK; 1 on error + */ +l_ok +pixForegroundFraction(PIX *pix, + l_float32 *pfract) +{ +l_int32 w, h, count; + + PROCNAME("pixForegroundFraction"); + + if (!pfract) + return ERROR_INT("&fract not defined", procName, 1); + *pfract = 0.0; + if (!pix || pixGetDepth(pix) != 1) + return ERROR_INT("pix not defined or not 1 bpp", procName, 1); + + pixCountPixels(pix, &count, NULL); + pixGetDimensions(pix, &w, &h, NULL); + *pfract = (l_float32)count / (l_float32)(w * h); + return 0; +} + + +/*! + * \brief pixaCountPixels() + * + * \param[in] pixa array of 1 bpp pix + * \return na of ON pixels in each pix, or NULL on error + */ +NUMA * +pixaCountPixels(PIXA *pixa) +{ +l_int32 d, i, n, count; +l_int32 *tab; +NUMA *na; +PIX *pix; + + PROCNAME("pixaCountPixels"); + + if (!pixa) + return (NUMA *)ERROR_PTR("pix not defined", procName, NULL); + + if ((n = pixaGetCount(pixa)) == 0) + return numaCreate(1); + + pix = pixaGetPix(pixa, 0, L_CLONE); + d = pixGetDepth(pix); + pixDestroy(&pix); + if (d != 1) + return (NUMA *)ERROR_PTR("pixa not 1 bpp", procName, NULL); + + if ((na = numaCreate(n)) == NULL) + return (NUMA *)ERROR_PTR("na not made", procName, NULL); + tab = makePixelSumTab8(); + for (i = 0; i < n; i++) { + pix = pixaGetPix(pixa, i, L_CLONE); + pixCountPixels(pix, &count, tab); + numaAddNumber(na, count); + pixDestroy(&pix); + } + + LEPT_FREE(tab); + return na; +} + + +/*! + * \brief pixCountPixels() + * + * \param[in] pixs 1 bpp + * \param[out] pcount count of ON pixels + * \param[in] tab8 [optional] 8-bit pixel lookup table + * \return 0 if OK; 1 on error + */ +l_ok +pixCountPixels(PIX *pixs, + l_int32 *pcount, + l_int32 *tab8) +{ +l_uint32 endmask; +l_int32 w, h, wpl, i, j; +l_int32 fullwords, endbits, sum; +l_int32 *tab; +l_uint32 *data; + + PROCNAME("pixCountPixels"); + + if (!pcount) + return ERROR_INT("&count not defined", procName, 1); + *pcount = 0; + if (!pixs || pixGetDepth(pixs) != 1) + return ERROR_INT("pixs not defined or not 1 bpp", procName, 1); + + tab = (tab8) ? tab8 : makePixelSumTab8(); + pixGetDimensions(pixs, &w, &h, NULL); + wpl = pixGetWpl(pixs); + data = pixGetData(pixs); + fullwords = w >> 5; + endbits = w & 31; + endmask = (endbits == 0) ? 0 : (0xffffffffU << (32 - endbits)); + + sum = 0; + for (i = 0; i < h; i++, data += wpl) { + for (j = 0; j < fullwords; j++) { + l_uint32 word = data[j]; + if (word) { + sum += tab[word & 0xff] + + tab[(word >> 8) & 0xff] + + tab[(word >> 16) & 0xff] + + tab[(word >> 24) & 0xff]; + } + } + if (endbits) { + l_uint32 word = data[j] & endmask; + if (word) { + sum += tab[word & 0xff] + + tab[(word >> 8) & 0xff] + + tab[(word >> 16) & 0xff] + + tab[(word >> 24) & 0xff]; + } + } + } + *pcount = sum; + + if (!tab8) LEPT_FREE(tab); + return 0; +} + + +/*! + * \brief pixCountPixelsInRect() + * + * \param[in] pixs 1 bpp + * \param[in] box (can be null) + * \param[out] pcount count of ON pixels + * \param[in] tab8 [optional] 8-bit pixel lookup table + * \return 0 if OK; 1 on error + */ +l_ok +pixCountPixelsInRect(PIX *pixs, + BOX *box, + l_int32 *pcount, + l_int32 *tab8) +{ +l_int32 bx, by, bw, bh; +PIX *pix1; + + PROCNAME("pixCountPixelsInRect"); + + if (!pcount) + return ERROR_INT("&count not defined", procName, 1); + *pcount = 0; + if (!pixs || pixGetDepth(pixs) != 1) + return ERROR_INT("pixs not defined or not 1 bpp", procName, 1); + + if (box) { + boxGetGeometry(box, &bx, &by, &bw, &bh); + pix1 = pixCreate(bw, bh, 1); + pixRasterop(pix1, 0, 0, bw, bh, PIX_SRC, pixs, bx, by); + pixCountPixels(pix1, pcount, tab8); + pixDestroy(&pix1); + } else { + pixCountPixels(pixs, pcount, tab8); + } + + return 0; +} + + +/*! + * \brief pixCountByRow() + * + * \param[in] pix 1 bpp + * \param[in] box [optional] clipping box for count; can be null + * \return na of number of ON pixels by row, or NULL on error + * + * <pre> + * Notes: + * (1) To resample for a bin size different from 1, use + * numaUniformSampling() on the result of this function. + * </pre> + */ +NUMA * +pixCountByRow(PIX *pix, + BOX *box) +{ +l_int32 i, j, w, h, wpl, count, xstart, xend, ystart, yend, bw, bh; +l_uint32 *line, *data; +NUMA *na; + + PROCNAME("pixCountByRow"); + + if (!pix || pixGetDepth(pix) != 1) + return (NUMA *)ERROR_PTR("pix undefined or not 1 bpp", procName, NULL); + if (!box) + return pixCountPixelsByRow(pix, NULL); + + pixGetDimensions(pix, &w, &h, NULL); + if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, ¥d, + &bw, &bh) == 1) + return (NUMA *)ERROR_PTR("invalid clipping box", procName, NULL); + + if ((na = numaCreate(bh)) == NULL) + return (NUMA *)ERROR_PTR("na not made", procName, NULL); + numaSetParameters(na, ystart, 1); + data = pixGetData(pix); + wpl = pixGetWpl(pix); + for (i = ystart; i < yend; i++) { + count = 0; + line = data + i * wpl; + for (j = xstart; j < xend; j++) { + if (GET_DATA_BIT(line, j)) + count++; + } + numaAddNumber(na, count); + } + + return na; +} + + +/*! + * \brief pixCountByColumn() + * + * \param[in] pix 1 bpp + * \param[in] box [optional] clipping box for count; can be null + * \return na of number of ON pixels by column, or NULL on error + * + * <pre> + * Notes: + * (1) To resample for a bin size different from 1, use + * numaUniformSampling() on the result of this function. + * </pre> + */ +NUMA * +pixCountByColumn(PIX *pix, + BOX *box) +{ +l_int32 i, j, w, h, wpl, count, xstart, xend, ystart, yend, bw, bh; +l_uint32 *line, *data; +NUMA *na; + + PROCNAME("pixCountByColumn"); + + if (!pix || pixGetDepth(pix) != 1) + return (NUMA *)ERROR_PTR("pix undefined or not 1 bpp", procName, NULL); + if (!box) + return pixCountPixelsByColumn(pix); + + pixGetDimensions(pix, &w, &h, NULL); + if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, ¥d, + &bw, &bh) == 1) + return (NUMA *)ERROR_PTR("invalid clipping box", procName, NULL); + + if ((na = numaCreate(bw)) == NULL) + return (NUMA *)ERROR_PTR("na not made", procName, NULL); + numaSetParameters(na, xstart, 1); + data = pixGetData(pix); + wpl = pixGetWpl(pix); + for (j = xstart; j < xend; j++) { + count = 0; + for (i = ystart; i < yend; i++) { + line = data + i * wpl; + if (GET_DATA_BIT(line, j)) + count++; + } + numaAddNumber(na, count); + } + + return na; +} + + +/*! + * \brief pixCountPixelsByRow() + * + * \param[in] pix 1 bpp + * \param[in] tab8 [optional] 8-bit pixel lookup table + * \return na of counts, or NULL on error + */ +NUMA * +pixCountPixelsByRow(PIX *pix, + l_int32 *tab8) +{ +l_int32 h, i, count; +l_int32 *tab; +NUMA *na; + + PROCNAME("pixCountPixelsByRow"); + + if (!pix || pixGetDepth(pix) != 1) + return (NUMA *)ERROR_PTR("pix undefined or not 1 bpp", procName, NULL); + + h = pixGetHeight(pix); + if ((na = numaCreate(h)) == NULL) + return (NUMA *)ERROR_PTR("na not made", procName, NULL); + + tab = (tab8) ? tab8 : makePixelSumTab8(); + for (i = 0; i < h; i++) { + pixCountPixelsInRow(pix, i, &count, tab); + numaAddNumber(na, count); + } + + if (!tab8) LEPT_FREE(tab); + return na; +} + + +/*! + * \brief pixCountPixelsByColumn() + * + * \param[in] pix 1 bpp + * \return na of counts in each column, or NULL on error + */ +NUMA * +pixCountPixelsByColumn(PIX *pix) +{ +l_int32 i, j, w, h, wpl; +l_uint32 *line, *data; +l_float32 *array; +NUMA *na; + + PROCNAME("pixCountPixelsByColumn"); + + if (!pix || pixGetDepth(pix) != 1) + return (NUMA *)ERROR_PTR("pix undefined or not 1 bpp", procName, NULL); + + pixGetDimensions(pix, &w, &h, NULL); + if ((na = numaCreate(w)) == NULL) + return (NUMA *)ERROR_PTR("na not made", procName, NULL); + numaSetCount(na, w); + array = numaGetFArray(na, L_NOCOPY); + data = pixGetData(pix); + wpl = pixGetWpl(pix); + for (i = 0; i < h; i++) { + line = data + wpl * i; + for (j = 0; j < w; j++) { + if (GET_DATA_BIT(line, j)) + array[j] += 1.0; + } + } + + return na; +} + + +/*! + * \brief pixCountPixelsInRow() + * + * \param[in] pix 1 bpp + * \param[in] row number + * \param[out] pcount sum of ON pixels in raster line + * \param[in] tab8 [optional] 8-bit pixel lookup table + * \return 0 if OK; 1 on error + */ +l_ok +pixCountPixelsInRow(PIX *pix, + l_int32 row, + l_int32 *pcount, + l_int32 *tab8) +{ +l_uint32 word, endmask; +l_int32 j, w, h, wpl; +l_int32 fullwords, endbits, sum; +l_int32 *tab; +l_uint32 *line; + + PROCNAME("pixCountPixelsInRow"); + + if (!pcount) + return ERROR_INT("&count not defined", procName, 1); + *pcount = 0; + if (!pix || pixGetDepth(pix) != 1) + return ERROR_INT("pix not defined or not 1 bpp", procName, 1); + + pixGetDimensions(pix, &w, &h, NULL); + if (row < 0 || row >= h) + return ERROR_INT("row out of bounds", procName, 1); + wpl = pixGetWpl(pix); + line = pixGetData(pix) + row * wpl; + fullwords = w >> 5; + endbits = w & 31; + endmask = (endbits == 0) ? 0 : (0xffffffffU << (32 - endbits)); + + tab = (tab8) ? tab8 : makePixelSumTab8(); + sum = 0; + for (j = 0; j < fullwords; j++) { + word = line[j]; + if (word) { + sum += tab[word & 0xff] + + tab[(word >> 8) & 0xff] + + tab[(word >> 16) & 0xff] + + tab[(word >> 24) & 0xff]; + } + } + if (endbits) { + word = line[j] & endmask; + if (word) { + sum += tab[word & 0xff] + + tab[(word >> 8) & 0xff] + + tab[(word >> 16) & 0xff] + + tab[(word >> 24) & 0xff]; + } + } + *pcount = sum; + + if (!tab8) LEPT_FREE(tab); + return 0; +} + + +/*! + * \brief pixGetMomentByColumn() + * + * \param[in] pix 1 bpp + * \param[in] order of moment, either 1 or 2 + * \return na of first moment of fg pixels, by column, or NULL on error + */ +NUMA * +pixGetMomentByColumn(PIX *pix, + l_int32 order) +{ +l_int32 i, j, w, h, wpl; +l_uint32 *line, *data; +l_float32 *array; +NUMA *na; + + PROCNAME("pixGetMomentByColumn"); + + if (!pix || pixGetDepth(pix) != 1) + return (NUMA *)ERROR_PTR("pix undefined or not 1 bpp", procName, NULL); + if (order != 1 && order != 2) + return (NUMA *)ERROR_PTR("order of moment not 1 or 2", procName, NULL); + + pixGetDimensions(pix, &w, &h, NULL); + if ((na = numaCreate(w)) == NULL) + return (NUMA *)ERROR_PTR("na not made", procName, NULL); + numaSetCount(na, w); + array = numaGetFArray(na, L_NOCOPY); + data = pixGetData(pix); + wpl = pixGetWpl(pix); + for (i = 0; i < h; i++) { + line = data + wpl * i; + for (j = 0; j < w; j++) { + if (GET_DATA_BIT(line, j)) { + if (order == 1) + array[j] += i; + else /* order == 2 */ + array[j] += i * i; + } + } + } + + return na; +} + + +/*! + * \brief pixThresholdPixelSum() + * + * \param[in] pix 1 bpp + * \param[in] thresh threshold + * \param[out] pabove 1 if above threshold; + * 0 if equal to or less than threshold + * \param[in] tab8 [optional] 8-bit pixel lookup table + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) This sums the ON pixels and returns immediately if the count + * goes above threshold. It is therefore more efficient + * for matching images (by running this function on the xor of + * the 2 images) than using pixCountPixels(), which counts all + * pixels before returning. + * </pre> + */ +l_ok +pixThresholdPixelSum(PIX *pix, + l_int32 thresh, + l_int32 *pabove, + l_int32 *tab8) +{ +l_uint32 word, endmask; +l_int32 *tab; +l_int32 w, h, wpl, i, j; +l_int32 fullwords, endbits, sum; +l_uint32 *line, *data; + + PROCNAME("pixThresholdPixelSum"); + + if (!pabove) + return ERROR_INT("&above not defined", procName, 1); + *pabove = 0; + if (!pix || pixGetDepth(pix) != 1) + return ERROR_INT("pix not defined or not 1 bpp", procName, 1); + + tab = (tab8) ? tab8 : makePixelSumTab8(); + pixGetDimensions(pix, &w, &h, NULL); + wpl = pixGetWpl(pix); + data = pixGetData(pix); + fullwords = w >> 5; + endbits = w & 31; + endmask = 0xffffffff << (32 - endbits); + + sum = 0; + for (i = 0; i < h; i++) { + line = data + wpl * i; + for (j = 0; j < fullwords; j++) { + word = line[j]; + if (word) { + sum += tab[word & 0xff] + + tab[(word >> 8) & 0xff] + + tab[(word >> 16) & 0xff] + + tab[(word >> 24) & 0xff]; + } + } + if (endbits) { + word = line[j] & endmask; + if (word) { + sum += tab[word & 0xff] + + tab[(word >> 8) & 0xff] + + tab[(word >> 16) & 0xff] + + tab[(word >> 24) & 0xff]; + } + } + if (sum > thresh) { + *pabove = 1; + if (!tab8) LEPT_FREE(tab); + return 0; + } + } + + if (!tab8) LEPT_FREE(tab); + return 0; +} + + +/*! + * \brief makePixelSumTab8() + * + * \return table of 256 l_int32. + * + * <pre> + * Notes: + * (1) This table of integers gives the number of 1 bits + * in the 8 bit index. + * </pre> + */ +l_int32 * +makePixelSumTab8(void) +{ +l_uint8 byte; +l_int32 i; +l_int32 *tab; + + tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32)); + for (i = 0; i < 256; i++) { + byte = (l_uint8)i; + tab[i] = (byte & 0x1) + + ((byte >> 1) & 0x1) + + ((byte >> 2) & 0x1) + + ((byte >> 3) & 0x1) + + ((byte >> 4) & 0x1) + + ((byte >> 5) & 0x1) + + ((byte >> 6) & 0x1) + + ((byte >> 7) & 0x1); + } + return tab; +} + + +/*! + * \brief makePixelCentroidTab8() + * + * \return table of 256 l_int32. + * + * <pre> + * Notes: + * (1) This table of integers gives the centroid weight of the 1 bits + * in the 8 bit index. In other words, if sumtab is obtained by + * makePixelSumTab8, and centroidtab is obtained by + * makePixelCentroidTab8, then, for 1 <= i <= 255, + * centroidtab[i] / (float)sumtab[i] + * is the centroid of the 1 bits in the 8-bit index i, where the + * MSB is considered to have position 0 and the LSB is considered + * to have position 7. + * </pre> + */ +l_int32 * +makePixelCentroidTab8(void) +{ +l_int32 i; +l_int32 *tab; + + tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32)); + tab[0] = 0; + tab[1] = 7; + for (i = 2; i < 4; i++) { + tab[i] = tab[i - 2] + 6; + } + for (i = 4; i < 8; i++) { + tab[i] = tab[i - 4] + 5; + } + for (i = 8; i < 16; i++) { + tab[i] = tab[i - 8] + 4; + } + for (i = 16; i < 32; i++) { + tab[i] = tab[i - 16] + 3; + } + for (i = 32; i < 64; i++) { + tab[i] = tab[i - 32] + 2; + } + for (i = 64; i < 128; i++) { + tab[i] = tab[i - 64] + 1; + } + for (i = 128; i < 256; i++) { + tab[i] = tab[i - 128]; + } + return tab; +} + + +/*-------------------------------------------------------------* + * Average of pixel values in gray images * + *-------------------------------------------------------------*/ +/*! + * \brief pixAverageByRow() + * + * \param[in] pix 8 or 16 bpp; no colormap + * \param[in] box [optional] clipping box for sum; can be null + * \param[in] type L_WHITE_IS_MAX, L_BLACK_IS_MAX + * \return na of pixel averages by row, or NULL on error + * + * <pre> + * Notes: + * (1) To resample for a bin size different from 1, use + * numaUniformSampling() on the result of this function. + * (2) If type == L_BLACK_IS_MAX, black pixels get the maximum + * value (0xff for 8 bpp, 0xffff for 16 bpp) and white get 0. + * </pre> + */ +NUMA * +pixAverageByRow(PIX *pix, + BOX *box, + l_int32 type) +{ +l_int32 i, j, w, h, d, wpl, xstart, xend, ystart, yend, bw, bh; +l_uint32 *line, *data; +l_float64 norm, sum; +NUMA *na; + + PROCNAME("pixAverageByRow"); + + if (!pix) + return (NUMA *)ERROR_PTR("pix not defined", procName, NULL); + pixGetDimensions(pix, &w, &h, &d); + if (d != 8 && d != 16) + return (NUMA *)ERROR_PTR("pix not 8 or 16 bpp", procName, NULL); + if (type != L_WHITE_IS_MAX && type != L_BLACK_IS_MAX) + return (NUMA *)ERROR_PTR("invalid type", procName, NULL); + if (pixGetColormap(pix) != NULL) + return (NUMA *)ERROR_PTR("pix colormapped", procName, NULL); + + if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, ¥d, + &bw, &bh) == 1) + return (NUMA *)ERROR_PTR("invalid clipping box", procName, NULL); + + norm = 1. / (l_float32)bw; + if ((na = numaCreate(bh)) == NULL) + return (NUMA *)ERROR_PTR("na not made", procName, NULL); + numaSetParameters(na, ystart, 1); + data = pixGetData(pix); + wpl = pixGetWpl(pix); + for (i = ystart; i < yend; i++) { + sum = 0.0; + line = data + i * wpl; + if (d == 8) { + for (j = xstart; j < xend; j++) + sum += GET_DATA_BYTE(line, j); + if (type == L_BLACK_IS_MAX) + sum = bw * 255 - sum; + } else { /* d == 16 */ + for (j = xstart; j < xend; j++) + sum += GET_DATA_TWO_BYTES(line, j); + if (type == L_BLACK_IS_MAX) + sum = bw * 0xffff - sum; + } + numaAddNumber(na, (l_float32)(norm * sum)); + } + + return na; +} + + +/*! + * \brief pixAverageByColumn() + * + * \param[in] pix 8 or 16 bpp; no colormap + * \param[in] box [optional] clipping box for sum; can be null + * \param[in] type L_WHITE_IS_MAX, L_BLACK_IS_MAX + * \return na of pixel averages by column, or NULL on error + * + * <pre> + * Notes: + * (1) To resample for a bin size different from 1, use + * numaUniformSampling() on the result of this function. + * (2) If type == L_BLACK_IS_MAX, black pixels get the maximum + * value (0xff for 8 bpp, 0xffff for 16 bpp) and white get 0. + * </pre> + */ +NUMA * +pixAverageByColumn(PIX *pix, + BOX *box, + l_int32 type) +{ +l_int32 i, j, w, h, d, wpl, xstart, xend, ystart, yend, bw, bh; +l_uint32 *line, *data; +l_float32 norm, sum; +NUMA *na; + + PROCNAME("pixAverageByColumn"); + + if (!pix) + return (NUMA *)ERROR_PTR("pix not defined", procName, NULL); + pixGetDimensions(pix, &w, &h, &d); + + if (d != 8 && d != 16) + return (NUMA *)ERROR_PTR("pix not 8 or 16 bpp", procName, NULL); + if (type != L_WHITE_IS_MAX && type != L_BLACK_IS_MAX) + return (NUMA *)ERROR_PTR("invalid type", procName, NULL); + if (pixGetColormap(pix) != NULL) + return (NUMA *)ERROR_PTR("pix colormapped", procName, NULL); + + if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, ¥d, + &bw, &bh) == 1) + return (NUMA *)ERROR_PTR("invalid clipping box", procName, NULL); + + if ((na = numaCreate(bw)) == NULL) + return (NUMA *)ERROR_PTR("na not made", procName, NULL); + numaSetParameters(na, xstart, 1); + norm = 1. / (l_float32)bh; + data = pixGetData(pix); + wpl = pixGetWpl(pix); + for (j = xstart; j < xend; j++) { + sum = 0.0; + if (d == 8) { + for (i = ystart; i < yend; i++) { + line = data + i * wpl; + sum += GET_DATA_BYTE(line, j); + } + if (type == L_BLACK_IS_MAX) + sum = bh * 255 - sum; + } else { /* d == 16 */ + for (i = ystart; i < yend; i++) { + line = data + i * wpl; + sum += GET_DATA_TWO_BYTES(line, j); + } + if (type == L_BLACK_IS_MAX) + sum = bh * 0xffff - sum; + } + numaAddNumber(na, (l_float32)(norm * sum)); + } + + return na; +} + + +/*! + * \brief pixAverageInRect() + * + * \param[in] pixs 1, 2, 4, 8 bpp; not cmapped + * \param[in] pixm [optional] 1 bpp mask; if null, use all pixels + * \param[in] box [optional] if null, use entire image + * \param[in] minval ignore values less than this + * \param[in] maxval ignore values greater than this + * \param[in] subsamp subsample factor: integer; use 1 for all pixels + * \param[out] pave average of pixel values under consideration + * \return 0 if OK; 1 on error; 2 if all pixels are filtered out + * + * <pre> + * Notes: + * (1) The average is computed with 4 optional filters: a rectangle, + * a mask, a contiguous set of range values, and subsampling. + * In practice you might use only one or two of these. + * (2) The mask %pixm is a blocking mask: only count pixels in the bg. + * If it exists, alignment is assumed at UL corner and computation + * is over the minimum intersection of %pixs and %pixm. + * If you want the average of pixels under the mask fg, invert it. + * (3) Set the range limits %minval = 0 and %maxval = 255 to use + * all non-masked pixels (regardless of value) in the average. + * (4) If no pixels are used in the averaging, the returned average + * value is 0 and the function returns 2. This is not an error, + * but it says to disregard the returned average value. + * (5) For example, to average all pixels in a given clipping rect %box, + * pixAverageInRect(pixs, NULL, box, 0, 255, 1, &aveval); + * </pre> + */ +l_ok +pixAverageInRect(PIX *pixs, + PIX *pixm, + BOX *box, + l_int32 minval, + l_int32 maxval, + l_int32 subsamp, + l_float32 *pave) +{ +l_int32 w, h, d, wpls, wm, hm, dm, wplm, val, count; +l_int32 i, j, xstart, xend, ystart, yend; +l_uint32 *datas, *datam, *lines, *linem; +l_float64 sum; + + PROCNAME("pixAverageInRect"); + + if (!pave) + return ERROR_INT("&ave not defined", procName, 1); + *pave = 0; + if (!pixs) + return ERROR_INT("pixs not defined", procName, 1); + if (pixGetColormap(pixs) != NULL) + return ERROR_INT("pixs is colormapped", procName, 1); + pixGetDimensions(pixs, &w, &h, &d); + if (d != 1 && d != 2 && d != 4 && d != 8) + return ERROR_INT("pixs not 1, 2, 4 or 8 bpp", procName, 1); + if (pixm) { + pixGetDimensions(pixm, &wm, &hm, &dm); + if (dm != 1) + return ERROR_INT("pixm not 1 bpp", procName, 1); + w = L_MIN(w, wm); + h = L_MIN(h, hm); + } + if (subsamp < 1) + return ERROR_INT("subsamp must be >= 1", procName, 1); + + if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, ¥d, + NULL, NULL) == 1) + return ERROR_INT("invalid clipping box", procName, 1); + + datas = pixGetData(pixs); + wpls = pixGetWpl(pixs); + if (pixm) { + datam = pixGetData(pixm); + wplm = pixGetWpl(pixm); + } + sum = 0.0; + count = 0; + for (i = ystart; i < yend; i += subsamp) { + lines = datas + i * wpls; + if (pixm) + linem = datam + i * wplm; + for (j = xstart; j < xend; j += subsamp) { + if (pixm && (GET_DATA_BIT(linem, j) == 1)) + continue; + if (d == 1) + val = GET_DATA_BIT(lines, j); + else if (d == 2) + val = GET_DATA_DIBIT(lines, j); + else if (d == 4) + val = GET_DATA_QBIT(lines, j); + else /* d == 8 */ + val = GET_DATA_BYTE(lines, j); + if (val >= minval && val <= maxval) { + sum += val; + count++; + } + } + } + + if (count == 0) + return 2; /* not an error; don't use the average value (0.0) */ + *pave = sum / (l_float32)count; + return 0; +} + + +/*-------------------------------------------------------------* + * Average of pixel values in RGB images * + *-------------------------------------------------------------*/ +/*! + * \brief pixAverageInRectRGB() + * + * \param[in] pixs rgb; not cmapped + * \param[in] pixm [optional] 1 bpp mask; if null, use all pixels + * \param[in] box [optional] if null, use entire image + * \param[in] subsamp subsample factor: integer; use 1 for all pixels + * \param[out] pave average color of pixel values under consideration, + * in format 0xrrggbb00. + * \return 0 if OK; 1 on error; 2 if all pixels are filtered out + * + * <pre> + * Notes: + * (1) The average is computed with 3 optional filters: a rectangle, + * a mask, and subsampling. + * In practice you might use only one or two of these. + * (2) The mask %pixm is a blocking mask: only count pixels in the bg. + * If it exists, alignment is assumed at UL corner and computation + * is over the minimum intersection of %pixs and %pixm. + * If you want the average of pixels under the mask fg, invert it. + * (3) If no pixels are used in the averaging, the returned average + * value is 0 and the function returns 2. This is not an error, + * but it says to disregard the returned average value. + * (4) For example, to average all pixels in a given clipping rect %box, + * pixAverageInRectRGB(pixs, NULL, box, 1, &aveval); + * </pre> + */ +l_ok +pixAverageInRectRGB(PIX *pixs, + PIX *pixm, + BOX *box, + l_int32 subsamp, + l_uint32 *pave) +{ +l_int32 w, h, wpls, wm, hm, dm, wplm, i, j, xstart, xend, ystart, yend; +l_int32 rval, gval, bval, rave, gave, bave, count; +l_uint32 *datas, *datam, *lines, *linem; +l_uint32 pixel; +l_float64 rsum, gsum, bsum; + + PROCNAME("pixAverageInRectRGB"); + + if (!pave) + return ERROR_INT("&ave not defined", procName, 1); + *pave = 0; + if (!pixs || pixGetDepth(pixs) != 32) + return ERROR_INT("pixs undefined or not 32 bpp", procName, 1); + pixGetDimensions(pixs, &w, &h, NULL); + if (pixm) { + pixGetDimensions(pixm, &wm, &hm, &dm); + if (dm != 1) + return ERROR_INT("pixm not 1 bpp", procName, 1); + w = L_MIN(w, wm); + h = L_MIN(h, hm); + } + if (subsamp < 1) + return ERROR_INT("subsamp must be >= 1", procName, 1); + + if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, ¥d, + NULL, NULL) == 1) + return ERROR_INT("invalid clipping box", procName, 1); + + datas = pixGetData(pixs); + wpls = pixGetWpl(pixs); + if (pixm) { + datam = pixGetData(pixm); + wplm = pixGetWpl(pixm); + } + rsum = gsum = bsum = 0.0; + count = 0; + for (i = ystart; i < yend; i += subsamp) { + lines = datas + i * wpls; + if (pixm) + linem = datam + i * wplm; + for (j = xstart; j < xend; j += subsamp) { + if (pixm && (GET_DATA_BIT(linem, j) == 1)) + continue; + pixel = *(lines + j); + extractRGBValues(pixel, &rval, &gval, &bval); + rsum += rval; + gsum += gval; + bsum += bval; + count++; + } + } + + if (count == 0) + return 2; /* not an error */ + rave = (l_uint32)(rsum / (l_float64)count); + gave = (l_uint32)(gsum / (l_float64)count); + bave = (l_uint32)(bsum / (l_float64)count); + composeRGBPixel(rave, gave, bave, pave); + return 0; +} + + +/*------------------------------------------------------------------* + * Variance of pixel values in gray images * + *------------------------------------------------------------------*/ +/*! + * \brief pixVarianceByRow() + * + * \param[in] pix 8 or 16 bpp; no colormap + * \param[in] box [optional] clipping box for variance; can be null + * \return na of rmsdev by row, or NULL on error + * + * <pre> + * Notes: + * (1) To resample for a bin size different from 1, use + * numaUniformSampling() on the result of this function. + * (2) We are actually computing the RMS deviation in each row. + * This is the square root of the variance. + * </pre> + */ +NUMA * +pixVarianceByRow(PIX *pix, + BOX *box) +{ +l_int32 i, j, w, h, d, wpl, xstart, xend, ystart, yend, bw, bh, val; +l_uint32 *line, *data; +l_float64 sum1, sum2, norm, ave, var, rootvar; +NUMA *na; + + PROCNAME("pixVarianceByRow"); + + if (!pix) + return (NUMA *)ERROR_PTR("pix not defined", procName, NULL); + pixGetDimensions(pix, &w, &h, &d); + if (d != 8 && d != 16) + return (NUMA *)ERROR_PTR("pix not 8 or 16 bpp", procName, NULL); + if (pixGetColormap(pix) != NULL) + return (NUMA *)ERROR_PTR("pix colormapped", procName, NULL); + + if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, ¥d, + &bw, &bh) == 1) + return (NUMA *)ERROR_PTR("invalid clipping box", procName, NULL); + + if ((na = numaCreate(bh)) == NULL) + return (NUMA *)ERROR_PTR("na not made", procName, NULL); + numaSetParameters(na, ystart, 1); + norm = 1. / (l_float32)bw; + data = pixGetData(pix); + wpl = pixGetWpl(pix); + for (i = ystart; i < yend; i++) { + sum1 = sum2 = 0.0; + line = data + i * wpl; + for (j = xstart; j < xend; j++) { + if (d == 8) + val = GET_DATA_BYTE(line, j); + else /* d == 16 */ + val = GET_DATA_TWO_BYTES(line, j); + sum1 += val; + sum2 += (l_float64)(val) * val; + } + ave = norm * sum1; + var = norm * sum2 - ave * ave; + rootvar = sqrt(var); + numaAddNumber(na, (l_float32)rootvar); + } + + return na; +} + + +/*! + * \brief pixVarianceByColumn() + * + * \param[in] pix 8 or 16 bpp; no colormap + * \param[in] box [optional] clipping box for variance; can be null + * \return na of rmsdev by column, or NULL on error + * + * <pre> + * Notes: + * (1) To resample for a bin size different from 1, use + * numaUniformSampling() on the result of this function. + * (2) We are actually computing the RMS deviation in each row. + * This is the square root of the variance. + * </pre> + */ +NUMA * +pixVarianceByColumn(PIX *pix, + BOX *box) +{ +l_int32 i, j, w, h, d, wpl, xstart, xend, ystart, yend, bw, bh, val; +l_uint32 *line, *data; +l_float64 sum1, sum2, norm, ave, var, rootvar; +NUMA *na; + + PROCNAME("pixVarianceByColumn"); + + if (!pix) + return (NUMA *)ERROR_PTR("pix not defined", procName, NULL); + pixGetDimensions(pix, &w, &h, &d); + if (d != 8 && d != 16) + return (NUMA *)ERROR_PTR("pix not 8 or 16 bpp", procName, NULL); + if (pixGetColormap(pix) != NULL) + return (NUMA *)ERROR_PTR("pix colormapped", procName, NULL); + + if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, ¥d, + &bw, &bh) == 1) + return (NUMA *)ERROR_PTR("invalid clipping box", procName, NULL); + + if ((na = numaCreate(bw)) == NULL) + return (NUMA *)ERROR_PTR("na not made", procName, NULL); + numaSetParameters(na, xstart, 1); + norm = 1. / (l_float32)bh; + data = pixGetData(pix); + wpl = pixGetWpl(pix); + for (j = xstart; j < xend; j++) { + sum1 = sum2 = 0.0; + for (i = ystart; i < yend; i++) { + line = data + wpl * i; + if (d == 8) + val = GET_DATA_BYTE(line, j); + else /* d == 16 */ + val = GET_DATA_TWO_BYTES(line, j); + sum1 += val; + sum2 += (l_float64)(val) * val; + } + ave = norm * sum1; + var = norm * sum2 - ave * ave; + rootvar = sqrt(var); + numaAddNumber(na, (l_float32)rootvar); + } + + return na; +} + + +/*! + * \brief pixVarianceInRect() + * + * \param[in] pix 1, 2, 4, 8 bpp; not cmapped + * \param[in] box [optional] if null, use entire image + * \param[out] prootvar sqrt variance of pixel values in region + * \return 0 if OK; 1 on error + */ +l_ok +pixVarianceInRect(PIX *pix, + BOX *box, + l_float32 *prootvar) +{ +l_int32 w, h, d, wpl, i, j, xstart, xend, ystart, yend, bw, bh, val; +l_uint32 *data, *line; +l_float64 sum1, sum2, norm, ave, var; + + PROCNAME("pixVarianceInRect"); + + if (!prootvar) + return ERROR_INT("&rootvar not defined", procName, 1); + *prootvar = 0.0; + if (!pix) + return ERROR_INT("pix not defined", procName, 1); + pixGetDimensions(pix, &w, &h, &d); + if (d != 1 && d != 2 && d != 4 && d != 8) + return ERROR_INT("pix not 1, 2, 4 or 8 bpp", procName, 1); + if (pixGetColormap(pix) != NULL) + return ERROR_INT("pix is colormapped", procName, 1); + + if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, ¥d, + &bw, &bh) == 1) + return ERROR_INT("invalid clipping box", procName, 1); + + wpl = pixGetWpl(pix); + data = pixGetData(pix); + sum1 = sum2 = 0.0; + for (i = ystart; i < yend; i++) { + line = data + i * wpl; + for (j = xstart; j < xend; j++) { + if (d == 1) { + val = GET_DATA_BIT(line, j); + sum1 += val; + sum2 += (l_float64)(val) * val; + } else if (d == 2) { + val = GET_DATA_DIBIT(line, j); + sum1 += val; + sum2 += (l_float64)(val) * val; + } else if (d == 4) { + val = GET_DATA_QBIT(line, j); + sum1 += val; + sum2 += (l_float64)(val) * val; + } else { /* d == 8 */ + val = GET_DATA_BYTE(line, j); + sum1 += val; + sum2 += (l_float64)(val) * val; + } + } + } + norm = 1.0 / ((l_float64)(bw) * bh); + ave = norm * sum1; + var = norm * sum2 - ave * ave; + *prootvar = (l_float32)sqrt(var); + return 0; +} + + +/*---------------------------------------------------------------------* + * Average of absolute value of pixel differences in gray images * + *---------------------------------------------------------------------*/ +/*! + * \brief pixAbsDiffByRow() + * + * \param[in] pix 8 bpp; no colormap + * \param[in] box [optional] clipping box for region; can be null + * \return na of abs val pixel difference averages by row, or NULL on error + * + * <pre> + * Notes: + * (1) This is an average over differences of adjacent pixels along + * each row. + * (2) To resample for a bin size different from 1, use + * numaUniformSampling() on the result of this function. + * </pre> + */ +NUMA * +pixAbsDiffByRow(PIX *pix, + BOX *box) +{ +l_int32 i, j, w, h, wpl, xstart, xend, ystart, yend, bw, bh, val0, val1; +l_uint32 *line, *data; +l_float64 norm, sum; +NUMA *na; + + PROCNAME("pixAbsDiffByRow"); + + if (!pix || pixGetDepth(pix) != 8) + return (NUMA *)ERROR_PTR("pix undefined or not 8 bpp", procName, NULL); + if (pixGetColormap(pix) != NULL) + return (NUMA *)ERROR_PTR("pix colormapped", procName, NULL); + + pixGetDimensions(pix, &w, &h, NULL); + if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, ¥d, + &bw, &bh) == 1) + return (NUMA *)ERROR_PTR("invalid clipping box", procName, NULL); + if (bw < 2) + return (NUMA *)ERROR_PTR("row width must be >= 2", procName, NULL); + + norm = 1. / (l_float32)(bw - 1); + if ((na = numaCreate(bh)) == NULL) + return (NUMA *)ERROR_PTR("na not made", procName, NULL); + numaSetParameters(na, ystart, 1); + data = pixGetData(pix); + wpl = pixGetWpl(pix); + for (i = ystart; i < yend; i++) { + sum = 0.0; + line = data + i * wpl; + val0 = GET_DATA_BYTE(line, xstart); + for (j = xstart + 1; j < xend; j++) { + val1 = GET_DATA_BYTE(line, j); + sum += L_ABS(val1 - val0); + val0 = val1; + } + numaAddNumber(na, (l_float32)(norm * sum)); + } + + return na; +} + + +/*! + * \brief pixAbsDiffByColumn() + * + * \param[in] pix 8 bpp; no colormap + * \param[in] box [optional] clipping box for region; can be null + * \return na of abs val pixel difference averages by column, + * or NULL on error + * + * <pre> + * Notes: + * (1) This is an average over differences of adjacent pixels along + * each column. + * (2) To resample for a bin size different from 1, use + * numaUniformSampling() on the result of this function. + * </pre> + */ +NUMA * +pixAbsDiffByColumn(PIX *pix, + BOX *box) +{ +l_int32 i, j, w, h, wpl, xstart, xend, ystart, yend, bw, bh, val0, val1; +l_uint32 *line, *data; +l_float64 norm, sum; +NUMA *na; + + PROCNAME("pixAbsDiffByColumn"); + + if (!pix || pixGetDepth(pix) != 8) + return (NUMA *)ERROR_PTR("pix undefined or not 8 bpp", procName, NULL); + if (pixGetColormap(pix) != NULL) + return (NUMA *)ERROR_PTR("pix colormapped", procName, NULL); + + pixGetDimensions(pix, &w, &h, NULL); + if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, ¥d, + &bw, &bh) == 1) + return (NUMA *)ERROR_PTR("invalid clipping box", procName, NULL); + if (bh < 2) + return (NUMA *)ERROR_PTR("column height must be >= 2", procName, NULL); + + norm = 1. / (l_float32)(bh - 1); + if ((na = numaCreate(bw)) == NULL) + return (NUMA *)ERROR_PTR("na not made", procName, NULL); + numaSetParameters(na, xstart, 1); + data = pixGetData(pix); + wpl = pixGetWpl(pix); + for (j = xstart; j < xend; j++) { + sum = 0.0; + line = data + ystart * wpl; + val0 = GET_DATA_BYTE(line, j); + for (i = ystart + 1; i < yend; i++) { + line = data + i * wpl; + val1 = GET_DATA_BYTE(line, j); + sum += L_ABS(val1 - val0); + val0 = val1; + } + numaAddNumber(na, (l_float32)(norm * sum)); + } + + return na; +} + + +/*! + * \brief pixAbsDiffInRect() + * + * \param[in] pix 8 bpp; not cmapped + * \param[in] box [optional] if null, use entire image + * \param[in] dir differences along L_HORIZONTAL_LINE or L_VERTICAL_LINE + * \param[out] pabsdiff average of abs diff pixel values in region + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) This gives the average over the abs val of differences of + * adjacent pixels values, along either each + * row: dir == L_HORIZONTAL_LINE + * column: dir == L_VERTICAL_LINE + * </pre> + */ +l_ok +pixAbsDiffInRect(PIX *pix, + BOX *box, + l_int32 dir, + l_float32 *pabsdiff) +{ +l_int32 w, h, wpl, i, j, xstart, xend, ystart, yend, bw, bh, val0, val1; +l_uint32 *data, *line; +l_float64 norm, sum; + + PROCNAME("pixAbsDiffInRect"); + + if (!pabsdiff) + return ERROR_INT("&absdiff not defined", procName, 1); + *pabsdiff = 0.0; + if (!pix || pixGetDepth(pix) != 8) + return ERROR_INT("pix undefined or not 8 bpp", procName, 1); + if (dir != L_HORIZONTAL_LINE && dir != L_VERTICAL_LINE) + return ERROR_INT("invalid direction", procName, 1); + if (pixGetColormap(pix) != NULL) + return ERROR_INT("pix is colormapped", procName, 1); + + pixGetDimensions(pix, &w, &h, NULL); + if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, ¥d, + &bw, &bh) == 1) + return ERROR_INT("invalid clipping box", procName, 1); + + wpl = pixGetWpl(pix); + data = pixGetData(pix); + if (dir == L_HORIZONTAL_LINE) { + norm = 1. / (l_float32)(bh * (bw - 1)); + sum = 0.0; + for (i = ystart; i < yend; i++) { + line = data + i * wpl; + val0 = GET_DATA_BYTE(line, xstart); + for (j = xstart + 1; j < xend; j++) { + val1 = GET_DATA_BYTE(line, j); + sum += L_ABS(val1 - val0); + val0 = val1; + } + } + } else { /* vertical line */ + norm = 1. / (l_float32)(bw * (bh - 1)); + sum = 0.0; + for (j = xstart; j < xend; j++) { + line = data + ystart * wpl; + val0 = GET_DATA_BYTE(line, j); + for (i = ystart + 1; i < yend; i++) { + line = data + i * wpl; + val1 = GET_DATA_BYTE(line, j); + sum += L_ABS(val1 - val0); + val0 = val1; + } + } + } + *pabsdiff = (l_float32)(norm * sum); + return 0; +} + + +/*! + * \brief pixAbsDiffOnLine() + * + * \param[in] pix 8 bpp; not cmapped + * \param[in] x1, y1 first point; x1 <= x2, y1 <= y2 + * \param[in] x2, y2 first point + * \param[out] pabsdiff average of abs diff pixel values on line + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) This gives the average over the abs val of differences of + * adjacent pixels values, along a line that is either horizontal + * or vertical. + * (2) If horizontal, require x1 < x2; if vertical, require y1 < y2. + * </pre> + */ +l_ok +pixAbsDiffOnLine(PIX *pix, + l_int32 x1, + l_int32 y1, + l_int32 x2, + l_int32 y2, + l_float32 *pabsdiff) +{ +l_int32 w, h, i, j, dir, size, sum; +l_uint32 val0, val1; + + PROCNAME("pixAbsDiffOnLine"); + + if (!pabsdiff) + return ERROR_INT("&absdiff not defined", procName, 1); + *pabsdiff = 0.0; + if (!pix || pixGetDepth(pix) != 8) + return ERROR_INT("pix undefined or not 8 bpp", procName, 1); + if (y1 == y2) { + dir = L_HORIZONTAL_LINE; + } else if (x1 == x2) { + dir = L_VERTICAL_LINE; + } else { + return ERROR_INT("line is neither horiz nor vert", procName, 1); + } + if (pixGetColormap(pix) != NULL) + return ERROR_INT("pix is colormapped", procName, 1); + + pixGetDimensions(pix, &w, &h, NULL); + sum = 0; + if (dir == L_HORIZONTAL_LINE) { + x1 = L_MAX(x1, 0); + x2 = L_MIN(x2, w - 1); + if (x1 >= x2) + return ERROR_INT("x1 >= x2", procName, 1); + size = x2 - x1; + pixGetPixel(pix, x1, y1, &val0); + for (j = x1 + 1; j <= x2; j++) { + pixGetPixel(pix, j, y1, &val1); + sum += L_ABS((l_int32)val1 - (l_int32)val0); + val0 = val1; + } + } else { /* vertical */ + y1 = L_MAX(y1, 0); + y2 = L_MIN(y2, h - 1); + if (y1 >= y2) + return ERROR_INT("y1 >= y2", procName, 1); + size = y2 - y1; + pixGetPixel(pix, x1, y1, &val0); + for (i = y1 + 1; i <= y2; i++) { + pixGetPixel(pix, x1, i, &val1); + sum += L_ABS((l_int32)val1 - (l_int32)val0); + val0 = val1; + } + } + *pabsdiff = (l_float32)sum / (l_float32)size; + return 0; +} + + +/*-------------------------------------------------------------* + * Count of pixels with specific value * + *-------------------------------------------------------------*/ +/*! + * \brief pixCountArbInRect() + * + * \param[in] pixs 8 bpp, or colormapped + * \param[in] box [optional] over which count is made; + * use entire image if NULL + * \param[in] val pixel value to count + * \param[in] factor subsampling factor; integer >= 1 + * \param[out] pcount count; estimate it if factor > 1 + * \return na histogram, or NULL on error + * + * <pre> + * Notes: + * (1) If pixs is cmapped, %val is compared to the colormap index; + * otherwise, %val is compared to the grayscale value. + * (2) Set the subsampling %factor > 1 to reduce the amount of computation. + * If %factor > 1, multiply the count by %factor * %factor. + * </pre> + */ +l_int32 +pixCountArbInRect(PIX *pixs, + BOX *box, + l_int32 val, + l_int32 factor, + l_int32 *pcount) +{ +l_int32 i, j, bx, by, bw, bh, w, h, wpl, pixval; +l_uint32 *data, *line; + + PROCNAME("pixCountArbInRect"); + + if (!pcount) + return ERROR_INT("&count not defined", procName, 1); + *pcount = 0; + if (!pixs) + return ERROR_INT("pixs not defined", procName, 1); + if (pixGetDepth(pixs) != 8 && !pixGetColormap(pixs)) + return ERROR_INT("pixs neither 8 bpp nor colormapped", + procName, 1); + if (factor < 1) + return ERROR_INT("sampling factor < 1", procName, 1); + + pixGetDimensions(pixs, &w, &h, NULL); + data = pixGetData(pixs); + wpl = pixGetWpl(pixs); + + if (!box) { + for (i = 0; i < h; i += factor) { + line = data + i * wpl; + for (j = 0; j < w; j += factor) { + pixval = GET_DATA_BYTE(line, j); + if (pixval == val) (*pcount)++; + } + } + } else { + boxGetGeometry(box, &bx, &by, &bw, &bh); + for (i = 0; i < bh; i += factor) { + if (by + i < 0 || by + i >= h) continue; + line = data + (by + i) * wpl; + for (j = 0; j < bw; j += factor) { + if (bx + j < 0 || bx + j >= w) continue; + pixval = GET_DATA_BYTE(line, bx + j); + if (pixval == val) (*pcount)++; + } + } + } + + if (factor > 1) /* assume pixel color is randomly distributed */ + *pcount = *pcount * factor * factor; + return 0; +} + + +/*-------------------------------------------------------------* + * Mirrored tiling of a smaller image * + *-------------------------------------------------------------*/ +/*! + * \brief pixMirroredTiling() + * + * \param[in] pixs 8 or 32 bpp, small tile; to be replicated + * \param[in] w, h dimensions of output pix + * \return pixd usually larger pix, mirror-tiled with pixs, + * or NULL on error + * + * <pre> + * Notes: + * (1) This uses mirrored tiling, where each row alternates + * with LR flips and every column alternates with TB + * flips, such that the result is a tiling with identical + * 2 x 2 tiles, each of which is composed of these transforms: + * ----------------- + * | 1 | LR | + * ----------------- + * | TB | LR/TB | + * ----------------- + * </pre> + */ +PIX * +pixMirroredTiling(PIX *pixs, + l_int32 w, + l_int32 h) +{ +l_int32 wt, ht, d, i, j, nx, ny; +PIX *pixd, *pixsfx, *pixsfy, *pixsfxy, *pix; + + PROCNAME("pixMirroredTiling"); + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); + pixGetDimensions(pixs, &wt, &ht, &d); + if (wt <= 0 || ht <= 0) + return (PIX *)ERROR_PTR("pixs size illegal", procName, NULL); + if (d != 8 && d != 32) + return (PIX *)ERROR_PTR("depth not 32 bpp", procName, NULL); + + if ((pixd = pixCreate(w, h, d)) == NULL) + return (PIX *)ERROR_PTR("pixd not made", procName, NULL); + pixCopySpp(pixd, pixs); + + nx = (w + wt - 1) / wt; + ny = (h + ht - 1) / ht; + pixsfx = pixFlipLR(NULL, pixs); + pixsfy = pixFlipTB(NULL, pixs); + pixsfxy = pixFlipTB(NULL, pixsfx); + for (i = 0; i < ny; i++) { + for (j = 0; j < nx; j++) { + pix = pixs; + if ((i & 1) && !(j & 1)) + pix = pixsfy; + else if (!(i & 1) && (j & 1)) + pix = pixsfx; + else if ((i & 1) && (j & 1)) + pix = pixsfxy; + pixRasterop(pixd, j * wt, i * ht, wt, ht, PIX_SRC, pix, 0, 0); + } + } + + pixDestroy(&pixsfx); + pixDestroy(&pixsfy); + pixDestroy(&pixsfxy); + return pixd; +} + + +/*! + * \brief pixFindRepCloseTile() + * + * \param[in] pixs 32 bpp rgb + * \param[in] box region of pixs to search around + * \param[in] searchdir L_HORIZ or L_VERT; direction to search + * \param[in] mindist min distance of selected tile edge from box; >= 0 + * \param[in] tsize tile size; > 1; even; typically ~50 + * \param[in] ntiles number of tiles tested in each row/column + * \param[out] pboxtile region of best tile + * \param[in] debug 1 for debug output + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This looks for one or two square tiles with conforming median + * intensity and low variance, that is outside but near the input box. + * (2) %mindist specifies the gap between the box and the + * potential tiles. The tiles are given an overlap of 50%. + * %ntiles specifies the number of tiles that are tested + * beyond %mindist for each row or column. + * (3) For example, if %mindist = 20, %tilesize = 50 and %ntiles = 3, + * a horizontal search to the right will have 3 tiles in each row, + * with left edges at 20, 45 and 70 from the right edge of the + * input %box. The number of rows of tiles is determined by + * the height of %box and %tsize, with the 50% overlap.. + * </pre> + */ +l_ok +pixFindRepCloseTile(PIX *pixs, + BOX *box, + l_int32 searchdir, + l_int32 mindist, + l_int32 tsize, + l_int32 ntiles, + BOX **pboxtile, + l_int32 debug) +{ +l_int32 w, h, i, n, bestindex; +l_float32 var_of_mean, median_of_mean, median_of_stdev, mean_val, stdev_val; +l_float32 mindels, bestdelm, delm, dels, mean, stdev; +BOXA *boxa; +NUMA *namean, *nastdev; +PIX *pix, *pixg; +PIXA *pixa; + + PROCNAME("pixFindRepCloseTile"); + + if (!pboxtile) + return ERROR_INT("&boxtile not defined", procName, 1); + *pboxtile = NULL; + if (!pixs) + return ERROR_INT("pixs not defined", procName, 1); + if (!box) + return ERROR_INT("box not defined", procName, 1); + if (searchdir != L_HORIZ && searchdir != L_VERT) + return ERROR_INT("invalid searchdir", procName, 1); + if (mindist < 0) + return ERROR_INT("mindist must be >= 0", procName, 1); + if (tsize < 2) + return ERROR_INT("tsize must be > 1", procName, 1); + if (ntiles > 7) { + L_WARNING("ntiles = %d; larger than suggested max of 7\n", + procName, ntiles); + } + + /* Locate tile regions */ + pixGetDimensions(pixs, &w, &h, NULL); + boxa = findTileRegionsForSearch(box, w, h, searchdir, mindist, + tsize, ntiles); + if (!boxa) + return ERROR_INT("no tiles found", procName, 1); + + /* Generate the tiles and the mean and stdev of intensity */ + pixa = pixClipRectangles(pixs, boxa); + n = pixaGetCount(pixa); + namean = numaCreate(n); + nastdev = numaCreate(n); + for (i = 0; i < n; i++) { + pix = pixaGetPix(pixa, i, L_CLONE); + pixg = pixConvertRGBToGray(pix, 0.33f, 0.34f, 0.33f); + pixGetAverageMasked(pixg, NULL, 0, 0, 1, L_MEAN_ABSVAL, &mean); + pixGetAverageMasked(pixg, NULL, 0, 0, 1, L_STANDARD_DEVIATION, &stdev); + numaAddNumber(namean, mean); + numaAddNumber(nastdev, stdev); + pixDestroy(&pix); + pixDestroy(&pixg); + } + + /* Find the median and variance of the averages. We require + * the best tile to have a mean pixel intensity within a standard + * deviation of the median of mean intensities, and choose the + * tile in that set with the smallest stdev of pixel intensities + * (as a proxy for the tile with least visible structure). + * The median of the stdev is used, for debugging, as a normalizing + * factor for the stdev of intensities within a tile. */ + numaGetStatsUsingHistogram(namean, 256, NULL, NULL, NULL, &var_of_mean, + &median_of_mean, 0.0, NULL, NULL); + numaGetStatsUsingHistogram(nastdev, 256, NULL, NULL, NULL, NULL, + &median_of_stdev, 0.0, NULL, NULL); + mindels = 1000.0; + bestdelm = 1000.0; + bestindex = 0; + for (i = 0; i < n; i++) { + numaGetFValue(namean, i, &mean_val); + numaGetFValue(nastdev, i, &stdev_val); + if (var_of_mean == 0.0) { /* uniform color; any box will do */ + delm = 0.0; /* any value < 1.01 */ + dels = 1.0; /* n'importe quoi */ + } else { + delm = L_ABS(mean_val - median_of_mean) / sqrt(var_of_mean); + dels = stdev_val / median_of_stdev; + } + if (delm < 1.01) { + if (dels < mindels) { + if (debug) { + lept_stderr("i = %d, mean = %7.3f, delm = %7.3f," + " stdev = %7.3f, dels = %7.3f\n", + i, mean_val, delm, stdev_val, dels); + } + mindels = dels; + bestdelm = delm; + bestindex = i; + } + } + } + *pboxtile = boxaGetBox(boxa, bestindex, L_COPY); + + if (debug) { + L_INFO("median of mean = %7.3f\n", procName, median_of_mean); + L_INFO("standard dev of mean = %7.3f\n", procName, sqrt(var_of_mean)); + L_INFO("median of stdev = %7.3f\n", procName, median_of_stdev); + L_INFO("best tile: index = %d\n", procName, bestindex); + L_INFO("delta from median in units of stdev = %5.3f\n", + procName, bestdelm); + L_INFO("stdev as fraction of median stdev = %5.3f\n", + procName, mindels); + } + + numaDestroy(&namean); + numaDestroy(&nastdev); + pixaDestroy(&pixa); + boxaDestroy(&boxa); + return 0; +} + + +/*! + * \brief findTileRegionsForSearch() + * + * \param[in] box region of Pix to search around + * \param[in] w, h dimensions of Pix + * \param[in] searchdir L_HORIZ or L_VERT; direction to search + * \param[in] mindist min distance of selected tile edge from box; >= 0 + * \param[in] tsize tile size; > 1; even; typically ~50 + * \param[in] ntiles number of tiles tested in each row/column + * \return boxa if OK, or NULL on error + * + * <pre> + * Notes: + * (1) See calling function pixfindRepCloseTile(). + * </pre> + */ +static BOXA * +findTileRegionsForSearch(BOX *box, + l_int32 w, + l_int32 h, + l_int32 searchdir, + l_int32 mindist, + l_int32 tsize, + l_int32 ntiles) +{ +l_int32 bx, by, bw, bh, left, right, top, bot, i, j, nrows, ncols; +l_int32 x0, y0, x, y, w_avail, w_needed, h_avail, h_needed, t_avail; +BOX *box1; +BOXA *boxa; + + PROCNAME("findTileRegionsForSearch"); + + if (!box) + return (BOXA *)ERROR_PTR("box not defined", procName, NULL); + if (ntiles == 0) + return (BOXA *)ERROR_PTR("no tiles requested", procName, NULL); + + boxGetGeometry(box, &bx, &by, &bw, &bh); + if (searchdir == L_HORIZ) { + /* Find the tile parameters for the search. Note that the + * tiles are overlapping by 50% in each direction. */ + left = bx; /* distance to left of box */ + right = w - bx - bw + 1; /* distance to right of box */ + w_avail = L_MAX(left, right) - mindist; + if (tsize & 1) tsize++; /* be sure it's even */ + if (w_avail < tsize) { + L_ERROR("tsize = %d, w_avail = %d\n", procName, tsize, w_avail); + return NULL; + } + w_needed = tsize + (ntiles - 1) * (tsize / 2); + if (w_needed > w_avail) { + t_avail = 1 + 2 * (w_avail - tsize) / tsize; + L_WARNING("ntiles = %d; room for only %d\n", procName, + ntiles, t_avail); + ntiles = t_avail; + w_needed = tsize + (ntiles - 1) * (tsize / 2); + } + nrows = L_MAX(1, 1 + 2 * (bh - tsize) / tsize); + + /* Generate the tile regions to search */ + boxa = boxaCreate(0); + if (left > right) /* search to left */ + x0 = bx - w_needed; + else /* search to right */ + x0 = bx + bw + mindist; + for (i = 0; i < nrows; i++) { + y = by + i * tsize / 2; + for (j = 0; j < ntiles; j++) { + x = x0 + j * tsize / 2; + box1 = boxCreate(x, y, tsize, tsize); + boxaAddBox(boxa, box1, L_INSERT); + } + } + } else { /* L_VERT */ + /* Find the tile parameters for the search */ + top = by; /* distance above box */ + bot = h - by - bh + 1; /* distance below box */ + h_avail = L_MAX(top, bot) - mindist; + if (h_avail < tsize) { + L_ERROR("tsize = %d, h_avail = %d\n", procName, tsize, h_avail); + return NULL; + } + h_needed = tsize + (ntiles - 1) * (tsize / 2); + if (h_needed > h_avail) { + t_avail = 1 + 2 * (h_avail - tsize) / tsize; + L_WARNING("ntiles = %d; room for only %d\n", procName, + ntiles, t_avail); + ntiles = t_avail; + h_needed = tsize + (ntiles - 1) * (tsize / 2); + } + ncols = L_MAX(1, 1 + 2 * (bw - tsize) / tsize); + + /* Generate the tile regions to search */ + boxa = boxaCreate(0); + if (top > bot) /* search above */ + y0 = by - h_needed; + else /* search below */ + y0 = by + bh + mindist; + for (j = 0; j < ncols; j++) { + x = bx + j * tsize / 2; + for (i = 0; i < ntiles; i++) { + y = y0 + i * tsize / 2; + box1 = boxCreate(x, y, tsize, tsize); + boxaAddBox(boxa, box1, L_INSERT); + } + } + } + return boxa; +} |