blob: 61a32e6f0655778b7ffa9871cc70f652eadb6874 [file] [log] [blame]
/*====================================================================*
- Copyright (C) 2001 Leptonica. All rights reserved.
- This software is distributed in the hope that it will be
- useful, but with NO WARRANTY OF ANY KIND.
- No author or distributor accepts responsibility to anyone for the
- consequences of using this software, or for whether it serves any
- particular purpose or works at all, unless he or she says so in
- writing. Everyone is granted permission to copy, modify and
- redistribute this source code, for commercial or non-commercial
- purposes, with the following restrictions: (1) the origin of this
- source code must not be misrepresented; (2) modified versions must
- be plainly marked as such; and (3) this notice may not be removed
- or altered from any source or modified source distribution.
*====================================================================*/
/*
* morph.c
*
* Generic binary morphological ops implemented with rasterop
* PIX *pixDilate()
* PIX *pixErode()
* PIX *pixHMT()
* PIX *pixOpen()
* PIX *pixClose()
* PIX *pixCloseSafe()
* PIX *pixOpenGeneralized()
* PIX *pixCloseGeneralized()
*
* Binary morphological (raster) ops with brick Sels
* PIX *pixDilateBrick()
* PIX *pixErodeBrick()
* PIX *pixOpenBrick()
* PIX *pixCloseBrick()
* PIX *pixCloseSafeBrick()
*
* Binary composed morphological (raster) ops with brick Sels
* l_int32 selectComposableSels()
* l_int32 selectComposableSizes()
* PIX *pixDilateCompBrick()
* PIX *pixErodeCompBrick()
* PIX *pixOpenCompBrick()
* PIX *pixCloseCompBrick()
* PIX *pixCloseSafeCompBrick()
*
* Functions associated with boundary conditions
* void resetMorphBoundaryCondition()
* l_int32 getMorphBorderPixelColor()
*
* Static helpers for arg processing
* static PIX *processMorphArgs1()
* static PIX *processMorphArgs2()
*
* You are provided with many simple ways to do binary morphology.
* In particular, if you are using brick Sels, there are six
* convenient methods, all specially tailored for separable operations
* on brick Sels. A "brick" Sel is a Sel that is a rectangle
* of solid SEL_HITs with the origin at or near the center.
* Note that a brick Sel can have one dimension of size 1.
* This is very common. All the brick Sel operations are
* separable, meaning the operation is done first in the horizontal
* direction and then in the vertical direction. If one of the
* dimensions is 1, this is a special case where the operation is
* only performed in the other direction.
*
* These six brick Sel methods are enumerated as follows:
*
* (1) Brick Sels: pix*Brick(), where * = {Dilate, Erode, Open, Close}.
* These are separable rasterop implementations. The Sels are
* automatically generated, used, and destroyed at the end.
* You can get the result as a new Pix, in-place back into the src Pix,
* or written to another existing Pix.
*
* (2) Brick Sels: pix*CompBrick(), where * = {Dilate, Erode, Open, Close}.
* These are separable, 2-way composite, rasterop implementations.
* The Sels are automatically generated, used, and destroyed at the end.
* You can get the result as a new Pix, in-place back into the src Pix,
* or written to another existing Pix. For large Sels, these are
* considerably faster than the corresponding pix*Brick() functions.
* N.B.: The size of the Sels that are actually used are typically
* close to, but not exactly equal to, the size input to the function.
*
* (3) Brick Sels: pix*BrickDwa(), where * = {Dilate, Erode, Open, Close}.
* These are separable dwa (destination word accumulation)
* implementations. They use auto-gen'd dwa code. You can get
* the result as a new Pix, in-place back into the src Pix,
* or written to another existing Pix. This is typically
* about 3x faster than the analogous rasterop pix*Brick()
* function, but it has the limitation that the Sel size must
* be less than 63. This is pre-set to work on a number
* of pre-generated Sels. If you want to use other Sels, the
* code can be auto-gen'd for them; see the instructions in morphdwa.c.
*
* (4) Same as (1), but you run it through pixMorphSequence(), with
* the sequence string either compiled in or generated using sprintf.
* All intermediate images and Sels are created, used and destroyed.
* You always get the result as a new Pix. For example, you can
* specify a separable 11 x 17 brick opening as "o11.17",
* or you can specify the horizontal and vertical operations
* explicitly as "o11.1 + o1.11". See morphseq.c for details.
*
* (5) Same as (2), but you run it through pixMorphCompSequence(), with
* the sequence string either compiled in or generated using sprintf.
* All intermediate images and Sels are created, used and destroyed.
* You always get the result as a new Pix. See morphseq.c for details.
*
* (6) Same as (3), but you run it through pixMorphSequenceDwa(), with
* the sequence string either compiled in or generated using sprintf.
* All intermediate images and Sels are created, used and destroyed.
* You always get the result as a new Pix. See morphseq.c for details.
*
* If you are using Sels that are not bricks, you have two choices:
* (a) simplest: use the basic rasterop implementations (pixDilate(), ...)
* (b) fastest: generate the destination word accumumlation (dwa)
* code for your Sels and compile it with the library.
*
* For an example, see flipdetect.c, which gives implementations
* using hit-miss Sels with both the rasterop and dwa versions.
* For the latter, the dwa code resides in fliphmtgen.c, and it
* was generated by prog/flipselgen.c. Both the rasterop and dwa
* implementations are tested by prog/fliptest.c.
*
* A global constant MORPH_BC is used to set the boundary conditions
* for rasterop-based binary morphology. MORPH_BC, in morph.c,
* is set by default to ASYMMETRIC_MORPH_BC for a non-symmetric
* convention for boundary pixels in dilation and erosion:
* All pixels outside the image are assumed to be OFF
* for both dilation and erosion.
* To use a symmetric definition, see comments in pixErode()
* and reset MORPH_BC to SYMMETRIC_MORPH_BC, using
* resetMorphBoundaryCondition().
*
* Boundary artifacts are possible in closing when the non-symmetric
* boundary conditions are used, because foreground pixels very close
* to the edge can be removed. This can be avoided by using either
* the symmetric boundary conditions or the function pixCloseSafe(),
* which adds a border before the operation and removes it afterwards.
*
* The hit-miss transform (HMT) is the bit-and of 2 erosions:
* (erosion of the src by the hits) & (erosion of the bit-inverted
* src by the misses)
*
* The 'generalized opening' is an HMT followed by a dilation that uses
* only the hits of the hit-miss Sel.
* The 'generalized closing' is a dilation (again, with the hits
* of a hit-miss Sel), followed by the HMT.
* Both of these 'generalized' functions are idempotent.
*
* These functions are extensively tested in prog/binmorph1_reg.c,
* prog/binmorph2_reg.c, and prog/binmorph3_reg.c.
*/
#include <stdio.h>
#include <math.h>
#include "allheaders.h"
/* Global constant; initialized here; must be declared extern
* in other files to access it directly. However, in most
* cases that is not necessary, because it can be reset
* using resetMorphBoundaryCondition(). */
l_int32 MORPH_BC = ASYMMETRIC_MORPH_BC;
/* We accept this cost in extra rasterops for decomposing exactly. */
static const l_int32 ACCEPTABLE_COST = 5;
/* Static helpers for arg processing */
static PIX * processMorphArgs1(PIX *pixd, PIX *pixs, SEL *sel, PIX **ppixt);
static PIX * processMorphArgs2(PIX *pixd, PIX *pixs, SEL *sel);
/*-----------------------------------------------------------------*
* Generic binary morphological ops implemented with rasterop *
*-----------------------------------------------------------------*/
/*!
* pixDilate()
*
* Input: pixd (<optional>; this can be null, equal to pixs,
* or different from pixs)
* pixs (1 bpp)
* sel
* Return: pixd
*
* Notes:
* (1) This dilates src using hits in Sel.
* (2) There are three cases:
* (a) pixd == null (result into new pixd)
* (b) pixd == pixs (in-place; writes result back to pixs)
* (c) pixd != pixs (puts result into existing pixd)
* (3) For clarity, if the case is known, use these patterns:
* (a) pixd = pixDilate(NULL, pixs, ...);
* (b) pixDilate(pixs, pixs, ...);
* (c) pixDilate(pixd, pixs, ...);
* (4) The size of the result is determined by pixs.
*/
PIX *
pixDilate(PIX *pixd,
PIX *pixs,
SEL *sel)
{
l_int32 i, j, w, h, sx, sy, cx, cy, seldata;
PIX *pixt;
PROCNAME("pixDilate");
if ((pixd = processMorphArgs1(pixd, pixs, sel, &pixt)) == NULL)
return (PIX *)ERROR_PTR("processMorphArgs1 failed", procName, pixd);
pixGetDimensions(pixs, &w, &h, NULL);
selGetParameters(sel, &sy, &sx, &cy, &cx);
pixClearAll(pixd);
for (i = 0; i < sy; i++) {
for (j = 0; j < sx; j++) {
seldata = sel->data[i][j];
if (seldata == 1) { /* src | dst */
pixRasterop(pixd, j - cx, i - cy, w, h, PIX_SRC | PIX_DST,
pixt, 0, 0);
}
}
}
pixDestroy(&pixt);
return pixd;
}
/*!
* pixErode()
*
* Input: pixd (<optional>; this can be null, equal to pixs,
* or different from pixs)
* pixs (1 bpp)
* sel
* Return: pixd
*
* Notes:
* (1) This erodes src using hits in Sel.
* (2) There are three cases:
* (a) pixd == null (result into new pixd)
* (b) pixd == pixs (in-place; writes result back to pixs)
* (c) pixd != pixs (puts result into existing pixd)
* (3) For clarity, if the case is known, use these patterns:
* (a) pixd = pixErode(NULL, pixs, ...);
* (b) pixErode(pixs, pixs, ...);
* (c) pixErode(pixd, pixs, ...);
* (4) The size of the result is determined by pixs.
*/
PIX *
pixErode(PIX *pixd,
PIX *pixs,
SEL *sel)
{
l_int32 i, j, w, h, sx, sy, cx, cy, seldata;
l_int32 xp, yp, xn, yn;
PIX *pixt;
PROCNAME("pixErode");
if ((pixd = processMorphArgs1(pixd, pixs, sel, &pixt)) == NULL)
return (PIX *)ERROR_PTR("processMorphArgs1 failed", procName, pixd);
pixGetDimensions(pixs, &w, &h, NULL);
selGetParameters(sel, &sy, &sx, &cy, &cx);
pixSetAll(pixd);
for (i = 0; i < sy; i++) {
for (j = 0; j < sx; j++) {
seldata = sel->data[i][j];
if (seldata == 1) { /* src & dst */
pixRasterop(pixd, cx - j, cy - i, w, h, PIX_SRC & PIX_DST,
pixt, 0, 0);
}
}
}
/* Clear near edges. We do this for the asymmetric boundary
* condition convention that implements erosion assuming all
* pixels surrounding the image are OFF. If you use a
* use a symmetric b.c. convention, where the erosion is
* implemented assuming pixels surrounding the image
* are ON, these operations are omitted. */
if (MORPH_BC == ASYMMETRIC_MORPH_BC) {
selFindMaxTranslations(sel, &xp, &yp, &xn, &yn);
if (xp > 0)
pixRasterop(pixd, 0, 0, xp, h, PIX_CLR, NULL, 0, 0);
if (xn > 0)
pixRasterop(pixd, w - xn, 0, xn, h, PIX_CLR, NULL, 0, 0);
if (yp > 0)
pixRasterop(pixd, 0, 0, w, yp, PIX_CLR, NULL, 0, 0);
if (yn > 0)
pixRasterop(pixd, 0, h - yn, w, yn, PIX_CLR, NULL, 0, 0);
}
pixDestroy(&pixt);
return pixd;
}
/*!
* pixHMT()
*
* Input: pixd (<optional>; this can be null, equal to pixs,
* or different from pixs)
* pixs (1 bpp)
* sel
* Return: pixd
*
* Notes:
* (1) The hit-miss transform erodes the src, using both hits
* and misses in the Sel. It ANDs the shifted src for hits
* and ANDs the inverted shifted src for misses.
* (2) There are three cases:
* (a) pixd == null (result into new pixd)
* (b) pixd == pixs (in-place; writes result back to pixs)
* (c) pixd != pixs (puts result into existing pixd)
* (3) For clarity, if the case is known, use these patterns:
* (a) pixd = pixHMT(NULL, pixs, ...);
* (b) pixHMT(pixs, pixs, ...);
* (c) pixHMT(pixd, pixs, ...);
* (4) The size of the result is determined by pixs.
*/
PIX *
pixHMT(PIX *pixd,
PIX *pixs,
SEL *sel)
{
l_int32 i, j, w, h, sx, sy, cx, cy, firstrasterop, seldata;
l_int32 xp, yp, xn, yn;
PIX *pixt;
PROCNAME("pixHMT");
if ((pixd = processMorphArgs1(pixd, pixs, sel, &pixt)) == NULL)
return (PIX *)ERROR_PTR("processMorphArgs1 failed", procName, pixd);
pixGetDimensions(pixs, &w, &h, NULL);
selGetParameters(sel, &sy, &sx, &cy, &cx);
firstrasterop = TRUE;
for (i = 0; i < sy; i++) {
for (j = 0; j < sx; j++) {
seldata = sel->data[i][j];
if (seldata == 1) { /* hit */
if (firstrasterop == TRUE) { /* src only */
pixClearAll(pixd);
pixRasterop(pixd, cx - j, cy - i, w, h, PIX_SRC,
pixt, 0, 0);
firstrasterop = FALSE;
}
else { /* src & dst */
pixRasterop(pixd, cx - j, cy - i, w, h, PIX_SRC & PIX_DST,
pixt, 0, 0);
}
}
else if (seldata == 2) { /* miss */
if (firstrasterop == TRUE) { /* ~src only */
pixSetAll(pixd);
pixRasterop(pixd, cx - j, cy - i, w, h, PIX_NOT(PIX_SRC),
pixt, 0, 0);
firstrasterop = FALSE;
}
else { /* ~src & dst */
pixRasterop(pixd, cx - j, cy - i, w, h,
PIX_NOT(PIX_SRC) & PIX_DST,
pixt, 0, 0);
}
}
}
}
/* Clear near edges */
selFindMaxTranslations(sel, &xp, &yp, &xn, &yn);
if (xp > 0)
pixRasterop(pixd, 0, 0, xp, h, PIX_CLR, NULL, 0, 0);
if (xn > 0)
pixRasterop(pixd, w - xn, 0, xn, h, PIX_CLR, NULL, 0, 0);
if (yp > 0)
pixRasterop(pixd, 0, 0, w, yp, PIX_CLR, NULL, 0, 0);
if (yn > 0)
pixRasterop(pixd, 0, h - yn, w, yn, PIX_CLR, NULL, 0, 0);
pixDestroy(&pixt);
return pixd;
}
/*!
* pixOpen()
*
* Input: pixd (<optional>; this can be null, equal to pixs,
* or different from pixs)
* pixs (1 bpp)
* sel
* Return: pixd
*
* Notes:
* (1) Generic morphological opening, using hits in the Sel.
* (2) There are three cases:
* (a) pixd == null (result into new pixd)
* (b) pixd == pixs (in-place; writes result back to pixs)
* (c) pixd != pixs (puts result into existing pixd)
* (3) For clarity, if the case is known, use these patterns:
* (a) pixd = pixOpen(NULL, pixs, ...);
* (b) pixOpen(pixs, pixs, ...);
* (c) pixOpen(pixd, pixs, ...);
* (4) The size of the result is determined by pixs.
*/
PIX *
pixOpen(PIX *pixd,
PIX *pixs,
SEL *sel)
{
PIX *pixt;
PROCNAME("pixOpen");
if ((pixd = processMorphArgs2(pixd, pixs, sel)) == NULL)
return (PIX *)ERROR_PTR("pixd not returned", procName, pixd);
if ((pixt = pixErode(NULL, pixs, sel)) == NULL)
return (PIX *)ERROR_PTR("pixt not made", procName, pixd);
pixDilate(pixd, pixt, sel);
pixDestroy(&pixt);
return pixd;
}
/*!
* pixClose()
*
* Input: pixd (<optional>; this can be null, equal to pixs,
* or different from pixs)
* pixs (1 bpp)
* sel
* Return: pixd
*
* Notes:
* (1) Generic morphological closing, using hits in the Sel.
* (2) This implementation is a strict dual of the opening if
* symmetric boundary conditions are used (see notes at top
* of this file).
* (3) There are three cases:
* (a) pixd == null (result into new pixd)
* (b) pixd == pixs (in-place; writes result back to pixs)
* (c) pixd != pixs (puts result into existing pixd)
* (4) For clarity, if the case is known, use these patterns:
* (a) pixd = pixClose(NULL, pixs, ...);
* (b) pixClose(pixs, pixs, ...);
* (c) pixClose(pixd, pixs, ...);
* (5) The size of the result is determined by pixs.
*/
PIX *
pixClose(PIX *pixd,
PIX *pixs,
SEL *sel)
{
PIX *pixt;
PROCNAME("pixClose");
if ((pixd = processMorphArgs2(pixd, pixs, sel)) == NULL)
return (PIX *)ERROR_PTR("pixd not returned", procName, pixd);
if ((pixt = pixDilate(NULL, pixs, sel)) == NULL)
return (PIX *)ERROR_PTR("pixt not made", procName, pixd);
pixErode(pixd, pixt, sel);
pixDestroy(&pixt);
return pixd;
}
/*!
* pixCloseSafe()
*
* Input: pixd (<optional>; this can be null, equal to pixs,
* or different from pixs)
* pixs (1 bpp)
* sel
* Return: pixd
*
* Notes:
* (1) Generic morphological closing, using hits in the Sel.
* (2) If non-symmetric boundary conditions are used, this
* function adds a border of OFF pixels that is of
* sufficient size to avoid losing pixels from the dilation,
* and it removes the border after the operation is finished.
* It thus enforces a correct extensive result for closing.
* (3) If symmetric b.c. are used, it is not necessary to add
* and remove this border.
* (4) There are three cases:
* (a) pixd == null (result into new pixd)
* (b) pixd == pixs (in-place; writes result back to pixs)
* (c) pixd != pixs (puts result into existing pixd)
* (5) For clarity, if the case is known, use these patterns:
* (a) pixd = pixCloseSafe(NULL, pixs, ...);
* (b) pixCloseSafe(pixs, pixs, ...);
* (c) pixCloseSafe(pixd, pixs, ...);
* (6) The size of the result is determined by pixs.
*/
PIX *
pixCloseSafe(PIX *pixd,
PIX *pixs,
SEL *sel)
{
l_int32 xp, yp, xn, yn, xmax, xbord;
PIX *pixt1, *pixt2;
PROCNAME("pixCloseSafe");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
if (!sel)
return (PIX *)ERROR_PTR("sel not defined", procName, pixd);
if (pixGetDepth(pixs) != 1)
return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
/* Symmetric b.c. handles correctly without added pixels */
if (MORPH_BC == SYMMETRIC_MORPH_BC)
return pixClose(pixd, pixs, sel);
selFindMaxTranslations(sel, &xp, &yp, &xn, &yn);
xmax = L_MAX(xp, xn);
xbord = 32 * ((xmax + 31) / 32); /* full 32 bit words */
if ((pixt1 = pixAddBorderGeneral(pixs, xbord, xbord, yp, yn, 0)) == NULL)
return (PIX *)ERROR_PTR("pixt1 not made", procName, pixd);
pixClose(pixt1, pixt1, sel);
if ((pixt2 = pixRemoveBorderGeneral(pixt1, xbord, xbord, yp, yn)) == NULL)
return (PIX *)ERROR_PTR("pixt2 not made", procName, pixd);
pixDestroy(&pixt1);
if (!pixd)
return pixt2;
pixCopy(pixd, pixt2);
pixDestroy(&pixt2);
return pixd;
}
/*!
* pixOpenGeneralized()
*
* Input: pixd (<optional>; this can be null, equal to pixs,
* or different from pixs)
* pixs (1 bpp)
* sel
* Return: pixd
*
* Notes:
* (1) Generalized morphological opening, using both hits and
* misses in the Sel.
* (2) This does a hit-miss transform, followed by a dilation
* using the hits.
* (3) There are three cases:
* (a) pixd == null (result into new pixd)
* (b) pixd == pixs (in-place; writes result back to pixs)
* (c) pixd != pixs (puts result into existing pixd)
* (4) For clarity, if the case is known, use these patterns:
* (a) pixd = pixOpenGeneralized(NULL, pixs, ...);
* (b) pixOpenGeneralized(pixs, pixs, ...);
* (c) pixOpenGeneralized(pixd, pixs, ...);
* (5) The size of the result is determined by pixs.
*/
PIX *
pixOpenGeneralized(PIX *pixd,
PIX *pixs,
SEL *sel)
{
PIX *pixt;
PROCNAME("pixOpenGeneralized");
if ((pixd = processMorphArgs2(pixd, pixs, sel)) == NULL)
return (PIX *)ERROR_PTR("pixd not returned", procName, pixd);
if ((pixt = pixHMT(NULL, pixs, sel)) == NULL)
return (PIX *)ERROR_PTR("pixt not made", procName, pixd);
pixDilate(pixd, pixt, sel);
pixDestroy(&pixt);
return pixd;
}
/*!
* pixCloseGeneralized()
*
* Input: pixd (<optional>; this can be null, equal to pixs,
* or different from pixs)
* pixs (1 bpp)
* sel
* Return: pixd
*
* Notes:
* (1) Generalized morphological closing, using both hits and
* misses in the Sel.
* (2) This does a dilation using the hits, followed by a
* hit-miss transform.
* (3) This operation is a dual of the generalized opening.
* (4) There are three cases:
* (a) pixd == null (result into new pixd)
* (b) pixd == pixs (in-place; writes result back to pixs)
* (c) pixd != pixs (puts result into existing pixd)
* (5) For clarity, if the case is known, use these patterns:
* (a) pixd = pixCloseGeneralized(NULL, pixs, ...);
* (b) pixCloseGeneralized(pixs, pixs, ...);
* (c) pixCloseGeneralized(pixd, pixs, ...);
* (6) The size of the result is determined by pixs.
*/
PIX *
pixCloseGeneralized(PIX *pixd,
PIX *pixs,
SEL *sel)
{
PIX *pixt;
PROCNAME("pixCloseGeneralized");
if ((pixd = processMorphArgs2(pixd, pixs, sel)) == NULL)
return (PIX *)ERROR_PTR("pixd not returned", procName, pixd);
if ((pixt = pixDilate(NULL, pixs, sel)) == NULL)
return (PIX *)ERROR_PTR("pixt not made", procName, pixd);
pixHMT(pixd, pixt, sel);
pixDestroy(&pixt);
return pixd;
}
/*-----------------------------------------------------------------*
* Binary morphological (raster) ops with brick Sels *
*-----------------------------------------------------------------*/
/*!
* pixDilateBrick()
*
* Input: pixd (<optional>; this can be null, equal to pixs,
* or different from pixs)
* pixs (1 bpp)
* hsize (width of brick Sel)
* vsize (height of brick Sel)
* Return: pixd
*
* Notes:
* (1) Sel is a brick with all elements being hits
* (2) The origin is at (x, y) = (hsize/2, vsize/2)
* (3) Do separably if both hsize and vsize are > 1.
* (4) There are three cases:
* (a) pixd == null (result into new pixd)
* (b) pixd == pixs (in-place; writes result back to pixs)
* (c) pixd != pixs (puts result into existing pixd)
* (5) For clarity, if the case is known, use these patterns:
* (a) pixd = pixDilateBrick(NULL, pixs, ...);
* (b) pixDilateBrick(pixs, pixs, ...);
* (c) pixDilateBrick(pixd, pixs, ...);
* (6) The size of the result is determined by pixs.
*/
PIX *
pixDilateBrick(PIX *pixd,
PIX *pixs,
l_int32 hsize,
l_int32 vsize)
{
PIX *pixt;
SEL *sel, *selh, *selv;
PROCNAME("pixDilateBrick");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
if (pixGetDepth(pixs) != 1)
return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
if (hsize < 1 || vsize < 1)
return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
if (hsize == 1 && vsize == 1)
return pixCopy(pixd, pixs);
if (hsize == 1 || vsize == 1) { /* no intermediate result */
sel = selCreateBrick(vsize, hsize, vsize / 2, hsize / 2, SEL_HIT);
pixd = pixDilate(pixd, pixs, sel);
selDestroy(&sel);
}
else {
selh = selCreateBrick(1, hsize, 0, hsize / 2, SEL_HIT);
selv = selCreateBrick(vsize, 1, vsize / 2, 0, SEL_HIT);
pixt = pixDilate(NULL, pixs, selh);
pixd = pixDilate(pixd, pixt, selv);
pixDestroy(&pixt);
selDestroy(&selh);
selDestroy(&selv);
}
return pixd;
}
/*!
* pixErodeBrick()
*
* Input: pixd (<optional>; this can be null, equal to pixs,
* or different from pixs)
* pixs (1 bpp)
* hsize (width of brick Sel)
* vsize (height of brick Sel)
* Return: pixd
*
* Notes:
* (1) Sel is a brick with all elements being hits
* (2) The origin is at (x, y) = (hsize/2, vsize/2)
* (3) Do separably if both hsize and vsize are > 1.
* (4) There are three cases:
* (a) pixd == null (result into new pixd)
* (b) pixd == pixs (in-place; writes result back to pixs)
* (c) pixd != pixs (puts result into existing pixd)
* (5) For clarity, if the case is known, use these patterns:
* (a) pixd = pixErodeBrick(NULL, pixs, ...);
* (b) pixErodeBrick(pixs, pixs, ...);
* (c) pixErodeBrick(pixd, pixs, ...);
* (6) The size of the result is determined by pixs.
*/
PIX *
pixErodeBrick(PIX *pixd,
PIX *pixs,
l_int32 hsize,
l_int32 vsize)
{
PIX *pixt;
SEL *sel, *selh, *selv;
PROCNAME("pixErodeBrick");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
if (pixGetDepth(pixs) != 1)
return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
if (hsize < 1 || vsize < 1)
return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
if (hsize == 1 && vsize == 1)
return pixCopy(pixd, pixs);
if (hsize == 1 || vsize == 1) { /* no intermediate result */
sel = selCreateBrick(vsize, hsize, vsize / 2, hsize / 2, SEL_HIT);
pixd = pixErode(pixd, pixs, sel);
selDestroy(&sel);
}
else {
selh = selCreateBrick(1, hsize, 0, hsize / 2, SEL_HIT);
selv = selCreateBrick(vsize, 1, vsize / 2, 0, SEL_HIT);
pixt = pixErode(NULL, pixs, selh);
pixd = pixErode(pixd, pixt, selv);
pixDestroy(&pixt);
selDestroy(&selh);
selDestroy(&selv);
}
return pixd;
}
/*!
* pixOpenBrick()
*
* Input: pixd (<optional>; this can be null, equal to pixs,
* or different from pixs)
* pixs (1 bpp)
* hsize (width of brick Sel)
* vsize (height of brick Sel)
* Return: pixd, or null on error
*
* Notes:
* (1) Sel is a brick with all elements being hits
* (2) The origin is at (x, y) = (hsize/2, vsize/2)
* (3) Do separably if both hsize and vsize are > 1.
* (4) There are three cases:
* (a) pixd == null (result into new pixd)
* (b) pixd == pixs (in-place; writes result back to pixs)
* (c) pixd != pixs (puts result into existing pixd)
* (5) For clarity, if the case is known, use these patterns:
* (a) pixd = pixOpenBrick(NULL, pixs, ...);
* (b) pixOpenBrick(pixs, pixs, ...);
* (c) pixOpenBrick(pixd, pixs, ...);
* (6) The size of the result is determined by pixs.
*/
PIX *
pixOpenBrick(PIX *pixd,
PIX *pixs,
l_int32 hsize,
l_int32 vsize)
{
PIX *pixt;
SEL *sel, *selh, *selv;
PROCNAME("pixOpenBrick");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
if (pixGetDepth(pixs) != 1)
return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
if (hsize < 1 || vsize < 1)
return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
if (hsize == 1 && vsize == 1)
return pixCopy(pixd, pixs);
if (hsize == 1 || vsize == 1) { /* no intermediate result */
sel = selCreateBrick(vsize, hsize, vsize / 2, hsize / 2, SEL_HIT);
pixd = pixOpen(pixd, pixs, sel);
selDestroy(&sel);
}
else { /* do separably */
selh = selCreateBrick(1, hsize, 0, hsize / 2, SEL_HIT);
selv = selCreateBrick(vsize, 1, vsize / 2, 0, SEL_HIT);
pixt = pixErode(NULL, pixs, selh);
pixd = pixErode(pixd, pixt, selv);
pixDilate(pixt, pixd, selh);
pixDilate(pixd, pixt, selv);
pixDestroy(&pixt);
selDestroy(&selh);
selDestroy(&selv);
}
return pixd;
}
/*!
* pixCloseBrick()
*
* Input: pixd (<optional>; this can be null, equal to pixs,
* or different from pixs)
* pixs (1 bpp)
* hsize (width of brick Sel)
* vsize (height of brick Sel)
* Return: pixd, or null on error
*
* Notes:
* (1) Sel is a brick with all elements being hits
* (2) The origin is at (x, y) = (hsize/2, vsize/2)
* (3) Do separably if both hsize and vsize are > 1.
* (4) There are three cases:
* (a) pixd == null (result into new pixd)
* (b) pixd == pixs (in-place; writes result back to pixs)
* (c) pixd != pixs (puts result into existing pixd)
* (5) For clarity, if the case is known, use these patterns:
* (a) pixd = pixCloseBrick(NULL, pixs, ...);
* (b) pixCloseBrick(pixs, pixs, ...);
* (c) pixCloseBrick(pixd, pixs, ...);
* (6) The size of the result is determined by pixs.
*/
PIX *
pixCloseBrick(PIX *pixd,
PIX *pixs,
l_int32 hsize,
l_int32 vsize)
{
PIX *pixt;
SEL *sel, *selh, *selv;
PROCNAME("pixCloseBrick");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
if (pixGetDepth(pixs) != 1)
return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
if (hsize < 1 || vsize < 1)
return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
if (hsize == 1 && vsize == 1)
return pixCopy(pixd, pixs);
if (hsize == 1 || vsize == 1) { /* no intermediate result */
sel = selCreateBrick(vsize, hsize, vsize / 2, hsize / 2, SEL_HIT);
pixd = pixClose(pixd, pixs, sel);
selDestroy(&sel);
}
else { /* do separably */
selh = selCreateBrick(1, hsize, 0, hsize / 2, SEL_HIT);
selv = selCreateBrick(vsize, 1, vsize / 2, 0, SEL_HIT);
pixt = pixDilate(NULL, pixs, selh);
pixd = pixDilate(pixd, pixt, selv);
pixErode(pixt, pixd, selh);
pixErode(pixd, pixt, selv);
pixDestroy(&pixt);
selDestroy(&selh);
selDestroy(&selv);
}
return pixd;
}
/*!
* pixCloseSafeBrick()
*
* Input: pixd (<optional>; this can be null, equal to pixs,
* or different from pixs)
* pixs (1 bpp)
* hsize (width of brick Sel)
* vsize (height of brick Sel)
* Return: pixd, or null on error
*
* Notes:
* (1) Sel is a brick with all elements being hits
* (2) The origin is at (x, y) = (hsize/2, vsize/2)
* (3) Do separably if both hsize and vsize are > 1.
* (4) Safe closing adds a border of 0 pixels, of sufficient size so
* that all pixels in input image are processed within
* 32-bit words in the expanded image. As a result, there is
* no special processing for pixels near the boundary, and there
* are no boundary effects. The border is removed at the end.
* (5) There are three cases:
* (a) pixd == null (result into new pixd)
* (b) pixd == pixs (in-place; writes result back to pixs)
* (c) pixd != pixs (puts result into existing pixd)
* (6) For clarity, if the case is known, use these patterns:
* (a) pixd = pixCloseBrick(NULL, pixs, ...);
* (b) pixCloseBrick(pixs, pixs, ...);
* (c) pixCloseBrick(pixd, pixs, ...);
* (7) The size of the result is determined by pixs.
*/
PIX *
pixCloseSafeBrick(PIX *pixd,
PIX *pixs,
l_int32 hsize,
l_int32 vsize)
{
l_int32 maxtrans, bordsize;
PIX *pixsb, *pixt, *pixdb;
SEL *sel, *selh, *selv;
PROCNAME("pixCloseSafeBrick");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
if (pixGetDepth(pixs) != 1)
return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
if (hsize < 1 || vsize < 1)
return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
if (hsize == 1 && vsize == 1)
return pixCopy(pixd, pixs);
/* Symmetric b.c. handles correctly without added pixels */
if (MORPH_BC == SYMMETRIC_MORPH_BC)
return pixCloseBrick(pixd, pixs, hsize, vsize);
maxtrans = L_MAX(hsize / 2, vsize / 2);
bordsize = 32 * ((maxtrans + 31) / 32); /* full 32 bit words */
pixsb = pixAddBorder(pixs, bordsize, 0);
if (hsize == 1 || vsize == 1) { /* no intermediate result */
sel = selCreateBrick(vsize, hsize, vsize / 2, hsize / 2, SEL_HIT);
pixdb = pixClose(NULL, pixsb, sel);
selDestroy(&sel);
}
else { /* do separably */
selh = selCreateBrick(1, hsize, 0, hsize / 2, SEL_HIT);
selv = selCreateBrick(vsize, 1, vsize / 2, 0, SEL_HIT);
pixt = pixDilate(NULL, pixsb, selh);
pixdb = pixDilate(NULL, pixt, selv);
pixErode(pixt, pixdb, selh);
pixErode(pixdb, pixt, selv);
pixDestroy(&pixt);
selDestroy(&selh);
selDestroy(&selv);
}
pixt = pixRemoveBorder(pixdb, bordsize);
pixDestroy(&pixsb);
pixDestroy(&pixdb);
if (!pixd)
pixd = pixt;
else {
pixCopy(pixd, pixt);
pixDestroy(&pixt);
}
return pixd;
}
/*-----------------------------------------------------------------*
* Binary composed morphological (raster) ops with brick Sels *
*-----------------------------------------------------------------*/
/* selectComposableSels()
*
* Input: size (of composed sel)
* direction (L_HORIZ, L_VERT)
* &sel1 (<optional return> contiguous sel; can be null)
* &sel2 (<optional return> comb sel; can be null)
* Return: 0 if OK, 1 on error
*
* Notes:
* (1) When using composable Sels, where the original Sel is
* decomposed into two, the best you can do in terms
* of reducing the computation is by a factor:
*
* 2 * sqrt(size) / size
*
* In practice, you get quite close to this. E.g.,
*
* Sel size | Optimum reduction factor
* -------- ------------------------
* 36 | 1/3
* 64 | 1/4
* 144 | 1/6
* 256 | 1/8
*/
l_int32
selectComposableSels(l_int32 size,
l_int32 direction,
SEL **psel1,
SEL **psel2)
{
l_int32 factor1, factor2;
PROCNAME("selectComposableSels");
if (!psel1 && !psel2)
return ERROR_INT("neither &sel1 nor &sel2 are defined", procName, 1);
if (psel1) *psel1 = NULL;
if (psel2) *psel2 = NULL;
if (size < 1 || size > 250 * 250)
return ERROR_INT("size < 1", procName, 1);
if (direction != L_HORIZ && direction != L_VERT)
return ERROR_INT("invalid direction", procName, 1);
if (selectComposableSizes(size, &factor1, &factor2))
return ERROR_INT("factors not found", procName, 1);
if (psel1) {
if (direction == L_HORIZ)
*psel1 = selCreateBrick(1, factor1, 0, factor1 / 2, SEL_HIT);
else
*psel1 = selCreateBrick(factor1, 1, factor1 / 2 , 0, SEL_HIT);
}
if (psel2)
*psel2 = selCreateComb(factor1, factor2, direction);
return 0;
}
/*!
* selectComposableSizes()
*
* Input: size (of sel to be decomposed)
* &factor1 (<return> larger factor)
* &factor2 (<return> smaller factor)
* Return: 0 if OK, 1 on error
*
* Notes:
* (1) This works for Sel sizes up to 62500, which seems sufficient.
* (2) The composable sel size is typically within +- 1 of
* the requested size. Up to size = 300, the maximum difference
* is +- 2.
* (3) We choose an overall cost function where the penalty for
* the size difference between input and actual is 4 times
* the penalty for additional rasterops.
* (4) Returned values: factor1 >= factor2
* If size > 1, then factor1 > 1.
*/
l_int32
selectComposableSizes(l_int32 size,
l_int32 *pfactor1,
l_int32 *pfactor2)
{
l_int32 i, midval, val1, val2m, val2p;
l_int32 index, prodm, prodp;
l_int32 mincost, totcost, rastcostm, rastcostp, diffm, diffp;
l_int32 lowval[256];
l_int32 hival[256];
l_int32 rastcost[256]; /* excess in sum of sizes (extra rasterops) */
l_int32 diff[256]; /* diff between product (sel size) and input size */
PROCNAME("selectComposableSizes");
if (size < 1 || size > 250 * 250)
return ERROR_INT("size < 1", procName, 1);
if (!pfactor1 || !pfactor2)
return ERROR_INT("&factor1 or &factor2 not defined", procName, 1);
midval = (l_int32)(sqrt((l_float64)size) + 0.001);
if (midval * midval == size) {
*pfactor1 = *pfactor2 = midval;
return 0;
}
/* Set up arrays. For each val1, optimize for lowest diff,
* and save the rastcost, the diff, and the two factors. */
for (val1 = midval + 1, i = 0; val1 > 0; val1--, i++) {
val2m = size / val1;
val2p = val2m + 1;
prodm = val1 * val2m;
prodp = val1 * val2p;
rastcostm = val1 + val2m - 2 * midval;
rastcostp = val1 + val2p - 2 * midval;
diffm = L_ABS(size - prodm);
diffp = L_ABS(size - prodp);
if (diffm <= diffp) {
lowval[i] = L_MIN(val1, val2m);
hival[i] = L_MAX(val1, val2m);
rastcost[i] = rastcostm;
diff[i] = diffm;
}
else {
lowval[i] = L_MIN(val1, val2p);
hival[i] = L_MAX(val1, val2p);
rastcost[i] = rastcostp;
diff[i] = diffp;
}
}
/* Choose the optimum factors; use cost ratio 4 on diff */
mincost = 10000;
for (i = 0; i < midval + 1; i++) {
if (diff[i] == 0 && rastcost[i] < ACCEPTABLE_COST) {
*pfactor1 = hival[i];
*pfactor2 = lowval[i];
return 0;
}
totcost = 4 * diff[i] + rastcost[i];
if (totcost < mincost) {
mincost = totcost;
index = i;
}
}
*pfactor1 = hival[index];
*pfactor2 = lowval[index];
return 0;
}
/*!
* pixDilateCompBrick()
*
* Input: pixd (<optional>; this can be null, equal to pixs,
* or different from pixs)
* pixs (1 bpp)
* hsize (width of brick Sel)
* vsize (height of brick Sel)
* Return: pixd, or null on error
*
* Notes:
* (1) Sel is a brick with all elements being hits
* (2) The origin is at (x, y) = (hsize/2, vsize/2)
* (3) Do compositely for each dimension > 1.
* (4) Do separably if both hsize and vsize are > 1.
* (5) There are three cases:
* (a) pixd == null (result into new pixd)
* (b) pixd == pixs (in-place; writes result back to pixs)
* (c) pixd != pixs (puts result into existing pixd)
* (6) For clarity, if the case is known, use these patterns:
* (a) pixd = pixDilateCompBrick(NULL, pixs, ...);
* (b) pixDilateCompBrick(pixs, pixs, ...);
* (c) pixDilateCompBrick(pixd, pixs, ...);
* (7) The dimensions of the resulting image are determined by pixs.
* (8) CAUTION: both hsize and vsize are being decomposed.
* The decomposer chooses a product of sizes (call them
* 'terms') for each that is close to the input size,
* but not necessarily equal to it. It attempts to optimize:
* (a) for consistency with the input values: the product
* of terms is close to the input size
* (b) for efficiency of the operation: the sum of the
* terms is small; ideally about twice the square
* root of the input size.
* So, for example, if the input hsize = 37, which is
* a prime number, the decomposer will break this into two
* terms, 6 and 6, so that the net result is a dilation
* with hsize = 36.
*/
PIX *
pixDilateCompBrick(PIX *pixd,
PIX *pixs,
l_int32 hsize,
l_int32 vsize)
{
PIX *pixt1, *pixt2, *pixt3;
SEL *selh1, *selh2, *selv1, *selv2;
PROCNAME("pixDilateCompBrick");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
if (pixGetDepth(pixs) != 1)
return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
if (hsize < 1 || vsize < 1)
return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
pixt1 = pixAddBorder(pixs, 32, 0);
if (hsize == 1 && vsize == 1)
return pixCopy(pixd, pixs);
if (hsize > 1)
selectComposableSels(hsize, L_HORIZ, &selh1, &selh2);
if (vsize > 1)
selectComposableSels(vsize, L_VERT, &selv1, &selv2);
if (vsize == 1) {
pixt2 = pixDilate(NULL, pixt1, selh1);
pixt3 = pixDilate(NULL, pixt2, selh2);
}
else if (hsize == 1) {
pixt2 = pixDilate(NULL, pixt1, selv1);
pixt3 = pixDilate(NULL, pixt2, selv2);
}
else {
pixt2 = pixDilate(NULL, pixt1, selh1);
pixt3 = pixDilate(NULL, pixt2, selh2);
pixDilate(pixt2, pixt3, selv1);
pixDilate(pixt3, pixt2, selv2);
}
pixDestroy(&pixt1);
pixDestroy(&pixt2);
if (hsize > 1) {
selDestroy(&selh1);
selDestroy(&selh2);
}
if (vsize > 1) {
selDestroy(&selv1);
selDestroy(&selv2);
}
pixt1 = pixRemoveBorder(pixt3, 32);
pixDestroy(&pixt3);
if (!pixd)
return pixt1;
pixCopy(pixd, pixt1);
pixDestroy(&pixt1);
return pixd;
}
/*!
* pixErodeCompBrick()
*
* Input: pixd (<optional>; this can be null, equal to pixs,
* or different from pixs)
* pixs (1 bpp)
* hsize (width of brick Sel)
* vsize (height of brick Sel)
* Return: pixd, or null on error
*
* Notes:
* (1) Sel is a brick with all elements being hits
* (2) The origin is at (x, y) = (hsize/2, vsize/2)
* (3) Do compositely for each dimension > 1.
* (4) Do separably if both hsize and vsize are > 1.
* (5) There are three cases:
* (a) pixd == null (result into new pixd)
* (b) pixd == pixs (in-place; writes result back to pixs)
* (c) pixd != pixs (puts result into existing pixd)
* (6) For clarity, if the case is known, use these patterns:
* (a) pixd = pixErodeCompBrick(NULL, pixs, ...);
* (b) pixErodeCompBrick(pixs, pixs, ...);
* (c) pixErodeCompBrick(pixd, pixs, ...);
* (7) The dimensions of the resulting image are determined by pixs.
* (8) CAUTION: both hsize and vsize are being decomposed.
* The decomposer chooses a product of sizes (call them
* 'terms') for each that is close to the input size,
* but not necessarily equal to it. It attempts to optimize:
* (a) for consistency with the input values: the product
* of terms is close to the input size
* (b) for efficiency of the operation: the sum of the
* terms is small; ideally about twice the square
* root of the input size.
* So, for example, if the input hsize = 37, which is
* a prime number, the decomposer will break this into two
* terms, 6 and 6, so that the net result is a dilation
* with hsize = 36.
*/
PIX *
pixErodeCompBrick(PIX *pixd,
PIX *pixs,
l_int32 hsize,
l_int32 vsize)
{
PIX *pixt;
SEL *selh1, *selh2, *selv1, *selv2;
PROCNAME("pixErodeCompBrick");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
if (pixGetDepth(pixs) != 1)
return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
if (hsize < 1 || vsize < 1)
return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
if (hsize == 1 && vsize == 1)
return pixCopy(pixd, pixs);
if (hsize > 1)
selectComposableSels(hsize, L_HORIZ, &selh1, &selh2);
if (vsize > 1)
selectComposableSels(vsize, L_VERT, &selv1, &selv2);
if (vsize == 1) {
pixt = pixErode(NULL, pixs, selh1);
pixd = pixErode(pixd, pixt, selh2);
}
else if (hsize == 1) {
pixt = pixErode(NULL, pixs, selv1);
pixd = pixErode(pixd, pixt, selv2);
}
else {
pixt = pixErode(NULL, pixs, selh1);
pixd = pixErode(pixd, pixt, selh2);
pixErode(pixt, pixd, selv1);
pixErode(pixd, pixt, selv2);
}
pixDestroy(&pixt);
if (hsize > 1) {
selDestroy(&selh1);
selDestroy(&selh2);
}
if (vsize > 1) {
selDestroy(&selv1);
selDestroy(&selv2);
}
return pixd;
}
/*!
* pixOpenCompBrick()
*
* Input: pixd (<optional>; this can be null, equal to pixs,
* or different from pixs)
* pixs (1 bpp)
* hsize (width of brick Sel)
* vsize (height of brick Sel)
* Return: pixd, or null on error
*
* Notes:
* (1) Sel is a brick with all elements being hits
* (2) The origin is at (x, y) = (hsize/2, vsize/2)
* (3) Do compositely for each dimension > 1.
* (4) Do separably if both hsize and vsize are > 1.
* (5) There are three cases:
* (a) pixd == null (result into new pixd)
* (b) pixd == pixs (in-place; writes result back to pixs)
* (c) pixd != pixs (puts result into existing pixd)
* (6) For clarity, if the case is known, use these patterns:
* (a) pixd = pixOpenCompBrick(NULL, pixs, ...);
* (b) pixOpenCompBrick(pixs, pixs, ...);
* (c) pixOpenCompBrick(pixd, pixs, ...);
* (7) The dimensions of the resulting image are determined by pixs.
* (8) CAUTION: both hsize and vsize are being decomposed.
* The decomposer chooses a product of sizes (call them
* 'terms') for each that is close to the input size,
* but not necessarily equal to it. It attempts to optimize:
* (a) for consistency with the input values: the product
* of terms is close to the input size
* (b) for efficiency of the operation: the sum of the
* terms is small; ideally about twice the square
* root of the input size.
* So, for example, if the input hsize = 37, which is
* a prime number, the decomposer will break this into two
* terms, 6 and 6, so that the net result is a dilation
* with hsize = 36.
*/
PIX *
pixOpenCompBrick(PIX *pixd,
PIX *pixs,
l_int32 hsize,
l_int32 vsize)
{
PIX *pixt;
SEL *selh1, *selh2, *selv1, *selv2;
PROCNAME("pixOpenCompBrick");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
if (pixGetDepth(pixs) != 1)
return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
if (hsize < 1 || vsize < 1)
return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
if (hsize == 1 && vsize == 1)
return pixCopy(pixd, pixs);
if (hsize > 1)
selectComposableSels(hsize, L_HORIZ, &selh1, &selh2);
if (vsize > 1)
selectComposableSels(vsize, L_VERT, &selv1, &selv2);
if (vsize == 1) {
pixt = pixErode(NULL, pixs, selh1);
pixd = pixErode(pixd, pixt, selh2);
pixDilate(pixt, pixd, selh1);
pixDilate(pixd, pixt, selh2);
}
else if (hsize == 1) {
pixt = pixErode(NULL, pixs, selv1);
pixd = pixErode(pixd, pixt, selv2);
pixDilate(pixt, pixd, selv1);
pixDilate(pixd, pixt, selv2);
}
else { /* do separably */
pixt = pixErode(NULL, pixs, selh1);
pixd = pixErode(pixd, pixt, selh2);
pixErode(pixt, pixd, selv1);
pixErode(pixd, pixt, selv2);
pixDilate(pixt, pixd, selh1);
pixDilate(pixd, pixt, selh2);
pixDilate(pixt, pixd, selv1);
pixDilate(pixd, pixt, selv2);
}
pixDestroy(&pixt);
if (hsize > 1) {
selDestroy(&selh1);
selDestroy(&selh2);
}
if (vsize > 1) {
selDestroy(&selv1);
selDestroy(&selv2);
}
return pixd;
}
/*!
* pixCloseCompBrick()
*
* Input: pixd (<optional>; this can be null, equal to pixs,
* or different from pixs)
* pixs (1 bpp)
* hsize (width of brick Sel)
* vsize (height of brick Sel)
* Return: pixd, or null on error
*
* Notes:
* (1) Sel is a brick with all elements being hits
* (2) The origin is at (x, y) = (hsize/2, vsize/2)
* (3) Do compositely for each dimension > 1.
* (4) Do separably if both hsize and vsize are > 1.
* (5) There are three cases:
* (a) pixd == null (result into new pixd)
* (b) pixd == pixs (in-place; writes result back to pixs)
* (c) pixd != pixs (puts result into existing pixd)
* (6) For clarity, if the case is known, use these patterns:
* (a) pixd = pixCloseCompBrick(NULL, pixs, ...);
* (b) pixCloseCompBrick(pixs, pixs, ...);
* (c) pixCloseCompBrick(pixd, pixs, ...);
* (7) The dimensions of the resulting image are determined by pixs.
* (8) CAUTION: both hsize and vsize are being decomposed.
* The decomposer chooses a product of sizes (call them
* 'terms') for each that is close to the input size,
* but not necessarily equal to it. It attempts to optimize:
* (a) for consistency with the input values: the product
* of terms is close to the input size
* (b) for efficiency of the operation: the sum of the
* terms is small; ideally about twice the square
* root of the input size.
* So, for example, if the input hsize = 37, which is
* a prime number, the decomposer will break this into two
* terms, 6 and 6, so that the net result is a dilation
* with hsize = 36.
*/
PIX *
pixCloseCompBrick(PIX *pixd,
PIX *pixs,
l_int32 hsize,
l_int32 vsize)
{
PIX *pixt;
SEL *selh1, *selh2, *selv1, *selv2;
PROCNAME("pixCloseCompBrick");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
if (pixGetDepth(pixs) != 1)
return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
if (hsize < 1 || vsize < 1)
return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
if (hsize == 1 && vsize == 1)
return pixCopy(pixd, pixs);
if (hsize > 1)
selectComposableSels(hsize, L_HORIZ, &selh1, &selh2);
if (vsize > 1)
selectComposableSels(vsize, L_VERT, &selv1, &selv2);
if (vsize == 1) {
pixt = pixDilate(NULL, pixs, selh1);
pixd = pixDilate(pixd, pixt, selh2);
pixErode(pixt, pixd, selh1);
pixErode(pixd, pixt, selh2);
}
else if (hsize == 1) {
pixt = pixDilate(NULL, pixs, selv1);
pixd = pixDilate(pixd, pixt, selv2);
pixErode(pixt, pixd, selv1);
pixErode(pixd, pixt, selv2);
}
else { /* do separably */
pixt = pixDilate(NULL, pixs, selh1);
pixd = pixDilate(pixd, pixt, selh2);
pixDilate(pixt, pixd, selv1);
pixDilate(pixd, pixt, selv2);
pixErode(pixt, pixd, selh1);
pixErode(pixd, pixt, selh2);
pixErode(pixt, pixd, selv1);
pixErode(pixd, pixt, selv2);
}
pixDestroy(&pixt);
if (hsize > 1) {
selDestroy(&selh1);
selDestroy(&selh2);
}
if (vsize > 1) {
selDestroy(&selv1);
selDestroy(&selv2);
}
return pixd;
}
/*!
* pixCloseSafeCompBrick()
*
* Input: pixd (<optional>; this can be null, equal to pixs,
* or different from pixs)
* pixs (1 bpp)
* hsize (width of brick Sel)
* vsize (height of brick Sel)
* Return: pixd, or null on error
*
* Notes:
* (1) Sel is a brick with all elements being hits
* (2) The origin is at (x, y) = (hsize/2, vsize/2)
* (3) Do compositely for each dimension > 1.
* (4) Do separably if both hsize and vsize are > 1.
* (5) Safe closing adds a border of 0 pixels, of sufficient size so
* that all pixels in input image are processed within
* 32-bit words in the expanded image. As a result, there is
* no special processing for pixels near the boundary, and there
* are no boundary effects. The border is removed at the end.
* (6) There are three cases:
* (a) pixd == null (result into new pixd)
* (b) pixd == pixs (in-place; writes result back to pixs)
* (c) pixd != pixs (puts result into existing pixd)
* (7) For clarity, if the case is known, use these patterns:
* (a) pixd = pixCloseSafeCompBrick(NULL, pixs, ...);
* (b) pixCloseSafeCompBrick(pixs, pixs, ...);
* (c) pixCloseSafeCompBrick(pixd, pixs, ...);
* (8) The dimensions of the resulting image are determined by pixs.
* (9) CAUTION: both hsize and vsize are being decomposed.
* The decomposer chooses a product of sizes (call them
* 'terms') for each that is close to the input size,
* but not necessarily equal to it. It attempts to optimize:
* (a) for consistency with the input values: the product
* of terms is close to the input size
* (b) for efficiency of the operation: the sum of the
* terms is small; ideally about twice the square
* root of the input size.
* So, for example, if the input hsize = 37, which is
* a prime number, the decomposer will break this into two
* terms, 6 and 6, so that the net result is a dilation
* with hsize = 36.
*/
PIX *
pixCloseSafeCompBrick(PIX *pixd,
PIX *pixs,
l_int32 hsize,
l_int32 vsize)
{
l_int32 maxtrans, bordsize;
PIX *pixsb, *pixt, *pixdb;
SEL *selh1, *selh2, *selv1, *selv2;
PROCNAME("pixCloseSafeCompBrick");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
if (pixGetDepth(pixs) != 1)
return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
if (hsize < 1 || vsize < 1)
return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
if (hsize == 1 && vsize == 1)
return pixCopy(pixd, pixs);
/* Symmetric b.c. handles correctly without added pixels */
if (MORPH_BC == SYMMETRIC_MORPH_BC)
return pixCloseCompBrick(pixd, pixs, hsize, vsize);
maxtrans = L_MAX(hsize / 2, vsize / 2);
bordsize = 32 * ((maxtrans + 31) / 32); /* full 32 bit words */
pixsb = pixAddBorder(pixs, bordsize, 0);
if (hsize > 1)
selectComposableSels(hsize, L_HORIZ, &selh1, &selh2);
if (vsize > 1)
selectComposableSels(vsize, L_VERT, &selv1, &selv2);
if (vsize == 1) {
pixt = pixDilate(NULL, pixsb, selh1);
pixdb = pixDilate(NULL, pixt, selh2);
pixErode(pixt, pixdb, selh1);
pixErode(pixdb, pixt, selh2);
}
else if (hsize == 1) {
pixt = pixDilate(NULL, pixsb, selv1);
pixdb = pixDilate(NULL, pixt, selv2);
pixErode(pixt, pixdb, selv1);
pixErode(pixdb, pixt, selv2);
}
else { /* do separably */
pixt = pixDilate(NULL, pixsb, selh1);
pixdb = pixDilate(NULL, pixt, selh2);
pixDilate(pixt, pixdb, selv1);
pixDilate(pixdb, pixt, selv2);
pixErode(pixt, pixdb, selh1);
pixErode(pixdb, pixt, selh2);
pixErode(pixt, pixdb, selv1);
pixErode(pixdb, pixt, selv2);
}
pixDestroy(&pixt);
pixt = pixRemoveBorder(pixdb, bordsize);
pixDestroy(&pixsb);
pixDestroy(&pixdb);
if (!pixd)
pixd = pixt;
else {
pixCopy(pixd, pixt);
pixDestroy(&pixt);
}
if (hsize > 1) {
selDestroy(&selh1);
selDestroy(&selh2);
}
if (vsize > 1) {
selDestroy(&selv1);
selDestroy(&selv2);
}
return pixd;
}
/*-----------------------------------------------------------------*
* Functions associated with boundary conditions *
*-----------------------------------------------------------------*/
/*!
* resetMorphBoundaryCondition()
*
* Input: bc (SYMMETRIC_MORPH_BC, ASYMMETRIC_MORPH_BC)
* Return: void
*/
void
resetMorphBoundaryCondition(l_int32 bc)
{
PROCNAME("resetMorphBoundaryCondition");
if (bc != SYMMETRIC_MORPH_BC && bc != ASYMMETRIC_MORPH_BC) {
L_WARNING("invalid bc; using asymmetric", procName);
bc = ASYMMETRIC_MORPH_BC;
}
MORPH_BC = bc;
return;
}
/*!
* getMorphBorderPixelColor()
*
* Input: type (L_MORPH_DILATE, L_MORPH_ERODE)
* depth (of pix)
* Return: color of border pixels for this operation
*/
l_uint32
getMorphBorderPixelColor(l_int32 type,
l_int32 depth)
{
PROCNAME("getMorphBorderPixelColor");
if (type != L_MORPH_DILATE && type != L_MORPH_ERODE)
return ERROR_INT("invalid type", procName, 0);
if (depth != 1 && depth != 2 && depth != 4 && depth != 8 &&
depth != 16 && depth != 32)
return ERROR_INT("invalid depth", procName, 0);
if (MORPH_BC == ASYMMETRIC_MORPH_BC || type == L_MORPH_DILATE)
return 0;
/* Symmetric & erosion */
if (depth < 32)
return ((1 << depth) - 1);
else /* depth == 32 */
return 0xffffff00;
}
/*-----------------------------------------------------------------*
* Static helpers for arg processing *
*-----------------------------------------------------------------*/
/*!
* processMorphArgs1()
*
* Input: pixd (<optional>; this can be null, equal to pixs,
* or different from pixs)
* pixs (1 bpp)
* sel
* &pixt (<returned>)
* Return: pixd, or null on error.
*
* Notes:
* (1) This is used for generic erosion, dilation and HMT.
*/
static PIX *
processMorphArgs1(PIX *pixd,
PIX *pixs,
SEL *sel,
PIX **ppixt)
{
l_int32 sx, sy;
PROCNAME("processMorphArgs1");
if (!ppixt)
return (PIX *)ERROR_PTR("&pixt not defined", procName, pixd);
*ppixt = NULL;
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
if (!sel)
return (PIX *)ERROR_PTR("sel not defined", procName, pixd);
if (pixGetDepth(pixs) != 1)
return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
selGetParameters(sel, &sx, &sy, NULL, NULL);
if (sx == 0 || sy == 0)
return (PIX *)ERROR_PTR("sel of size 0", procName, pixd);
/* We require pixd to exist and to be the same size as pixs.
* Further, pixt must be a copy (or clone) of pixs. */
if (!pixd) {
if ((pixd = pixCreateTemplate(pixs)) == NULL)
return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
*ppixt = pixClone(pixs);
}
else {
pixResizeImageData(pixd, pixs);
if (pixd == pixs) { /* in-place; must make a copy of pixs */
if ((*ppixt = pixCopy(NULL, pixs)) == NULL)
return (PIX *)ERROR_PTR("pixt not made", procName, pixd);
}
else
*ppixt = pixClone(pixs);
}
return pixd;
}
/*!
* processMorphArgs2()
*
* This is used for generic openings and closings.
*/
static PIX *
processMorphArgs2(PIX *pixd,
PIX *pixs,
SEL *sel)
{
l_int32 sx, sy;
PROCNAME("processMorphArgs2");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
if (!sel)
return (PIX *)ERROR_PTR("sel not defined", procName, pixd);
if (pixGetDepth(pixs) != 1)
return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
selGetParameters(sel, &sx, &sy, NULL, NULL);
if (sx == 0 || sy == 0)
return (PIX *)ERROR_PTR("sel of size 0", procName, pixd);
if (!pixd)
return pixCreateTemplate(pixs);
pixResizeImageData(pixd, pixs);
return pixd;
}