view mupdf-source/thirdparty/leptonica/src/morphdwa.c @ 40:aa33339d6b8a upstream

ADD: MuPDF v1.26.10: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.5.
author Franz Glasner <fzglas.hg@dom66.de>
date Sat, 11 Oct 2025 11:31:38 +0200
parents b50eed0cc0ef
children
line wrap: on
line source

/*====================================================================*
 -  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;
}