diff mupdf-source/thirdparty/leptonica/src/morphdwa.c @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mupdf-source/thirdparty/leptonica/src/morphdwa.c	Mon Sep 15 11:43:07 2025 +0200
@@ -0,0 +1,1573 @@
+/*====================================================================*
+ -  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 morphdwa.c
+ * <pre>
+ *
+ *    Binary morphological (dwa) ops with brick Sels
+ *         PIX     *pixDilateBrickDwa()
+ *         PIX     *pixErodeBrickDwa()
+ *         PIX     *pixOpenBrickDwa()
+ *         PIX     *pixCloseBrickDwa()
+ *
+ *    Binary composite morphological (dwa) ops with brick Sels
+ *         PIX     *pixDilateCompBrickDwa()
+ *         PIX     *pixErodeCompBrickDwa()
+ *         PIX     *pixOpenCompBrickDwa()
+ *         PIX     *pixCloseCompBrickDwa()
+ *
+ *    Binary extended composite morphological (dwa) ops with brick Sels
+ *         PIX     *pixDilateCompBrickExtendDwa()
+ *         PIX     *pixErodeCompBrickExtendDwa()
+ *         PIX     *pixOpenCompBrickExtendDwa()
+ *         PIX     *pixCloseCompBrickExtendDwa()
+ *         l_int32  getExtendedCompositeParameters()
+ *
+ *    These are higher-level interfaces for dwa morphology with brick Sels.
+ *    Because many morphological operations are performed using
+ *    separable brick Sels, it is useful to have a simple interface
+ *    for this.
+ *
+ *    We have included all 58 of the brick Sels that are generated
+ *    by selaAddBasic().  These are sufficient for all the decomposable
+ *    bricks up to size 63, which is the limit for dwa Sels with
+ *    origins at the center of the Sel.
+ *
+ *    All three sets can be used as the basic interface for general
+ *    brick operations.  Here are the internal calling sequences:
+ *
+ *      (1) If you try to apply a non-decomposable operation, such as
+ *          pixErodeBrickDwa(), with a Sel size that doesn't exist,
+ *          this calls a decomposable operation, pixErodeCompBrickDwa(),
+ *          instead.  This can differ in linear Sel size by up to
+ *          2 pixels from the request.
+ *
+ *      (2) If either Sel brick dimension is greater than 63, the extended
+ *          composite function is called.
+ *
+ *      (3) The extended composite function calls the composite function
+ *          a number of times with size 63, and once with size < 63.
+ *          Because each operation with a size of 63 is done compositely
+ *          with 7 x 9 (exactly 63), the net result is correct in
+ *          length to within 2 pixels.
+ *
+ *    For composite operations, both using a comb and extended (beyond 63),
+ *    horizontal and vertical operations are composed separately
+ *    and sequentially.
+ *
+ *    We have also included use of all the 76 comb Sels that are generated
+ *    by selaAddDwaCombs().  The generated code is in dwacomb.2.c
+ *    and dwacomblow.2.c.  These are used for the composite dwa
+ *    brick operations.
+ *
+ *    The non-composite brick operations, such as pixDilateBrickDwa(),
+ *    will call the associated composite operation in situations where
+ *    the requisite brick Sel has not been compiled into fmorphgen*.1.c.
+ *
+ *    If you want to use brick Sels that are not represented in the
+ *    basic set of 58, you must generate the dwa code to implement them.
+ *    You have three choices for how to use these:
+ *
+ *    (1) Add both the new Sels and the dwa code to the library:
+ *        ~ For simplicity, add your new brick Sels to those defined
+ *          in selaAddBasic().
+ *        ~ Recompile the library.
+ *        ~ Make prog/fmorphautogen.
+ *        ~ Run prog/fmorphautogen, to generate new versions of the
+ *          dwa code in fmorphgen.1.c and fmorphgenlow.1.c.
+ *        ~ Copy these two files to src.
+ *        ~ Recompile the library again.
+ *        ~ Use the new brick Sels in your program and compile it.
+ *
+ *    (2) Make both the new Sels and dwa code outside the library,
+ *        and link it directly to an executable:
+ *        ~ Write a function to generate the new Sels in a Sela, and call
+ *          fmorphautogen(sela, <N>, filename) to generate the code.
+ *        ~ Compile your program that uses the newly generated function
+ *          pixMorphDwa_<N>(), and link to the two new C files.
+ *
+ *    (3) Make the new Sels in the library and use the dwa code outside it:
+ *        ~ Add code in the library to generate your new brick Sels.
+ *          (It is suggested that you NOT add these Sels to the
+ *          selaAddBasic() function; write a new function that generates
+ *          a new Sela.)
+ *        ~ Recompile the library.
+ *        ~ Write a small program that generates the Sela and calls
+ *          fmorphautogen(sela, <N>, filename) to generate the code.
+ *        ~ Compile your program that uses the newly generated function
+ *          pixMorphDwa_<N>(), and link to the two new C files.
+ *       As an example of this approach, see prog/dwamorph*_reg.c:
+ *        ~ added selaAddDwaLinear() to sel2.c
+ *        ~ wrote dwamorph1_reg.c, to generate the dwa code.
+ *        ~ compiled and linked the generated code with the application,
+ *          dwamorph2_reg.c.  (Note: because this was a regression test,
+ *          dwamorph1_reg also builds and runs the application program.)
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif  /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_SEL_LOOKUP   0
+#endif  /* ~NO_CONSOLE_IO */
+
+/*-----------------------------------------------------------------*
+ *           Binary morphological (dwa) ops with brick Sels        *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief   pixDilateBrickDwa()
+ *
+ * \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) These implement 2D brick Sels, using linear Sels generated
+ *          with selaAddBasic().
+ *      (2) A brick Sel has hits for all elements.
+ *      (3) The origin of the Sel is at (x, y) = (hsize/2, vsize/2)
+ *      (4) Do separably if both hsize and vsize are > 1.
+ *      (5) It is necessary that both horizontal and vertical Sels
+ *          of the input size are defined in the basic sela.
+ *      (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 = pixDilateBrickDwa(NULL, pixs, ...);
+ *          (b) pixDilateBrickDwa(pixs, pixs, ...);
+ *          (c) pixDilateBrickDwa(pixd, pixs, ...);
+ *      (8) The size of pixd is determined by pixs.
+ *      (9) If either linear Sel is not found, this calls
+ *          the appropriate decomposible function.
+ * </pre>
+ */
+PIX *
+pixDilateBrickDwa(PIX     *pixd,
+                  PIX     *pixs,
+                  l_int32  hsize,
+                  l_int32  vsize)
+{
+l_int32  found;
+char    *selnameh, *selnamev;
+SELA    *sela;
+PIX     *pixt1, *pixt2, *pixt3;
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", __func__, pixd);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(pixd, pixs);
+
+    sela = selaAddBasic(NULL);
+    found = TRUE;
+    selnameh = selnamev = NULL;
+    if (hsize > 1) {
+        selnameh = selaGetBrickName(sela, hsize, 1);
+        if (!selnameh) found = FALSE;
+    }
+    if (vsize > 1) {
+        selnamev = selaGetBrickName(sela, 1, vsize);
+        if (!selnamev) found = FALSE;
+    }
+    selaDestroy(&sela);
+    if (!found) {
+        L_INFO("Calling the decomposable dwa function\n", __func__);
+        if (selnameh) LEPT_FREE(selnameh);
+        if (selnamev) LEPT_FREE(selnamev);
+        return pixDilateCompBrickDwa(pixd, pixs, hsize, vsize);
+    }
+
+    if (vsize == 1) {
+        pixt2 = pixMorphDwa_1(NULL, pixs, L_MORPH_DILATE, selnameh);
+        LEPT_FREE(selnameh);
+    } else if (hsize == 1) {
+        pixt2 = pixMorphDwa_1(NULL, pixs, L_MORPH_DILATE, selnamev);
+        LEPT_FREE(selnamev);
+    } else {
+        pixt1 = pixAddBorder(pixs, 32, 0);
+        pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnameh);
+        pixFMorphopGen_1(pixt1, pixt3, L_MORPH_DILATE, selnamev);
+        pixt2 = pixRemoveBorder(pixt1, 32);
+        pixDestroy(&pixt1);
+        pixDestroy(&pixt3);
+        LEPT_FREE(selnameh);
+        LEPT_FREE(selnamev);
+    }
+
+    if (!pixd)
+        return pixt2;
+
+    pixTransferAllData(pixd, &pixt2, 0, 0);
+    return pixd;
+}
+
+
+/*!
+ * \brief   pixErodeBrickDwa()
+ *
+ * \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) These implement 2D brick Sels, using linear Sels generated
+ *          with selaAddBasic().
+ *      (2) A brick Sel has hits for all elements.
+ *      (3) The origin of the Sel is at (x, y) = (hsize/2, vsize/2)
+ *      (4) Do separably if both hsize and vsize are > 1.
+ *      (5) It is necessary that both horizontal and vertical Sels
+ *          of the input size are defined in the basic sela.
+ *      (6) Note that we must always set or clear the border pixels
+ *          before each operation, depending on the the b.c.
+ *          (symmetric or asymmetric).
+ *      (7) 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)
+ *      (8) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixErodeBrickDwa(NULL, pixs, ...);
+ *          (b) pixErodeBrickDwa(pixs, pixs, ...);
+ *          (c) pixErodeBrickDwa(pixd, pixs, ...);
+ *      (9) The size of the result is determined by pixs.
+ *      (10) If either linear Sel is not found, this calls
+ *           the appropriate decomposible function.
+ * </pre>
+ */
+PIX *
+pixErodeBrickDwa(PIX     *pixd,
+                 PIX     *pixs,
+                 l_int32  hsize,
+                 l_int32  vsize)
+{
+l_int32  found;
+char    *selnameh, *selnamev;
+SELA    *sela;
+PIX     *pixt1, *pixt2, *pixt3;
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", __func__, pixd);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(pixd, pixs);
+
+    sela = selaAddBasic(NULL);
+    found = TRUE;
+    selnameh = selnamev = NULL;
+    if (hsize > 1) {
+        selnameh = selaGetBrickName(sela, hsize, 1);
+        if (!selnameh) found = FALSE;
+    }
+    if (vsize > 1) {
+        selnamev = selaGetBrickName(sela, 1, vsize);
+        if (!selnamev) found = FALSE;
+    }
+    selaDestroy(&sela);
+    if (!found) {
+        L_INFO("Calling the decomposable dwa function\n", __func__);
+        if (selnameh) LEPT_FREE(selnameh);
+        if (selnamev) LEPT_FREE(selnamev);
+        return pixErodeCompBrickDwa(pixd, pixs, hsize, vsize);
+    }
+
+    if (vsize == 1) {
+        pixt2 = pixMorphDwa_1(NULL, pixs, L_MORPH_ERODE, selnameh);
+        LEPT_FREE(selnameh);
+    } else if (hsize == 1) {
+        pixt2 = pixMorphDwa_1(NULL, pixs, L_MORPH_ERODE, selnamev);
+        LEPT_FREE(selnamev);
+    } else {
+        pixt1 = pixAddBorder(pixs, 32, 0);
+        pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnameh);
+        pixFMorphopGen_1(pixt1, pixt3, L_MORPH_ERODE, selnamev);
+        pixt2 = pixRemoveBorder(pixt1, 32);
+        pixDestroy(&pixt1);
+        pixDestroy(&pixt3);
+        LEPT_FREE(selnameh);
+        LEPT_FREE(selnamev);
+    }
+
+    if (!pixd)
+        return pixt2;
+
+    pixTransferAllData(pixd, &pixt2, 0, 0);
+    return pixd;
+}
+
+
+/*!
+ * \brief   pixOpenBrickDwa()
+ *
+ * \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) These implement 2D brick Sels, using linear Sels generated
+ *          with selaAddBasic().
+ *      (2) A brick Sel has hits for all elements.
+ *      (3) The origin of the Sel is at (x, y) = (hsize/2, vsize/2)
+ *      (4) Do separably if both hsize and vsize are > 1.
+ *      (5) It is necessary that both horizontal and vertical Sels
+ *          of the input size are defined in the basic sela.
+ *      (6) Note that we must always set or clear the border pixels
+ *          before each operation, depending on the the b.c.
+ *          (symmetric or asymmetric).
+ *      (7) 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)
+ *      (8) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixOpenBrickDwa(NULL, pixs, ...);
+ *          (b) pixOpenBrickDwa(pixs, pixs, ...);
+ *          (c) pixOpenBrickDwa(pixd, pixs, ...);
+ *      (9) The size of the result is determined by pixs.
+ *      (10) If either linear Sel is not found, this calls
+ *           the appropriate decomposible function.
+ * </pre>
+ */
+PIX *
+pixOpenBrickDwa(PIX     *pixd,
+                PIX     *pixs,
+                l_int32  hsize,
+                l_int32  vsize)
+{
+l_int32  found;
+char    *selnameh, *selnamev;
+SELA    *sela;
+PIX     *pixt1, *pixt2, *pixt3;
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", __func__, pixd);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(pixd, pixs);
+
+    sela = selaAddBasic(NULL);
+    found = TRUE;
+    selnameh = selnamev = NULL;
+    if (hsize > 1) {
+        selnameh = selaGetBrickName(sela, hsize, 1);
+        if (!selnameh) found = FALSE;
+    }
+    if (vsize > 1) {
+        selnamev = selaGetBrickName(sela, 1, vsize);
+        if (!selnamev) found = FALSE;
+    }
+    selaDestroy(&sela);
+    if (!found) {
+        L_INFO("Calling the decomposable dwa function\n", __func__);
+        if (selnameh) LEPT_FREE(selnameh);
+        if (selnamev) LEPT_FREE(selnamev);
+        return pixOpenCompBrickDwa(pixd, pixs, hsize, vsize);
+    }
+
+    pixt1 = pixAddBorder(pixs, 32, 0);
+    if (vsize == 1) {   /* horizontal only */
+        pixt2 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_OPEN, selnameh);
+        LEPT_FREE(selnameh);
+    } else if (hsize == 1) {   /* vertical only */
+        pixt2 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_OPEN, selnamev);
+        LEPT_FREE(selnamev);
+    } else {  /* do separable */
+        pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnameh);
+        pixt2 = pixFMorphopGen_1(NULL, pixt3, L_MORPH_ERODE, selnamev);
+        pixFMorphopGen_1(pixt3, pixt2, L_MORPH_DILATE, selnameh);
+        pixFMorphopGen_1(pixt2, pixt3, L_MORPH_DILATE, selnamev);
+        LEPT_FREE(selnameh);
+        LEPT_FREE(selnamev);
+        pixDestroy(&pixt3);
+    }
+    pixt3 = pixRemoveBorder(pixt2, 32);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+
+    if (!pixd)
+        return pixt3;
+
+    pixTransferAllData(pixd, &pixt3, 0, 0);
+    return pixd;
+}
+
+
+/*!
+ * \brief   pixCloseBrickDwa()
+ *
+ * \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) This is a 'safe' closing; we add an extra border of 32 OFF
+ *          pixels for the standard asymmetric b.c.
+ *      (2) These implement 2D brick Sels, using linear Sels generated
+ *          with selaAddBasic().
+ *      (3) A brick Sel has hits for all elements.
+ *      (4) The origin of the Sel is at (x, y) = (hsize/2, vsize/2)
+ *      (5) Do separably if both hsize and vsize are > 1.
+ *      (6) It is necessary that both horizontal and vertical Sels
+ *          of the input size are defined in the basic sela.
+ *      (7) Note that we must always set or clear the border pixels
+ *          before each operation, depending on the the b.c.
+ *          (symmetric or asymmetric).
+ *      (8) 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)
+ *      (9) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixCloseBrickDwa(NULL, pixs, ...);
+ *          (b) pixCloseBrickDwa(pixs, pixs, ...);
+ *          (c) pixCloseBrickDwa(pixd, pixs, ...);
+ *      (10) The size of the result is determined by pixs.
+ *      (11) If either linear Sel is not found, this calls
+ *           the appropriate decomposible function.
+ * </pre>
+ */
+PIX *
+pixCloseBrickDwa(PIX     *pixd,
+                 PIX     *pixs,
+                 l_int32  hsize,
+                 l_int32  vsize)
+{
+l_int32  bordercolor, bordersize, found;
+char    *selnameh, *selnamev;
+SELA    *sela;
+PIX     *pixt1, *pixt2, *pixt3;
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", __func__, pixd);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(pixd, pixs);
+
+    sela = selaAddBasic(NULL);
+    found = TRUE;
+    selnameh = selnamev = NULL;
+    if (hsize > 1) {
+        selnameh = selaGetBrickName(sela, hsize, 1);
+        if (!selnameh) found = FALSE;
+    }
+    if (vsize > 1) {
+        selnamev = selaGetBrickName(sela, 1, vsize);
+        if (!selnamev) found = FALSE;
+    }
+    selaDestroy(&sela);
+    if (!found) {
+        L_INFO("Calling the decomposable dwa function\n", __func__);
+        if (selnameh) LEPT_FREE(selnameh);
+        if (selnamev) LEPT_FREE(selnamev);
+        return pixCloseCompBrickDwa(pixd, pixs, hsize, vsize);
+    }
+
+        /* For "safe closing" with ASYMMETRIC_MORPH_BC, we always need
+         * an extra 32 OFF pixels around the image (in addition to
+         * the 32 added pixels for all dwa operations), whereas with
+         * SYMMETRIC_MORPH_BC this is not necessary. */
+    bordercolor = getMorphBorderPixelColor(L_MORPH_ERODE, 1);
+    if (bordercolor == 0)   /* asymmetric b.c. */
+        bordersize = 64;
+    else   /* symmetric b.c. */
+        bordersize = 32;
+    pixt1 = pixAddBorder(pixs, bordersize, 0);
+
+    if (vsize == 1) {   /* horizontal only */
+        pixt2 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_CLOSE, selnameh);
+        LEPT_FREE(selnameh);
+    } else if (hsize == 1) {   /* vertical only */
+        pixt2 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_CLOSE, selnamev);
+        LEPT_FREE(selnamev);
+    } else {  /* do separable */
+        pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnameh);
+        pixt2 = pixFMorphopGen_1(NULL, pixt3, L_MORPH_DILATE, selnamev);
+        pixFMorphopGen_1(pixt3, pixt2, L_MORPH_ERODE, selnameh);
+        pixFMorphopGen_1(pixt2, pixt3, L_MORPH_ERODE, selnamev);
+        LEPT_FREE(selnameh);
+        LEPT_FREE(selnamev);
+        pixDestroy(&pixt3);
+    }
+    pixt3 = pixRemoveBorder(pixt2, bordersize);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+
+    if (!pixd)
+        return pixt3;
+
+    pixTransferAllData(pixd, &pixt3, 0, 0);
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------*
+ *    Binary composite morphological (dwa) ops with brick Sels     *
+ *-----------------------------------------------------------------*/
+/*!
+ * \brief   pixDilateCompBrickDwa()
+ *
+ * \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) These implement a separable composite dilation with 2D brick Sels.
+ *      (2) For efficiency, it may decompose each linear morphological
+ *          operation into two (brick + comb).
+ *      (3) A brick Sel has hits for all elements.
+ *      (4) The origin of the Sel is at (x, y) = (hsize/2, vsize/2)
+ *      (5) Do separably if both hsize and vsize are > 1.
+ *      (6) It is necessary that both horizontal and vertical Sels
+ *          of the input size are defined in the basic sela.
+ *      (7) 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)
+ *      (8) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixDilateCompBrickDwa(NULL, pixs, ...);
+ *          (b) pixDilateCompBrickDwa(pixs, pixs, ...);
+ *          (c) pixDilateCompBrickDwa(pixd, pixs, ...);
+ *      (9) The size of pixd is determined by pixs.
+ *      (10) 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 *
+pixDilateCompBrickDwa(PIX     *pixd,
+                      PIX     *pixs,
+                      l_int32  hsize,
+                      l_int32  vsize)
+{
+char    *selnameh1, *selnameh2, *selnamev1, *selnamev2;
+l_int32  hsize1, hsize2, vsize1, vsize2;
+PIX     *pixt1, *pixt2, *pixt3;
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", __func__, pixd);
+    if (hsize > 63 || vsize > 63)
+        return pixDilateCompBrickExtendDwa(pixd, pixs, hsize, vsize);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(pixd, pixs);
+
+    hsize1 = hsize2 = vsize1 = vsize2 = 1;
+    selnameh1 = selnameh2 = selnamev1 = selnamev2 = NULL;
+    if (hsize > 1)
+        getCompositeParameters(hsize, &hsize1, &hsize2, &selnameh1,
+                               &selnameh2, NULL, NULL);
+    if (vsize > 1)
+        getCompositeParameters(vsize, &vsize1, &vsize2, NULL, NULL,
+                               &selnamev1, &selnamev2);
+
+#if DEBUG_SEL_LOOKUP
+    lept_stderr("nameh1=%s, nameh2=%s, namev1=%s, namev2=%s\n",
+                selnameh1, selnameh2, selnamev1, selnamev2);
+    lept_stderr("hsize1=%d, hsize2=%d, vsize1=%d, vsize2=%d\n",
+                hsize1, hsize2, vsize1, vsize2);
+#endif  /* DEBUG_SEL_LOOKUP */
+
+    pixt1 = pixAddBorder(pixs, 64, 0);
+    if (vsize == 1) {
+        if (hsize2 == 1) {
+            pixt2 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnameh1);
+        } else {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnameh1);
+            pixt2 = pixFMorphopGen_2(NULL, pixt3, L_MORPH_DILATE, selnameh2);
+            pixDestroy(&pixt3);
+        }
+    } else if (hsize == 1) {
+        if (vsize2 == 1) {
+            pixt2 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnamev1);
+        } else {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnamev1);
+            pixt2 = pixFMorphopGen_2(NULL, pixt3, L_MORPH_DILATE, selnamev2);
+            pixDestroy(&pixt3);
+        }
+    } else {  /* vsize and hsize both > 1 */
+        if (hsize2 == 1) {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnameh1);
+        } else {
+            pixt2 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnameh1);
+            pixt3 = pixFMorphopGen_2(NULL, pixt2, L_MORPH_DILATE, selnameh2);
+            pixDestroy(&pixt2);
+        }
+        if (vsize2 == 1) {
+            pixt2 = pixFMorphopGen_1(NULL, pixt3, L_MORPH_DILATE, selnamev1);
+        } else {
+            pixt2 = pixFMorphopGen_1(NULL, pixt3, L_MORPH_DILATE, selnamev1);
+            pixFMorphopGen_2(pixt2, pixt2, L_MORPH_DILATE, selnamev2);
+        }
+        pixDestroy(&pixt3);
+    }
+    pixDestroy(&pixt1);
+    pixt1 = pixRemoveBorder(pixt2, 64);
+    pixDestroy(&pixt2);
+    if (selnameh1) LEPT_FREE(selnameh1);
+    if (selnameh2) LEPT_FREE(selnameh2);
+    if (selnamev1) LEPT_FREE(selnamev1);
+    if (selnamev2) LEPT_FREE(selnamev2);
+
+    if (!pixd)
+        return pixt1;
+
+    pixTransferAllData(pixd, &pixt1, 0, 0);
+    return pixd;
+}
+
+
+/*!
+ * \brief   pixErodeCompBrickDwa()
+ *
+ * \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) These implement a separable composite erosion with 2D brick Sels.
+ *      (2) For efficiency, it may decompose each linear morphological
+ *          operation into two (brick + comb).
+ *      (3) A brick Sel has hits for all elements.
+ *      (4) The origin of the Sel is at (x, y) = (hsize/2, vsize/2)
+ *      (5) Do separably if both hsize and vsize are > 1.
+ *      (6) It is necessary that both horizontal and vertical Sels
+ *          of the input size are defined in the basic sela.
+ *      (7) 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)
+ *      (8) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixErodeCompBrickDwa(NULL, pixs, ...);
+ *          (b) pixErodeCompBrickDwa(pixs, pixs, ...);
+ *          (c) pixErodeCompBrickDwa(pixd, pixs, ...);
+ *      (9) The size of pixd is determined by pixs.
+ *      (10) 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 *
+pixErodeCompBrickDwa(PIX     *pixd,
+                     PIX     *pixs,
+                     l_int32  hsize,
+                     l_int32  vsize)
+{
+char    *selnameh1, *selnameh2, *selnamev1, *selnamev2;
+l_int32  hsize1, hsize2, vsize1, vsize2, bordercolor;
+PIX     *pixt1, *pixt2, *pixt3;
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", __func__, pixd);
+    if (hsize > 63 || vsize > 63)
+        return pixErodeCompBrickExtendDwa(pixd, pixs, hsize, vsize);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(pixd, pixs);
+
+    hsize1 = hsize2 = vsize1 = vsize2 = 1;
+    selnameh1 = selnameh2 = selnamev1 = selnamev2 = NULL;
+    if (hsize > 1)
+        getCompositeParameters(hsize, &hsize1, &hsize2, &selnameh1,
+                               &selnameh2, NULL, NULL);
+    if (vsize > 1)
+        getCompositeParameters(vsize, &vsize1, &vsize2, NULL, NULL,
+                               &selnamev1, &selnamev2);
+
+        /* For symmetric b.c., bordercolor == 1 for erosion */
+    bordercolor = getMorphBorderPixelColor(L_MORPH_ERODE, 1);
+    pixt1 = pixAddBorder(pixs, 64, bordercolor);
+
+    if (vsize == 1) {
+        if (hsize2 == 1) {
+            pixt2 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnameh1);
+        } else {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnameh1);
+            pixt2 = pixFMorphopGen_2(NULL, pixt3, L_MORPH_ERODE, selnameh2);
+            pixDestroy(&pixt3);
+        }
+    } else if (hsize == 1) {
+        if (vsize2 == 1) {
+            pixt2 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnamev1);
+        } else {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnamev1);
+            pixt2 = pixFMorphopGen_2(NULL, pixt3, L_MORPH_ERODE, selnamev2);
+            pixDestroy(&pixt3);
+        }
+    } else {  /* vsize and hsize both > 1 */
+        if (hsize2 == 1) {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnameh1);
+        } else {
+            pixt2 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnameh1);
+            pixt3 = pixFMorphopGen_2(NULL, pixt2, L_MORPH_ERODE, selnameh2);
+            pixDestroy(&pixt2);
+        }
+        if (vsize2 == 1) {
+            pixt2 = pixFMorphopGen_1(NULL, pixt3, L_MORPH_ERODE, selnamev1);
+        } else {
+            pixt2 = pixFMorphopGen_1(NULL, pixt3, L_MORPH_ERODE, selnamev1);
+            pixFMorphopGen_2(pixt2, pixt2, L_MORPH_ERODE, selnamev2);
+        }
+        pixDestroy(&pixt3);
+    }
+    pixDestroy(&pixt1);
+    pixt1 = pixRemoveBorder(pixt2, 64);
+    pixDestroy(&pixt2);
+    if (selnameh1) LEPT_FREE(selnameh1);
+    if (selnameh2) LEPT_FREE(selnameh2);
+    if (selnamev1) LEPT_FREE(selnamev1);
+    if (selnamev2) LEPT_FREE(selnamev2);
+
+    if (!pixd)
+        return pixt1;
+
+    pixTransferAllData(pixd, &pixt1, 0, 0);
+    return pixd;
+}
+
+
+/*!
+ * \brief   pixOpenCompBrickDwa()
+ *
+ * \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) These implement a separable composite opening with 2D brick Sels.
+ *      (2) For efficiency, it may decompose each linear morphological
+ *          operation into two (brick + comb).
+ *      (3) A brick Sel has hits for all elements.
+ *      (4) The origin of the Sel is at (x, y) = (hsize/2, vsize/2)
+ *      (5) Do separably if both hsize and vsize are > 1.
+ *      (6) It is necessary that both horizontal and vertical Sels
+ *          of the input size are defined in the basic sela.
+ *      (7) 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)
+ *      (8) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixOpenCompBrickDwa(NULL, pixs, ...);
+ *          (b) pixOpenCompBrickDwa(pixs, pixs, ...);
+ *          (c) pixOpenCompBrickDwa(pixd, pixs, ...);
+ *      (9) The size of pixd is determined by pixs.
+ *      (10) 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 *
+pixOpenCompBrickDwa(PIX     *pixd,
+                    PIX     *pixs,
+                    l_int32  hsize,
+                    l_int32  vsize)
+{
+char    *selnameh1, *selnameh2, *selnamev1, *selnamev2;
+l_int32  hsize1, hsize2, vsize1, vsize2, bordercolor;
+PIX     *pixt1, *pixt2, *pixt3;
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", __func__, pixd);
+    if (hsize > 63 || vsize > 63)
+        return pixOpenCompBrickExtendDwa(pixd, pixs, hsize, vsize);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(pixd, pixs);
+
+    hsize1 = hsize2 = vsize1 = vsize2 = 1;
+    selnameh1 = selnameh2 = selnamev1 = selnamev2 = NULL;
+    if (hsize > 1)
+        getCompositeParameters(hsize, &hsize1, &hsize2, &selnameh1,
+                               &selnameh2, NULL, NULL);
+    if (vsize > 1)
+        getCompositeParameters(vsize, &vsize1, &vsize2, NULL, NULL,
+                               &selnamev1, &selnamev2);
+
+        /* For symmetric b.c., initialize erosion with bordercolor == 1 */
+    bordercolor = getMorphBorderPixelColor(L_MORPH_ERODE, 1);
+    pixt1 = pixAddBorder(pixs, 64, bordercolor);
+
+    if (vsize == 1) {
+        if (hsize2 == 1) {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnameh1);
+            if (bordercolor == 1)
+                pixSetOrClearBorder(pixt3, 64, 64, 64, 64, PIX_CLR);
+            pixt2 = pixFMorphopGen_1(NULL, pixt3, L_MORPH_DILATE, selnameh1);
+        } else {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnameh1);
+            pixt2 = pixFMorphopGen_2(NULL, pixt3, L_MORPH_ERODE, selnameh2);
+            if (bordercolor == 1)
+                pixSetOrClearBorder(pixt2, 64, 64, 64, 64, PIX_CLR);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_DILATE, selnameh1);
+            pixFMorphopGen_2(pixt2, pixt3, L_MORPH_DILATE, selnameh2);
+        }
+    } else if (hsize == 1) {
+        if (vsize2 == 1)  {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnamev1);
+            if (bordercolor == 1)
+                pixSetOrClearBorder(pixt3, 64, 64, 64, 64, PIX_CLR);
+            pixt2 = pixFMorphopGen_1(NULL, pixt3, L_MORPH_DILATE, selnamev1);
+        } else {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnamev1);
+            pixt2 = pixFMorphopGen_2(NULL, pixt3, L_MORPH_ERODE, selnamev2);
+            if (bordercolor == 1)
+                pixSetOrClearBorder(pixt2, 64, 64, 64, 64, PIX_CLR);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_DILATE, selnamev1);
+            pixFMorphopGen_2(pixt2, pixt3, L_MORPH_DILATE, selnamev2);
+        }
+    } else {  /* vsize and hsize both > 1 */
+        if (hsize2 == 1 && vsize2 == 1) {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnameh1);
+            pixt2 = pixFMorphopGen_1(NULL, pixt3, L_MORPH_ERODE, selnamev1);
+            if (bordercolor == 1)
+                pixSetOrClearBorder(pixt2, 64, 64, 64, 64, PIX_CLR);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_DILATE, selnameh1);
+            pixFMorphopGen_1(pixt2, pixt3, L_MORPH_DILATE, selnamev1);
+        } else if (vsize2 == 1) {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnameh1);
+            pixt2 = pixFMorphopGen_2(NULL, pixt3, L_MORPH_ERODE, selnameh2);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_ERODE, selnamev1);
+            if (bordercolor == 1)
+                pixSetOrClearBorder(pixt3, 64, 64, 64, 64, PIX_CLR);
+            pixFMorphopGen_1(pixt2, pixt3, L_MORPH_DILATE, selnameh1);
+            pixFMorphopGen_2(pixt3, pixt2, L_MORPH_DILATE, selnameh2);
+            pixFMorphopGen_1(pixt2, pixt3, L_MORPH_DILATE, selnamev1);
+        } else if (hsize2 == 1) {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnameh1);
+            pixt2 = pixFMorphopGen_1(NULL, pixt3, L_MORPH_ERODE, selnamev1);
+            pixFMorphopGen_2(pixt3, pixt2, L_MORPH_ERODE, selnamev2);
+            if (bordercolor == 1)
+                pixSetOrClearBorder(pixt3, 64, 64, 64, 64, PIX_CLR);
+            pixFMorphopGen_1(pixt2, pixt3, L_MORPH_DILATE, selnameh1);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_DILATE, selnamev1);
+            pixFMorphopGen_2(pixt2, pixt3, L_MORPH_DILATE, selnamev2);
+        } else {   /* both directions are combed */
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnameh1);
+            pixt2 = pixFMorphopGen_2(NULL, pixt3, L_MORPH_ERODE, selnameh2);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_ERODE, selnamev1);
+            pixFMorphopGen_2(pixt2, pixt3, L_MORPH_ERODE, selnamev2);
+            if (bordercolor == 1)
+                pixSetOrClearBorder(pixt2, 64, 64, 64, 64, PIX_CLR);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_DILATE, selnameh1);
+            pixFMorphopGen_2(pixt2, pixt3, L_MORPH_DILATE, selnameh2);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_DILATE, selnamev1);
+            pixFMorphopGen_2(pixt2, pixt3, L_MORPH_DILATE, selnamev2);
+        }
+    }
+    pixDestroy(&pixt3);
+
+    pixDestroy(&pixt1);
+    pixt1 = pixRemoveBorder(pixt2, 64);
+    pixDestroy(&pixt2);
+    if (selnameh1) LEPT_FREE(selnameh1);
+    if (selnameh2) LEPT_FREE(selnameh2);
+    if (selnamev1) LEPT_FREE(selnamev1);
+    if (selnamev2) LEPT_FREE(selnamev2);
+
+    if (!pixd)
+        return pixt1;
+
+    pixTransferAllData(pixd, &pixt1, 0, 0);
+    return pixd;
+}
+
+
+/*!
+ * \brief   pixCloseCompBrickDwa()
+ *
+ * \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) This implements a separable composite safe closing with 2D
+ *          brick Sels.
+ *      (2) For efficiency, it may decompose each linear morphological
+ *          operation into two (brick + comb).
+ *      (3) A brick Sel has hits for all elements.
+ *      (4) The origin of the Sel is at (x, y) = (hsize/2, vsize/2)
+ *      (5) Do separably if both hsize and vsize are > 1.
+ *      (6) It is necessary that both horizontal and vertical Sels
+ *          of the input size are defined in the basic sela.
+ *      (7) 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)
+ *      (8) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixCloseCompBrickDwa(NULL, pixs, ...);
+ *          (b) pixCloseCompBrickDwa(pixs, pixs, ...);
+ *          (c) pixCloseCompBrickDwa(pixd, pixs, ...);
+ *      (9) The size of pixd is determined by pixs.
+ *      (10) 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 *
+pixCloseCompBrickDwa(PIX     *pixd,
+                     PIX     *pixs,
+                     l_int32  hsize,
+                     l_int32  vsize)
+{
+char    *selnameh1, *selnameh2, *selnamev1, *selnamev2;
+l_int32  hsize1, hsize2, vsize1, vsize2, setborder;
+PIX     *pixt1, *pixt2, *pixt3;
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", __func__, pixd);
+    if (hsize > 63 || vsize > 63)
+        return pixCloseCompBrickExtendDwa(pixd, pixs, hsize, vsize);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(pixd, pixs);
+
+    hsize1 = hsize2 = vsize1 = vsize2 = 1;
+    selnameh1 = selnameh2 = selnamev1 = selnamev2 = NULL;
+    if (hsize > 1)
+        getCompositeParameters(hsize, &hsize1, &hsize2, &selnameh1,
+                               &selnameh2, NULL, NULL);
+    if (vsize > 1)
+        getCompositeParameters(vsize, &vsize1, &vsize2, NULL, NULL,
+                               &selnamev1, &selnamev2);
+
+    pixt3 = NULL;
+        /* For symmetric b.c., PIX_SET border for erosions */
+    setborder = getMorphBorderPixelColor(L_MORPH_ERODE, 1);
+    pixt1 = pixAddBorder(pixs, 64, 0);
+
+    if (vsize == 1) {
+        if (hsize2 == 1) {
+            pixt2 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_CLOSE, selnameh1);
+        } else {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnameh1);
+            pixt2 = pixFMorphopGen_2(NULL, pixt3, L_MORPH_DILATE, selnameh2);
+            if (setborder == 1)
+                pixSetOrClearBorder(pixt2, 64, 64, 64, 64, PIX_SET);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_ERODE, selnameh1);
+            pixFMorphopGen_2(pixt2, pixt3, L_MORPH_ERODE, selnameh2);
+        }
+    } else if (hsize == 1) {
+        if (vsize2 == 1) {
+            pixt2 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_CLOSE, selnamev1);
+        } else {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnamev1);
+            pixt2 = pixFMorphopGen_2(NULL, pixt3, L_MORPH_DILATE, selnamev2);
+            if (setborder == 1)
+                pixSetOrClearBorder(pixt2, 64, 64, 64, 64, PIX_SET);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_ERODE, selnamev1);
+            pixFMorphopGen_2(pixt2, pixt3, L_MORPH_ERODE, selnamev2);
+        }
+    } else {  /* vsize and hsize both > 1 */
+        if (hsize2 == 1 && vsize2 == 1) {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnameh1);
+            pixt2 = pixFMorphopGen_1(NULL, pixt3, L_MORPH_DILATE, selnamev1);
+            if (setborder == 1)
+                pixSetOrClearBorder(pixt2, 64, 64, 64, 64, PIX_SET);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_ERODE, selnameh1);
+            pixFMorphopGen_1(pixt2, pixt3, L_MORPH_ERODE, selnamev1);
+        } else if (vsize2 == 1) {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnameh1);
+            pixt2 = pixFMorphopGen_2(NULL, pixt3, L_MORPH_DILATE, selnameh2);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_DILATE, selnamev1);
+            if (setborder == 1)
+                pixSetOrClearBorder(pixt3, 64, 64, 64, 64, PIX_SET);
+            pixFMorphopGen_1(pixt2, pixt3, L_MORPH_ERODE, selnameh1);
+            pixFMorphopGen_2(pixt3, pixt2, L_MORPH_ERODE, selnameh2);
+            pixFMorphopGen_1(pixt2, pixt3, L_MORPH_ERODE, selnamev1);
+        } else if (hsize2 == 1) {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnameh1);
+            pixt2 = pixFMorphopGen_1(NULL, pixt3, L_MORPH_DILATE, selnamev1);
+            pixFMorphopGen_2(pixt3, pixt2, L_MORPH_DILATE, selnamev2);
+            if (setborder == 1)
+                pixSetOrClearBorder(pixt3, 64, 64, 64, 64, PIX_SET);
+            pixFMorphopGen_1(pixt2, pixt3, L_MORPH_ERODE, selnameh1);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_ERODE, selnamev1);
+            pixFMorphopGen_2(pixt2, pixt3, L_MORPH_ERODE, selnamev2);
+        } else {   /* both directions are combed */
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnameh1);
+            pixt2 = pixFMorphopGen_2(NULL, pixt3, L_MORPH_DILATE, selnameh2);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_DILATE, selnamev1);
+            pixFMorphopGen_2(pixt2, pixt3, L_MORPH_DILATE, selnamev2);
+            if (setborder == 1)
+                pixSetOrClearBorder(pixt2, 64, 64, 64, 64, PIX_SET);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_ERODE, selnameh1);
+            pixFMorphopGen_2(pixt2, pixt3, L_MORPH_ERODE, selnameh2);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_ERODE, selnamev1);
+            pixFMorphopGen_2(pixt2, pixt3, L_MORPH_ERODE, selnamev2);
+        }
+    }
+    pixDestroy(&pixt3);
+
+    pixDestroy(&pixt1);
+    pixt1 = pixRemoveBorder(pixt2, 64);
+    pixDestroy(&pixt2);
+    if (selnameh1) LEPT_FREE(selnameh1);
+    if (selnameh2) LEPT_FREE(selnameh2);
+    if (selnamev1) LEPT_FREE(selnamev1);
+    if (selnamev2) LEPT_FREE(selnamev2);
+
+    if (!pixd)
+        return pixt1;
+
+    pixTransferAllData(pixd, &pixt1, 0, 0);
+    return pixd;
+}
+
+
+/*--------------------------------------------------------------------------*
+ *    Binary expanded composite morphological (dwa) ops with brick Sels     *
+ *--------------------------------------------------------------------------*/
+/*!
+ * \brief   pixDilateCompBrickExtendDwa()
+ *
+ * \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) Ankur Jain suggested and implemented extending the composite
+ *          DWA operations beyond the 63 pixel limit.  This is a
+ *          simplified and approximate implementation of the extension.
+ *          This allows arbitrary Dwa morph operations using brick Sels,
+ *          by decomposing the horizontal and vertical dilations into
+ *          a sequence of 63-element dilations plus a dilation of size
+ *          between 3 and 62.
+ *      (2) The 63-element dilations are exact, whereas the extra dilation
+ *          is approximate, because the underlying decomposition is
+ *          in pixDilateCompBrickDwa().  See there for further details.
+ *      (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) There is no need to call this directly:  pixDilateCompBrickDwa()
+ *          calls this function if either brick dimension exceeds 63.
+ * </pre>
+ */
+PIX *
+pixDilateCompBrickExtendDwa(PIX     *pixd,
+                            PIX     *pixs,
+                            l_int32  hsize,
+                            l_int32  vsize)
+{
+l_int32  i, nops, nh, extrah, nv, extrav;
+PIX     *pixt1, *pixt2, *pixt3;
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", __func__, pixd);
+
+    if (hsize < 64 && vsize < 64)
+        return pixDilateCompBrickDwa(pixd, pixs, hsize, vsize);
+
+    if (hsize > 63)
+        getExtendedCompositeParameters(hsize, &nh, &extrah, NULL);
+    if (vsize > 63)
+        getExtendedCompositeParameters(vsize, &nv, &extrav, NULL);
+
+        /* Horizontal dilation first: pixs --> pixt2.  Do not alter pixs. */
+    pixt1 = pixCreateTemplate(pixs);  /* temp image */
+    if (hsize == 1) {
+        pixt2 = pixClone(pixs);
+    } else if (hsize < 64) {
+        pixt2 = pixDilateCompBrickDwa(NULL, pixs, hsize, 1);
+    } else if (hsize == 64) {  /* approximate */
+        pixt2 = pixDilateCompBrickDwa(NULL, pixs, 63, 1);
+    } else {
+        nops = (extrah < 3) ? nh : nh + 1;
+        if (nops & 1) {  /* odd */
+            if (extrah > 2)
+                pixt2 = pixDilateCompBrickDwa(NULL, pixs, extrah, 1);
+            else
+                pixt2 = pixDilateCompBrickDwa(NULL, pixs, 63, 1);
+            for (i = 0; i < nops / 2; i++) {
+                pixDilateCompBrickDwa(pixt1, pixt2, 63, 1);
+                pixDilateCompBrickDwa(pixt2, pixt1, 63, 1);
+            }
+        } else {  /* nops even */
+            if (extrah > 2) {
+                pixDilateCompBrickDwa(pixt1, pixs, extrah, 1);
+                pixt2 = pixDilateCompBrickDwa(NULL, pixt1, 63, 1);
+            } else {  /* they're all 63s */
+                pixDilateCompBrickDwa(pixt1, pixs, 63, 1);
+                pixt2 = pixDilateCompBrickDwa(NULL, pixt1, 63, 1);
+            }
+            for (i = 0; i < nops / 2 - 1; i++) {
+                pixDilateCompBrickDwa(pixt1, pixt2, 63, 1);
+                pixDilateCompBrickDwa(pixt2, pixt1, 63, 1);
+            }
+        }
+    }
+
+        /* Vertical dilation: pixt2 --> pixt3.  */
+    if (vsize == 1) {
+        pixt3 = pixClone(pixt2);
+    } else if (vsize < 64) {
+        pixt3 = pixDilateCompBrickDwa(NULL, pixt2, 1, vsize);
+    } else if (vsize == 64) {  /* approximate */
+        pixt3 = pixDilateCompBrickDwa(NULL, pixt2, 1, 63);
+    } else {
+        nops = (extrav < 3) ? nv : nv + 1;
+        if (nops & 1) {  /* odd */
+            if (extrav > 2)
+                pixt3 = pixDilateCompBrickDwa(NULL, pixt2, 1, extrav);
+            else
+                pixt3 = pixDilateCompBrickDwa(NULL, pixt2, 1, 63);
+            for (i = 0; i < nops / 2; i++) {
+                pixDilateCompBrickDwa(pixt1, pixt3, 1, 63);
+                pixDilateCompBrickDwa(pixt3, pixt1, 1, 63);
+            }
+        } else {  /* nops even */
+            if (extrav > 2) {
+                pixDilateCompBrickDwa(pixt1, pixt2, 1, extrav);
+                pixt3 = pixDilateCompBrickDwa(NULL, pixt1, 1, 63);
+            } else {  /* they're all 63s */
+                pixDilateCompBrickDwa(pixt1, pixt2, 1, 63);
+                pixt3 = pixDilateCompBrickDwa(NULL, pixt1, 1, 63);
+            }
+            for (i = 0; i < nops / 2 - 1; i++) {
+                pixDilateCompBrickDwa(pixt1, pixt3, 1, 63);
+                pixDilateCompBrickDwa(pixt3, pixt1, 1, 63);
+            }
+        }
+    }
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+
+    if (!pixd)
+        return pixt3;
+
+    pixTransferAllData(pixd, &pixt3, 0, 0);
+    return pixd;
+}
+
+
+/*!
+ * \brief   pixErodeCompBrickExtendDwa()
+ *
+ * \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) See pixDilateCompBrickExtendDwa() for usage.
+ *      (2) There is no need to call this directly:  pixErodeCompBrickDwa()
+ *          calls this function if either brick dimension exceeds 63.
+ * </pre>
+ */
+PIX *
+pixErodeCompBrickExtendDwa(PIX     *pixd,
+                           PIX     *pixs,
+                           l_int32  hsize,
+                           l_int32  vsize)
+{
+l_int32  i, nops, nh, extrah, nv, extrav;
+PIX     *pixt1, *pixt2, *pixt3;
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", __func__, pixd);
+
+    if (hsize < 64 && vsize < 64)
+        return pixErodeCompBrickDwa(pixd, pixs, hsize, vsize);
+
+    if (hsize > 63)
+        getExtendedCompositeParameters(hsize, &nh, &extrah, NULL);
+    if (vsize > 63)
+        getExtendedCompositeParameters(vsize, &nv, &extrav, NULL);
+
+        /* Horizontal erosion first: pixs --> pixt2.  Do not alter pixs. */
+    pixt1 = pixCreateTemplate(pixs);  /* temp image */
+    if (hsize == 1) {
+        pixt2 = pixClone(pixs);
+    } else if (hsize < 64) {
+        pixt2 = pixErodeCompBrickDwa(NULL, pixs, hsize, 1);
+    } else if (hsize == 64) {  /* approximate */
+        pixt2 = pixErodeCompBrickDwa(NULL, pixs, 63, 1);
+    } else {
+        nops = (extrah < 3) ? nh : nh + 1;
+        if (nops & 1) {  /* odd */
+            if (extrah > 2)
+                pixt2 = pixErodeCompBrickDwa(NULL, pixs, extrah, 1);
+            else
+                pixt2 = pixErodeCompBrickDwa(NULL, pixs, 63, 1);
+            for (i = 0; i < nops / 2; i++) {
+                pixErodeCompBrickDwa(pixt1, pixt2, 63, 1);
+                pixErodeCompBrickDwa(pixt2, pixt1, 63, 1);
+            }
+        } else {  /* nops even */
+            if (extrah > 2) {
+                pixErodeCompBrickDwa(pixt1, pixs, extrah, 1);
+                pixt2 = pixErodeCompBrickDwa(NULL, pixt1, 63, 1);
+            } else {  /* they're all 63s */
+                pixErodeCompBrickDwa(pixt1, pixs, 63, 1);
+                pixt2 = pixErodeCompBrickDwa(NULL, pixt1, 63, 1);
+            }
+            for (i = 0; i < nops / 2 - 1; i++) {
+                pixErodeCompBrickDwa(pixt1, pixt2, 63, 1);
+                pixErodeCompBrickDwa(pixt2, pixt1, 63, 1);
+            }
+        }
+    }
+
+        /* Vertical erosion: pixt2 --> pixt3.  */
+    if (vsize == 1) {
+        pixt3 = pixClone(pixt2);
+    } else if (vsize < 64) {
+        pixt3 = pixErodeCompBrickDwa(NULL, pixt2, 1, vsize);
+    } else if (vsize == 64) {  /* approximate */
+        pixt3 = pixErodeCompBrickDwa(NULL, pixt2, 1, 63);
+    } else {
+        nops = (extrav < 3) ? nv : nv + 1;
+        if (nops & 1) {  /* odd */
+            if (extrav > 2)
+                pixt3 = pixErodeCompBrickDwa(NULL, pixt2, 1, extrav);
+            else
+                pixt3 = pixErodeCompBrickDwa(NULL, pixt2, 1, 63);
+            for (i = 0; i < nops / 2; i++) {
+                pixErodeCompBrickDwa(pixt1, pixt3, 1, 63);
+                pixErodeCompBrickDwa(pixt3, pixt1, 1, 63);
+            }
+        } else {  /* nops even */
+            if (extrav > 2) {
+                pixErodeCompBrickDwa(pixt1, pixt2, 1, extrav);
+                pixt3 = pixErodeCompBrickDwa(NULL, pixt1, 1, 63);
+            } else {  /* they're all 63s */
+                pixErodeCompBrickDwa(pixt1, pixt2, 1, 63);
+                pixt3 = pixErodeCompBrickDwa(NULL, pixt1, 1, 63);
+            }
+            for (i = 0; i < nops / 2 - 1; i++) {
+                pixErodeCompBrickDwa(pixt1, pixt3, 1, 63);
+                pixErodeCompBrickDwa(pixt3, pixt1, 1, 63);
+            }
+        }
+    }
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+
+    if (!pixd)
+        return pixt3;
+
+    pixTransferAllData(pixd, &pixt3, 0, 0);
+    return pixd;
+}
+
+
+/*!
+ * \brief   pixOpenCompBrickExtendDwa()
+ *
+ * \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) 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
+ *      2) There is no need to call this directly:  pixOpenCompBrickDwa(
+ *          calls this function if either brick dimension exceeds 63.
+ * </pre>
+ */
+PIX *
+pixOpenCompBrickExtendDwa(PIX     *pixd,
+                          PIX     *pixs,
+                          l_int32  hsize,
+                          l_int32  vsize)
+{
+PIX     *pixt;
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", __func__, pixd);
+
+    pixt = pixErodeCompBrickExtendDwa(NULL, pixs, hsize, vsize);
+    pixd = pixDilateCompBrickExtendDwa(pixd, pixt, hsize, vsize);
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ * \brief   pixCloseCompBrickExtendDwa()
+ *
+ * \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) 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
+ *      2) There is no need to call this directly:  pixCloseCompBrickDwa(
+ *          calls this function if either brick dimension exceeds 63.
+ * </pre>
+ */
+PIX *
+pixCloseCompBrickExtendDwa(PIX     *pixd,
+                           PIX     *pixs,
+                           l_int32  hsize,
+                           l_int32  vsize)
+{
+l_int32  bordercolor, borderx, bordery;
+PIX     *pixt1, *pixt2, *pixt3;
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", __func__, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", __func__, pixd);
+
+        /* For "safe closing" with ASYMMETRIC_MORPH_BC, we always need
+         * an extra 32 OFF pixels around the image (in addition to
+         * the 32 added pixels for all dwa operations), whereas with
+         * SYMMETRIC_MORPH_BC this is not necessary. */
+    bordercolor = getMorphBorderPixelColor(L_MORPH_ERODE, 1);
+    if (bordercolor == 0) {  /* asymmetric b.c. */
+        borderx = 32 + (hsize / 64) * 32;
+        bordery = 32 + (vsize / 64) * 32;
+    } else {  /* symmetric b.c. */
+        borderx = bordery = 32;
+    }
+    pixt1 = pixAddBorderGeneral(pixs, borderx, borderx, bordery, bordery, 0);
+
+    pixt2 = pixDilateCompBrickExtendDwa(NULL, pixt1, hsize, vsize);
+    pixErodeCompBrickExtendDwa(pixt1, pixt2, hsize, vsize);
+
+    pixt3 = pixRemoveBorderGeneral(pixt1, borderx, borderx, bordery, bordery);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+
+    if (!pixd)
+        return pixt3;
+
+    pixTransferAllData(pixd, &pixt3, 0, 0);
+    return pixd;
+}
+
+
+/*!
+ * \brief   getExtendedCompositeParameters()
+ *
+ * \param[in]    size          of linear Sel
+ * \param[out]   pn            number of 63 wide convolutions
+ * \param[out]   pextra        size of extra Sel
+ * \param[out]   pactualsize   [optional] actual size used in operation
+ * \return  0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) The DWA implementation allows Sels to be used with hits
+ *          up to 31 pixels from the origin, either horizontally or
+ *          vertically.  Larger Sels can be used if decomposed into
+ *          a set of operations with Sels not exceeding 63 pixels
+ *          in either width or height (and with the origin as close
+ *          to the center of the Sel as possible).
+ *      (2) This returns the decomposition of a linear Sel of length
+ *          %size into a set of %n Sels of length 63 plus an extra
+ *          Sel of length %extra.
+ *      (3) For notation, let w == %size, n == %n, and e == %extra.
+ *          We have 1 < e < 63.
+ *
+ *          Then if w < 64, we have n = 0 and e = w.
+ *          The general formula for w > 63 is:
+ *             w = 63 + (n - 1) * 62 + (e - 1)
+ *
+ *          Where did this come from?  Each successive convolution with
+ *          a Sel of length L adds a total length (L - 1) to w.
+ *          This accounts for using 62 for each additional Sel of size 63,
+ *          and using (e - 1) for the additional Sel of size e.
+ *
+ *          Solving for n and e for w > 63:
+ *             n = 1 + Int((w - 63) / 62)
+ *             e = w - 63 - (n - 1) * 62 + 1
+ *
+ *          The extra part is decomposed into two factors f1 and f2,
+ *          and the actual size of the extra part is
+ *             e' = f1 * f2
+ *          Then the actual width is:
+ *             w' = 63 + (n - 1) * 62 + f1 * f2 - 1
+ * </pre>
+ */
+l_ok
+getExtendedCompositeParameters(l_int32   size,
+                               l_int32  *pn,
+                               l_int32  *pextra,
+                               l_int32  *pactualsize)
+{
+l_int32  n, extra, fact1, fact2;
+
+    if (!pn || !pextra)
+        return ERROR_INT("&n and &extra not both defined", __func__, 1);
+
+    if (size <= 63) {
+        n = 0;
+        extra = L_MIN(1, size);
+    } else {  /* size > 63 */
+        n = 1 + (l_int32)((size - 63) / 62);
+        extra = size - 63 - (n - 1) * 62 + 1;
+    }
+
+    if (pactualsize) {
+        selectComposableSizes(extra, &fact1, &fact2);
+        *pactualsize = 63 + (n - 1) * 62 + fact1 * fact2 - 1;
+    }
+
+    *pn = n;
+    *pextra = extra;
+    return 0;
+}