summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'leptonica/src/morph.c')
-rw-r--r--leptonica/src/morph.c1915
1 files changed, 1915 insertions, 0 deletions
diff --git a/leptonica/src/morph.c b/leptonica/src/morph.c
new file mode 100644
index 00000000..0f01ea21
--- /dev/null
+++ b/leptonica/src/morph.c
@@ -0,0 +1,1915 @@
+/*====================================================================*
+ - 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 morph.c
+ * <pre>
+ *
+ * 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 snprintf.
+ * 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 snprintf.
+ * 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 snprintf.
+ * 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.
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif /* HAVE_CONFIG_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(). */
+LEPT_DLL 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 *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixDilate()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixErode()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixHMT()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixOpen()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixClose()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixCloseSafe()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixOpenGeneralized()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief pixCloseGeneralized()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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 *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief pixDilateBrick()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ if (!sel)
+ return (PIX *)ERROR_PTR("sel not made", procName, pixd);
+ pixd = pixDilate(pixd, pixs, sel);
+ selDestroy(&sel);
+ } else {
+ if ((selh = selCreateBrick(1, hsize, 0, hsize / 2, SEL_HIT)) == NULL)
+ return (PIX *)ERROR_PTR("selh not made", procName, pixd);
+ if ((selv = selCreateBrick(vsize, 1, vsize / 2, 0, SEL_HIT)) == NULL) {
+ selDestroy(&selh);
+ return (PIX *)ERROR_PTR("selv not made", procName, pixd);
+ }
+ pixt = pixDilate(NULL, pixs, selh);
+ pixd = pixDilate(pixd, pixt, selv);
+ pixDestroy(&pixt);
+ selDestroy(&selh);
+ selDestroy(&selv);
+ }
+
+ return pixd;
+}
+
+
+/*!
+ * \brief pixErodeBrick()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ if (!sel)
+ return (PIX *)ERROR_PTR("sel not made", procName, pixd);
+ pixd = pixErode(pixd, pixs, sel);
+ selDestroy(&sel);
+ } else {
+ if ((selh = selCreateBrick(1, hsize, 0, hsize / 2, SEL_HIT)) == NULL)
+ return (PIX *)ERROR_PTR("selh not made", procName, pixd);
+ if ((selv = selCreateBrick(vsize, 1, vsize / 2, 0, SEL_HIT)) == NULL) {
+ selDestroy(&selh);
+ return (PIX *)ERROR_PTR("selv not made", procName, pixd);
+ }
+ pixt = pixErode(NULL, pixs, selh);
+ pixd = pixErode(pixd, pixt, selv);
+ pixDestroy(&pixt);
+ selDestroy(&selh);
+ selDestroy(&selv);
+ }
+
+ return pixd;
+}
+
+
+/*!
+ * \brief pixOpenBrick()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ if (!sel)
+ return (PIX *)ERROR_PTR("sel not made", procName, pixd);
+ pixd = pixOpen(pixd, pixs, sel);
+ selDestroy(&sel);
+ } else { /* do separably */
+ if ((selh = selCreateBrick(1, hsize, 0, hsize / 2, SEL_HIT)) == NULL)
+ return (PIX *)ERROR_PTR("selh not made", procName, pixd);
+ if ((selv = selCreateBrick(vsize, 1, vsize / 2, 0, SEL_HIT)) == NULL) {
+ selDestroy(&selh);
+ return (PIX *)ERROR_PTR("selv not made", procName, pixd);
+ }
+ 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;
+}
+
+
+/*!
+ * \brief pixCloseBrick()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ if (!sel)
+ return (PIX *)ERROR_PTR("sel not made", procName, pixd);
+ pixd = pixClose(pixd, pixs, sel);
+ selDestroy(&sel);
+ } else { /* do separably */
+ if ((selh = selCreateBrick(1, hsize, 0, hsize / 2, SEL_HIT)) == NULL)
+ return (PIX *)ERROR_PTR("selh not made", procName, pixd);
+ if ((selv = selCreateBrick(vsize, 1, vsize / 2, 0, SEL_HIT)) == NULL) {
+ selDestroy(&selh);
+ return (PIX *)ERROR_PTR("selv not made", procName, pixd);
+ }
+ 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;
+}
+
+
+/*!
+ * \brief pixCloseSafeBrick()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+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);
+ if (!sel) {
+ pixDestroy(&pixsb);
+ return (PIX *)ERROR_PTR("sel not made", procName, pixd);
+ }
+ 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);
+ if (!selh || !selv) {
+ selDestroy(&selh);
+ selDestroy(&selv);
+ pixDestroy(&pixsb);
+ return (PIX *)ERROR_PTR("selh and selv not both made",
+ procName, pixd);
+ }
+ 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 *
+ *-----------------------------------------------------------------*/
+/* \brief selectComposableSels()
+ *
+ * \param[in] size of composed sel
+ * \param[in] direction L_HORIZ, L_VERT
+ * \param[out] psel1 [optional] contiguous sel; can be null
+ * \param[out] psel2 [optional] comb sel; can be null
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+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 > 10000)
+ return ERROR_INT("size < 1 or size > 10000", 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;
+}
+
+
+/*!
+ * \brief selectComposableSizes()
+ *
+ * \param[in] size of sel to be decomposed
+ * \param[out] pfactor1 larger factor
+ * \param[out] pfactor2 smaller factor
+ * \return 0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ * (1) This works for Sel sizes up to 10000, 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.
+ * </pre>
+ */
+l_ok
+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 > 10000)
+ return ERROR_INT("size < 1 or size > 10000", 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;
+ index = 1; /* unimportant initial value */
+ 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;
+}
+
+
+/*!
+ * \brief pixDilateCompBrick()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixDilateCompBrick(PIX *pixd,
+ PIX *pixs,
+ l_int32 hsize,
+ l_int32 vsize)
+{
+PIX *pix1, *pix2, *pix3;
+SEL *selh1 = NULL;
+SEL *selh2 = NULL;
+SEL *selv1 = NULL;
+SEL *selv2 = NULL;
+
+ 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);
+
+ if (hsize == 1 && vsize == 1)
+ return pixCopy(pixd, pixs);
+ if (hsize > 1) {
+ if (selectComposableSels(hsize, L_HORIZ, &selh1, &selh2)) {
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ return (PIX *)ERROR_PTR("horiz sels not made", procName, pixd);
+ }
+ }
+ if (vsize > 1) {
+ if (selectComposableSels(vsize, L_VERT, &selv1, &selv2)) {
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ selDestroy(&selv1);
+ selDestroy(&selv2);
+ return (PIX *)ERROR_PTR("vert sels not made", procName, pixd);
+ }
+ }
+
+ pix1 = pixAddBorder(pixs, 32, 0);
+ if (vsize == 1) {
+ pix2 = pixDilate(NULL, pix1, selh1);
+ pix3 = pixDilate(NULL, pix2, selh2);
+ } else if (hsize == 1) {
+ pix2 = pixDilate(NULL, pix1, selv1);
+ pix3 = pixDilate(NULL, pix2, selv2);
+ } else {
+ pix2 = pixDilate(NULL, pix1, selh1);
+ pix3 = pixDilate(NULL, pix2, selh2);
+ pixDilate(pix2, pix3, selv1);
+ pixDilate(pix3, pix2, selv2);
+ }
+ pixDestroy(&pix1);
+ pixDestroy(&pix2);
+
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ selDestroy(&selv1);
+ selDestroy(&selv2);
+
+ pix1 = pixRemoveBorder(pix3, 32);
+ pixDestroy(&pix3);
+ if (!pixd)
+ return pix1;
+ pixCopy(pixd, pix1);
+ pixDestroy(&pix1);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixErodeCompBrick()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixErodeCompBrick(PIX *pixd,
+ PIX *pixs,
+ l_int32 hsize,
+ l_int32 vsize)
+{
+PIX *pixt;
+SEL *selh1 = NULL;
+SEL *selh2 = NULL;
+SEL *selv1 = NULL;
+SEL *selv2 = NULL;
+
+ 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) {
+ if (selectComposableSels(hsize, L_HORIZ, &selh1, &selh2)) {
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ return (PIX *)ERROR_PTR("horiz sels not made", procName, pixd);
+ }
+ }
+ if (vsize > 1) {
+ if (selectComposableSels(vsize, L_VERT, &selv1, &selv2)) {
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ selDestroy(&selv1);
+ selDestroy(&selv2);
+ return (PIX *)ERROR_PTR("vert sels not made", procName, pixd);
+ }
+ }
+
+ 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);
+
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ selDestroy(&selv1);
+ selDestroy(&selv2);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixOpenCompBrick()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixOpenCompBrick(PIX *pixd,
+ PIX *pixs,
+ l_int32 hsize,
+ l_int32 vsize)
+{
+PIX *pixt;
+SEL *selh1 = NULL;
+SEL *selh2 = NULL;
+SEL *selv1 = NULL;
+SEL *selv2 = NULL;
+
+ 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) {
+ if (selectComposableSels(hsize, L_HORIZ, &selh1, &selh2)) {
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ return (PIX *)ERROR_PTR("horiz sels not made", procName, pixd);
+ }
+ }
+ if (vsize > 1) {
+ if (selectComposableSels(vsize, L_VERT, &selv1, &selv2)) {
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ selDestroy(&selv1);
+ selDestroy(&selv2);
+ return (PIX *)ERROR_PTR("vert sels not made", procName, pixd);
+ }
+ }
+
+ 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);
+
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ selDestroy(&selv1);
+ selDestroy(&selv2);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixCloseCompBrick()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixCloseCompBrick(PIX *pixd,
+ PIX *pixs,
+ l_int32 hsize,
+ l_int32 vsize)
+{
+PIX *pixt;
+SEL *selh1 = NULL;
+SEL *selh2 = NULL;
+SEL *selv1 = NULL;
+SEL *selv2 = NULL;
+
+ 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) {
+ if (selectComposableSels(hsize, L_HORIZ, &selh1, &selh2)) {
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ return (PIX *)ERROR_PTR("horiz sels not made", procName, pixd);
+ }
+ }
+ if (vsize > 1) {
+ if (selectComposableSels(vsize, L_VERT, &selv1, &selv2)) {
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ selDestroy(&selv1);
+ selDestroy(&selv2);
+ return (PIX *)ERROR_PTR("vert sels not made", procName, pixd);
+ }
+ }
+
+ 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);
+
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ selDestroy(&selv1);
+ selDestroy(&selv2);
+ return pixd;
+}
+
+
+/*!
+ * \brief pixCloseSafeCompBrick()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] hsize width of brick Sel
+ * \param[in] vsize height of brick Sel
+ * \return pixd, or NULL on error
+ *
+ * <pre>
+ * 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.
+ * </pre>
+ */
+PIX *
+pixCloseSafeCompBrick(PIX *pixd,
+ PIX *pixs,
+ l_int32 hsize,
+ l_int32 vsize)
+{
+l_int32 maxtrans, bordsize;
+PIX *pixsb, *pixt, *pixdb;
+SEL *selh1 = NULL;
+SEL *selh2 = NULL;
+SEL *selv1 = NULL;
+SEL *selv2 = NULL;
+
+ 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);
+
+ if (hsize > 1) {
+ if (selectComposableSels(hsize, L_HORIZ, &selh1, &selh2)) {
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ return (PIX *)ERROR_PTR("horiz sels not made", procName, pixd);
+ }
+ }
+ if (vsize > 1) {
+ if (selectComposableSels(vsize, L_VERT, &selv1, &selv2)) {
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ selDestroy(&selv1);
+ selDestroy(&selv2);
+ return (PIX *)ERROR_PTR("vert sels not made", procName, pixd);
+ }
+ }
+
+ maxtrans = L_MAX(hsize / 2, vsize / 2);
+ bordsize = 32 * ((maxtrans + 31) / 32); /* full 32 bit words */
+ pixsb = pixAddBorder(pixs, bordsize, 0);
+
+ 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);
+ }
+
+ selDestroy(&selh1);
+ selDestroy(&selh2);
+ selDestroy(&selv1);
+ selDestroy(&selv2);
+ return pixd;
+}
+
+
+/*-----------------------------------------------------------------*
+ * Functions associated with boundary conditions *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief resetMorphBoundaryCondition()
+ *
+ * \param[in] 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\n", procName);
+ bc = ASYMMETRIC_MORPH_BC;
+ }
+ MORPH_BC = bc;
+ return;
+}
+
+
+/*!
+ * \brief getMorphBorderPixelColor()
+ *
+ * \param[in] type L_MORPH_DILATE, L_MORPH_ERODE
+ * \param[in] 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 *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief processMorphArgs1()
+ *
+ * \param[in] pixd [optional]; this can be null, equal to pixs,
+ * or different from pixs
+ * \param[in] pixs 1 bpp
+ * \param[in] sel
+ * \param[out] ppixt copy or clone of %pixs
+ * \return pixd, or NULL on error.
+ *
+ * <pre>
+ * Notes:
+ * (1) This is used for generic erosion, dilation and HMT.
+ * </pre>
+ */
+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;
+}
+
+
+/*!
+ * \brief 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;
+}