mirror of http://192.168.1.51:8099/lmh188/twain3.0
3508 lines
116 KiB
C
3508 lines
116 KiB
C
|
/*====================================================================*
|
||
|
- 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()
|
||
|
*
|
||
|
* 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()
|
||
|
*
|
||
|
* 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>
|
||
|
*/
|
||
|
|
||
|
#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);
|
||
|
fprintf(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;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*-------------------------------------------------------------*
|
||
|
* 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] pix 1, 2, 4, 8 bpp; not cmapped
|
||
|
* \param[in] box [optional] if null, use entire image
|
||
|
* \param[out] pave average of pixel values in region
|
||
|
* \return 0 if OK; 1 on error
|
||
|
*/
|
||
|
l_ok
|
||
|
pixAverageInRect(PIX *pix,
|
||
|
BOX *box,
|
||
|
l_float32 *pave)
|
||
|
{
|
||
|
l_int32 w, h, d, wpl, i, j, xstart, xend, ystart, yend, bw, bh;
|
||
|
l_uint32 *data, *line;
|
||
|
l_float64 ave;
|
||
|
|
||
|
PROCNAME("pixAverageInRect");
|
||
|
|
||
|
if (!pave)
|
||
|
return ERROR_INT("&ave not defined", procName, 1);
|
||
|
*pave = 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);
|
||
|
ave = 0;
|
||
|
for (i = ystart; i < yend; i++) {
|
||
|
line = data + i * wpl;
|
||
|
for (j = xstart; j < xend; j++) {
|
||
|
if (d == 1)
|
||
|
ave += GET_DATA_BIT(line, j);
|
||
|
else if (d == 2)
|
||
|
ave += GET_DATA_DIBIT(line, j);
|
||
|
else if (d == 4)
|
||
|
ave += GET_DATA_QBIT(line, j);
|
||
|
else /* d == 8 */
|
||
|
ave += GET_DATA_BYTE(line, j);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
*pave = ave / ((l_float32)(bw) * bh);
|
||
|
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.33, 0.34, 0.33);
|
||
|
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) {
|
||
|
fprintf(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;
|
||
|
}
|