/*====================================================================* - 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 pixafunc1.c *
 *
 *      Filters
 *           PIX      *pixSelectBySize()
 *           PIXA     *pixaSelectBySize()
 *           NUMA     *pixaMakeSizeIndicator()
 *
 *           PIX      *pixSelectByPerimToAreaRatio()
 *           PIXA     *pixaSelectByPerimToAreaRatio()
 *           PIX      *pixSelectByPerimSizeRatio()
 *           PIXA     *pixaSelectByPerimSizeRatio()
 *           PIX      *pixSelectByAreaFraction()
 *           PIXA     *pixaSelectByAreaFraction()
 *           PIX      *pixSelectByArea()
 *           PIXA     *pixaSelectByArea()
 *           PIX      *pixSelectByWidthHeightRatio()
 *           PIXA     *pixaSelectByWidthHeightRatio()
 *           PIXA     *pixaSelectByNumConnComp()
 *
 *           PIXA     *pixaSelectWithIndicator()
 *           l_int32   pixRemoveWithIndicator()
 *           l_int32   pixAddWithIndicator()
 *           PIXA     *pixaSelectWithString()
 *           PIX      *pixaRenderComponent()
 *
 *      Sort functions
 *           PIXA     *pixaSort()
 *           PIXA     *pixaBinSort()
 *           PIXA     *pixaSortByIndex()
 *           PIXAA    *pixaSort2dByIndex()
 *
 *      Pixa and Pixaa range selection
 *           PIXA     *pixaSelectRange()
 *           PIXAA    *pixaaSelectRange()
 *
 *      Pixa and Pixaa scaling
 *           PIXAA    *pixaaScaleToSize()
 *           PIXAA    *pixaaScaleToSizeVar()
 *           PIXA     *pixaScaleToSize()
 *           PIXA     *pixaScaleToSizeRel()
 *           PIXA     *pixaScale()
 *           PIXA     *pixaScaleBySampling()
 *
 *      Pixa rotation and translation
 *           PIXA     *pixaRotate()
 *           PIXA     *pixaRotateOrth()
 *           PIXA     *pixaTranslate()
 *
 *      Miscellaneous
 *           PIXA     *pixaAddBorderGeneral()
 *           PIXA     *pixaaFlattenToPixa()
 *           l_int32   pixaaSizeRange()
 *           l_int32   pixaSizeRange()
 *           PIXA     *pixaClipToPix()
 *           PIXA     *pixaClipToForeground()
 *           l_int32   pixaGetRenderingDepth()
 *           l_int32   pixaHasColor()
 *           l_int32   pixaAnyColormaps()
 *           l_int32   pixaGetDepthInfo()
 *           PIXA     *pixaConvertToSameDepth()
 *           l_int32   pixaEqual()
 *           l_int32   pixaSetFullSizeBoxa()
 * 
*/ #ifdef HAVE_CONFIG_H #include #endif /* HAVE_CONFIG_H */ #include #include "allheaders.h" /* For more than this number of c.c. in a binarized image of * semi-perimeter (w + h) about 5000 or less, the O(n) binsort * is faster than the O(nlogn) shellsort. */ static const l_int32 MinCompsForBinSort = 200; /* Don't rotate any angle smaller than this */ static const l_float32 MinAngleToRotate = 0.001f; /* radians; ~0.06 deg */ /*---------------------------------------------------------------------* * Filters * *---------------------------------------------------------------------*/ /* * These filters work on the connected components of 1 bpp images. * They are typically used on pixa that have been generated from a Pix * using pixConnComp(), so that the corresponding Boxa is available. * * The filters remove or retain c.c. based on these properties: * (a) size [pixaFindDimensions()] * (b) area-to-perimeter ratio [pixaFindAreaPerimRatio()] * (c) foreground area as a fraction of bounding box area (w * h) * [pixaFindForegroundArea()] * (d) number of foreground pixels [pixaCountPixels()] * (e) width/height aspect ratio [pixFindWidthHeightRatio()] * * We provide two different high-level interfaces: * (1) Functions that use one of the filters on either * a pix or the pixa of components. * (2) A general method that generates numas of indicator functions, * logically combines them, and efficiently removes or adds * the selected components. * * For interface (1), the filtering is performed with a single function call. * This is the easiest way to do simple filtering. These functions * are named pixSelectBy*() and pixaSelectBy*(), where the '*' is one of: * Size * PerimToAreaRatio * PerimSizeRatio * Area * AreaFraction * WidthHeightRatio * * For more complicated filtering, use the general method (2). * The numa indicator functions for a pixa are generated by these functions: * pixaFindDimensions() * pixaFindPerimToAreaRatio() * pixaFindPerimSizeRatio() * pixaFindAreaFraction() * pixaCountPixels() * pixaFindWidthHeightRatio() * pixaFindWidthHeightProduct() * * Here is an illustration using the general method. Suppose you want * all 8-connected components that have a height greater than 40 pixels, * a width not more than 30 pixels, between 150 and 300 fg pixels, * and a perimeter-to-size ratio between 1.2 and 2.0. * * // Generate the pixa of 8 cc pieces. * boxa = pixConnComp(pixs, &pixa, 8); * * // Extract the data we need about each component. * pixaFindDimensions(pixa, &naw, &nah); * nas = pixaCountPixels(pixa); * nar = pixaFindPerimSizeRatio(pixa); * * // Build the indicator arrays for the set of components, * // based on thresholds and selection criteria. * na1 = numaMakeThresholdIndicator(nah, 40, L_SELECT_IF_GT); * na2 = numaMakeThresholdIndicator(naw, 30, L_SELECT_IF_LTE); * na3 = numaMakeThresholdIndicator(nas, 150, L_SELECT_IF_GTE); * na4 = numaMakeThresholdIndicator(nas, 300, L_SELECT_IF_LTE); * na5 = numaMakeThresholdIndicator(nar, 1.2, L_SELECT_IF_GTE); * na6 = numaMakeThresholdIndicator(nar, 2.0, L_SELECT_IF_LTE); * * // Combine the indicator arrays logically to find * // the components that will be retained. * nad = numaLogicalOp(NULL, na1, na2, L_INTERSECTION); * numaLogicalOp(nad, nad, na3, L_INTERSECTION); * numaLogicalOp(nad, nad, na4, L_INTERSECTION); * numaLogicalOp(nad, nad, na5, L_INTERSECTION); * numaLogicalOp(nad, nad, na6, L_INTERSECTION); * * // Invert to get the components that will be removed. * numaInvert(nad, nad); * * // Remove the components, in-place. * pixRemoveWithIndicator(pixs, pixa, nad); */ /*! * \brief pixSelectBySize() * * \param[in] pixs 1 bpp * \param[in] width, height threshold dimensions * \param[in] connectivity 4 or 8 * \param[in] type L_SELECT_WIDTH, L_SELECT_HEIGHT, * L_SELECT_IF_EITHER, L_SELECT_IF_BOTH * \param[in] relation L_SELECT_IF_LT, L_SELECT_IF_GT, * L_SELECT_IF_LTE, L_SELECT_IF_GTE * \param[out] pchanged [optional] 1 if changed; 0 otherwise * \return filtered pixd, or NULL on error * *
 * Notes:
 *      (1) The args specify constraints on the size of the
 *          components that are kept.
 *      (2) If unchanged, returns a copy of pixs.  Otherwise,
 *          returns a new pix with the filtered components.
 *      (3) If the selection type is L_SELECT_WIDTH, the input
 *          height is ignored, and v.v.
 *      (4) To keep small components, use relation = L_SELECT_IF_LT or
 *          L_SELECT_IF_LTE.
 *          To keep large components, use relation = L_SELECT_IF_GT or
 *          L_SELECT_IF_GTE.
 * 
*/ PIX * pixSelectBySize(PIX *pixs, l_int32 width, l_int32 height, l_int32 connectivity, l_int32 type, l_int32 relation, l_int32 *pchanged) { l_int32 w, h, empty, changed, count; BOXA *boxa; PIX *pixd; PIXA *pixas, *pixad; PROCNAME("pixSelectBySize"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (connectivity != 4 && connectivity != 8) return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); if (type != L_SELECT_WIDTH && type != L_SELECT_HEIGHT && type != L_SELECT_IF_EITHER && type != L_SELECT_IF_BOTH) return (PIX *)ERROR_PTR("invalid type", procName, NULL); if (relation != L_SELECT_IF_LT && relation != L_SELECT_IF_GT && relation != L_SELECT_IF_LTE && relation != L_SELECT_IF_GTE) return (PIX *)ERROR_PTR("invalid relation", procName, NULL); if (pchanged) *pchanged = FALSE; /* Check if any components exist */ pixZero(pixs, &empty); if (empty) return pixCopy(NULL, pixs); /* Identify and select the components */ boxa = pixConnComp(pixs, &pixas, connectivity); pixad = pixaSelectBySize(pixas, width, height, type, relation, &changed); boxaDestroy(&boxa); pixaDestroy(&pixas); if (!changed) { pixaDestroy(&pixad); return pixCopy(NULL, pixs); } /* Render the result */ if (pchanged) *pchanged = TRUE; pixGetDimensions(pixs, &w, &h, NULL); count = pixaGetCount(pixad); if (count == 0) { /* return empty pix */ pixd = pixCreateTemplate(pixs); } else { pixd = pixaDisplay(pixad, w, h); pixCopyResolution(pixd, pixs); pixCopyColormap(pixd, pixs); pixCopyText(pixd, pixs); pixCopyInputFormat(pixd, pixs); } pixaDestroy(&pixad); return pixd; } /*! * \brief pixaSelectBySize() * * \param[in] pixas * \param[in] width, height threshold dimensions * \param[in] type L_SELECT_WIDTH, L_SELECT_HEIGHT, * L_SELECT_IF_EITHER, L_SELECT_IF_BOTH * \param[in] relation L_SELECT_IF_LT, L_SELECT_IF_GT, * L_SELECT_IF_LTE, L_SELECT_IF_GTE * \param[out] pchanged [optional] 1 if changed; 0 otherwise * \return pixad, or NULL on error * *
 * Notes:
 *      (1) The args specify constraints on the size of the
 *          components that are kept.
 *      (2) Uses pix and box clones in the new pixa.
 *      (3) If the selection type is L_SELECT_WIDTH, the input
 *          height is ignored, and v.v.
 *      (4) To keep small components, use relation = L_SELECT_IF_LT or
 *          L_SELECT_IF_LTE.
 *          To keep large components, use relation = L_SELECT_IF_GT or
 *          L_SELECT_IF_GTE.
 * 
*/ PIXA * pixaSelectBySize(PIXA *pixas, l_int32 width, l_int32 height, l_int32 type, l_int32 relation, l_int32 *pchanged) { NUMA *na; PIXA *pixad; PROCNAME("pixaSelectBySize"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (type != L_SELECT_WIDTH && type != L_SELECT_HEIGHT && type != L_SELECT_IF_EITHER && type != L_SELECT_IF_BOTH) return (PIXA *)ERROR_PTR("invalid type", procName, NULL); if (relation != L_SELECT_IF_LT && relation != L_SELECT_IF_GT && relation != L_SELECT_IF_LTE && relation != L_SELECT_IF_GTE) return (PIXA *)ERROR_PTR("invalid relation", procName, NULL); /* Compute the indicator array for saving components */ na = pixaMakeSizeIndicator(pixas, width, height, type, relation); /* Filter to get output */ pixad = pixaSelectWithIndicator(pixas, na, pchanged); numaDestroy(&na); return pixad; } /*! * \brief pixaMakeSizeIndicator() * * \param[in] pixa * \param[in] width, height threshold dimensions * \param[in] type L_SELECT_WIDTH, L_SELECT_HEIGHT, * L_SELECT_IF_EITHER, L_SELECT_IF_BOTH * \param[in] relation L_SELECT_IF_LT, L_SELECT_IF_GT, * L_SELECT_IF_LTE, L_SELECT_IF_GTE * \return na indicator array, or NULL on error * *
 * Notes:
 *      (1) The args specify constraints on the size of the
 *          components that are kept.
 *      (2) If the selection type is L_SELECT_WIDTH, the input
 *          height is ignored, and v.v.
 *      (3) To keep small components, use relation = L_SELECT_IF_LT or
 *          L_SELECT_IF_LTE.
 *          To keep large components, use relation = L_SELECT_IF_GT or
 *          L_SELECT_IF_GTE.
 * 
*/ NUMA * pixaMakeSizeIndicator(PIXA *pixa, l_int32 width, l_int32 height, l_int32 type, l_int32 relation) { l_int32 i, n, w, h, ival; NUMA *na; PROCNAME("pixaMakeSizeIndicator"); if (!pixa) return (NUMA *)ERROR_PTR("pixa not defined", procName, NULL); if (type != L_SELECT_WIDTH && type != L_SELECT_HEIGHT && type != L_SELECT_IF_EITHER && type != L_SELECT_IF_BOTH) return (NUMA *)ERROR_PTR("invalid type", procName, NULL); if (relation != L_SELECT_IF_LT && relation != L_SELECT_IF_GT && relation != L_SELECT_IF_LTE && relation != L_SELECT_IF_GTE) return (NUMA *)ERROR_PTR("invalid relation", procName, NULL); n = pixaGetCount(pixa); na = numaCreate(n); for (i = 0; i < n; i++) { ival = 0; pixaGetPixDimensions(pixa, i, &w, &h, NULL); switch (type) { case L_SELECT_WIDTH: if ((relation == L_SELECT_IF_LT && w < width) || (relation == L_SELECT_IF_GT && w > width) || (relation == L_SELECT_IF_LTE && w <= width) || (relation == L_SELECT_IF_GTE && w >= width)) ival = 1; break; case L_SELECT_HEIGHT: if ((relation == L_SELECT_IF_LT && h < height) || (relation == L_SELECT_IF_GT && h > height) || (relation == L_SELECT_IF_LTE && h <= height) || (relation == L_SELECT_IF_GTE && h >= height)) ival = 1; break; case L_SELECT_IF_EITHER: if (((relation == L_SELECT_IF_LT) && (w < width || h < height)) || ((relation == L_SELECT_IF_GT) && (w > width || h > height)) || ((relation == L_SELECT_IF_LTE) && (w <= width || h <= height)) || ((relation == L_SELECT_IF_GTE) && (w >= width || h >= height))) ival = 1; break; case L_SELECT_IF_BOTH: if (((relation == L_SELECT_IF_LT) && (w < width && h < height)) || ((relation == L_SELECT_IF_GT) && (w > width && h > height)) || ((relation == L_SELECT_IF_LTE) && (w <= width && h <= height)) || ((relation == L_SELECT_IF_GTE) && (w >= width && h >= height))) ival = 1; break; default: L_WARNING("can't get here!\n", procName); break; } numaAddNumber(na, ival); } return na; } /*! * \brief pixSelectByPerimToAreaRatio() * * \param[in] pixs 1 bpp * \param[in] thresh threshold ratio of fg boundary to fg pixels * \param[in] connectivity 4 or 8 * \param[in] type L_SELECT_IF_LT, L_SELECT_IF_GT, * L_SELECT_IF_LTE, L_SELECT_IF_GTE * \param[out] pchanged [optional] 1 if changed; 0 if clone returned * \return pixd, or NULL on error * *
 * Notes:
 *      (1) The args specify constraints on the size of the
 *          components that are kept.
 *      (2) If unchanged, returns a copy of pixs.  Otherwise,
 *          returns a new pix with the filtered components.
 *      (3) This filters "thick" components, where a thick component
 *          is defined to have a ratio of boundary to interior pixels
 *          that is smaller than a given threshold value.
 *      (4) Use L_SELECT_IF_LT or L_SELECT_IF_LTE to save the thicker
 *          components, and L_SELECT_IF_GT or L_SELECT_IF_GTE to remove them.
 * 
*/ PIX * pixSelectByPerimToAreaRatio(PIX *pixs, l_float32 thresh, l_int32 connectivity, l_int32 type, l_int32 *pchanged) { l_int32 w, h, empty, changed, count; BOXA *boxa; PIX *pixd; PIXA *pixas, *pixad; PROCNAME("pixSelectByPerimToAreaRatio"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (connectivity != 4 && connectivity != 8) return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); if (type != L_SELECT_IF_LT && type != L_SELECT_IF_GT && type != L_SELECT_IF_LTE && type != L_SELECT_IF_GTE) return (PIX *)ERROR_PTR("invalid type", procName, NULL); if (pchanged) *pchanged = FALSE; /* Check if any components exist */ pixZero(pixs, &empty); if (empty) return pixCopy(NULL, pixs); /* Filter thin components */ boxa = pixConnComp(pixs, &pixas, connectivity); pixad = pixaSelectByPerimToAreaRatio(pixas, thresh, type, &changed); boxaDestroy(&boxa); pixaDestroy(&pixas); if (!changed) { pixaDestroy(&pixad); return pixCopy(NULL, pixs); } /* Render the result */ if (pchanged) *pchanged = TRUE; pixGetDimensions(pixs, &w, &h, NULL); count = pixaGetCount(pixad); if (count == 0) { /* return empty pix */ pixd = pixCreateTemplate(pixs); } else { pixd = pixaDisplay(pixad, w, h); pixCopyResolution(pixd, pixs); pixCopyColormap(pixd, pixs); pixCopyText(pixd, pixs); pixCopyInputFormat(pixd, pixs); } pixaDestroy(&pixad); return pixd; } /*! * \brief pixaSelectByPerimToAreaRatio() * * \param[in] pixas * \param[in] thresh threshold ratio of fg boundary to fg pixels * \param[in] type L_SELECT_IF_LT, L_SELECT_IF_GT, * L_SELECT_IF_LTE, L_SELECT_IF_GTE * \param[out] pchanged [optional] 1 if changed; 0 if clone returned * \return pixad, or NULL on error * *
 * Notes:
 *      (1) Returns a pixa clone if no components are removed.
 *      (2) Uses pix and box clones in the new pixa.
 *      (3) See pixSelectByPerimToAreaRatio().
 * 
*/ PIXA * pixaSelectByPerimToAreaRatio(PIXA *pixas, l_float32 thresh, l_int32 type, l_int32 *pchanged) { NUMA *na, *nai; PIXA *pixad; PROCNAME("pixaSelectByPerimToAreaRatio"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (type != L_SELECT_IF_LT && type != L_SELECT_IF_GT && type != L_SELECT_IF_LTE && type != L_SELECT_IF_GTE) return (PIXA *)ERROR_PTR("invalid type", procName, NULL); /* Compute component ratios. */ na = pixaFindPerimToAreaRatio(pixas); /* Generate indicator array for elements to be saved. */ nai = numaMakeThresholdIndicator(na, thresh, type); numaDestroy(&na); /* Filter to get output */ pixad = pixaSelectWithIndicator(pixas, nai, pchanged); numaDestroy(&nai); return pixad; } /*! * \brief pixSelectByPerimSizeRatio() * * \param[in] pixs 1 bpp * \param[in] thresh threshold ratio of fg boundary to fg pixels * \param[in] connectivity 4 or 8 * \param[in] type L_SELECT_IF_LT, L_SELECT_IF_GT, * L_SELECT_IF_LTE, L_SELECT_IF_GTE * \param[out] pchanged [optional] 1 if changed; 0 if clone returned * \return pixd, or NULL on error * *
 * Notes:
 *      (1) The args specify constraints on the size of the
 *          components that are kept.
 *      (2) If unchanged, returns a copy of pixs.  Otherwise,
 *          returns a new pix with the filtered components.
 *      (3) This filters components with smooth vs. dendritic shape, using
 *          the ratio of the fg boundary pixels to the circumference of
 *          the bounding box, and comparing it to a threshold value.
 *      (4) Use L_SELECT_IF_LT or L_SELECT_IF_LTE to save the smooth
 *          boundary components, and L_SELECT_IF_GT or L_SELECT_IF_GTE
 *          to remove them.
 * 
*/ PIX * pixSelectByPerimSizeRatio(PIX *pixs, l_float32 thresh, l_int32 connectivity, l_int32 type, l_int32 *pchanged) { l_int32 w, h, empty, changed, count; BOXA *boxa; PIX *pixd; PIXA *pixas, *pixad; PROCNAME("pixSelectByPerimSizeRatio"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (connectivity != 4 && connectivity != 8) return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); if (type != L_SELECT_IF_LT && type != L_SELECT_IF_GT && type != L_SELECT_IF_LTE && type != L_SELECT_IF_GTE) return (PIX *)ERROR_PTR("invalid type", procName, NULL); if (pchanged) *pchanged = FALSE; /* Check if any components exist */ pixZero(pixs, &empty); if (empty) return pixCopy(NULL, pixs); /* Filter thin components */ boxa = pixConnComp(pixs, &pixas, connectivity); pixad = pixaSelectByPerimSizeRatio(pixas, thresh, type, &changed); boxaDestroy(&boxa); pixaDestroy(&pixas); if (!changed) { pixaDestroy(&pixad); return pixCopy(NULL, pixs); } /* Render the result */ if (pchanged) *pchanged = TRUE; pixGetDimensions(pixs, &w, &h, NULL); count = pixaGetCount(pixad); if (count == 0) { /* return empty pix */ pixd = pixCreateTemplate(pixs); } else { pixd = pixaDisplay(pixad, w, h); pixCopyResolution(pixd, pixs); pixCopyColormap(pixd, pixs); pixCopyText(pixd, pixs); pixCopyInputFormat(pixd, pixs); } pixaDestroy(&pixad); return pixd; } /*! * \brief pixaSelectByPerimSizeRatio() * * \param[in] pixas * \param[in] thresh threshold ratio of fg boundary to b.b. circumference * \param[in] type L_SELECT_IF_LT, L_SELECT_IF_GT, * L_SELECT_IF_LTE, L_SELECT_IF_GTE * \param[out] pchanged [optional] 1 if changed; 0 if clone returned * \return pixad, or NULL on error * *
 * Notes:
 *      (1) Returns a pixa clone if no components are removed.
 *      (2) Uses pix and box clones in the new pixa.
 *      (3) See pixSelectByPerimSizeRatio().
 * 
*/ PIXA * pixaSelectByPerimSizeRatio(PIXA *pixas, l_float32 thresh, l_int32 type, l_int32 *pchanged) { NUMA *na, *nai; PIXA *pixad; PROCNAME("pixaSelectByPerimSizeRatio"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (type != L_SELECT_IF_LT && type != L_SELECT_IF_GT && type != L_SELECT_IF_LTE && type != L_SELECT_IF_GTE) return (PIXA *)ERROR_PTR("invalid type", procName, NULL); /* Compute component ratios. */ na = pixaFindPerimSizeRatio(pixas); /* Generate indicator array for elements to be saved. */ nai = numaMakeThresholdIndicator(na, thresh, type); numaDestroy(&na); /* Filter to get output */ pixad = pixaSelectWithIndicator(pixas, nai, pchanged); numaDestroy(&nai); return pixad; } /*! * \brief pixSelectByAreaFraction() * * \param[in] pixs 1 bpp * \param[in] thresh threshold ratio of fg pixels to (w * h) * \param[in] connectivity 4 or 8 * \param[in] type L_SELECT_IF_LT, L_SELECT_IF_GT, * L_SELECT_IF_LTE, L_SELECT_IF_GTE * \param[out] pchanged [optional] 1 if changed; 0 if clone returned * \return pixd, or NULL on error * *
 * Notes:
 *      (1) The args specify constraints on the amount of foreground
 *          coverage of the components that are kept.
 *      (2) If unchanged, returns a copy of pixs.  Otherwise,
 *          returns a new pix with the filtered components.
 *      (3) This filters components based on the fraction of fg pixels
 *          of the component in its bounding box.
 *      (4) Use L_SELECT_IF_LT or L_SELECT_IF_LTE to save components
 *          with less than the threshold fraction of foreground, and
 *          L_SELECT_IF_GT or L_SELECT_IF_GTE to remove them.
 * 
*/ PIX * pixSelectByAreaFraction(PIX *pixs, l_float32 thresh, l_int32 connectivity, l_int32 type, l_int32 *pchanged) { l_int32 w, h, empty, changed, count; BOXA *boxa; PIX *pixd; PIXA *pixas, *pixad; PROCNAME("pixSelectByAreaFraction"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (connectivity != 4 && connectivity != 8) return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); if (type != L_SELECT_IF_LT && type != L_SELECT_IF_GT && type != L_SELECT_IF_LTE && type != L_SELECT_IF_GTE) return (PIX *)ERROR_PTR("invalid type", procName, NULL); if (pchanged) *pchanged = FALSE; /* Check if any components exist */ pixZero(pixs, &empty); if (empty) return pixCopy(NULL, pixs); /* Filter components */ boxa = pixConnComp(pixs, &pixas, connectivity); pixad = pixaSelectByAreaFraction(pixas, thresh, type, &changed); boxaDestroy(&boxa); pixaDestroy(&pixas); if (!changed) { pixaDestroy(&pixad); return pixCopy(NULL, pixs); } /* Render the result */ if (pchanged) *pchanged = TRUE; pixGetDimensions(pixs, &w, &h, NULL); count = pixaGetCount(pixad); if (count == 0) { /* return empty pix */ pixd = pixCreateTemplate(pixs); } else { pixd = pixaDisplay(pixad, w, h); pixCopyResolution(pixd, pixs); pixCopyColormap(pixd, pixs); pixCopyText(pixd, pixs); pixCopyInputFormat(pixd, pixs); } pixaDestroy(&pixad); return pixd; } /*! * \brief pixaSelectByAreaFraction() * * \param[in] pixas * \param[in] thresh threshold ratio of fg pixels to (w * h) * \param[in] type L_SELECT_IF_LT, L_SELECT_IF_GT, * L_SELECT_IF_LTE, L_SELECT_IF_GTE * \param[out] pchanged [optional] 1 if changed; 0 if clone returned * \return pixad, or NULL on error * *
 * Notes:
 *      (1) Returns a pixa clone if no components are removed.
 *      (2) Uses pix and box clones in the new pixa.
 *      (3) This filters components based on the fraction of fg pixels
 *          of the component in its bounding box.
 *      (4) Use L_SELECT_IF_LT or L_SELECT_IF_LTE to save components
 *          with less than the threshold fraction of foreground, and
 *          L_SELECT_IF_GT or L_SELECT_IF_GTE to remove them.
 * 
*/ PIXA * pixaSelectByAreaFraction(PIXA *pixas, l_float32 thresh, l_int32 type, l_int32 *pchanged) { NUMA *na, *nai; PIXA *pixad; PROCNAME("pixaSelectByAreaFraction"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (type != L_SELECT_IF_LT && type != L_SELECT_IF_GT && type != L_SELECT_IF_LTE && type != L_SELECT_IF_GTE) return (PIXA *)ERROR_PTR("invalid type", procName, NULL); /* Compute component ratios. */ na = pixaFindAreaFraction(pixas); /* Generate indicator array for elements to be saved. */ nai = numaMakeThresholdIndicator(na, thresh, type); numaDestroy(&na); /* Filter to get output */ pixad = pixaSelectWithIndicator(pixas, nai, pchanged); numaDestroy(&nai); return pixad; } /*! * \brief pixSelectByArea() * * \param[in] pixs 1 bpp * \param[in] thresh threshold number of FG pixels * \param[in] connectivity 4 or 8 * \param[in] type L_SELECT_IF_LT, L_SELECT_IF_GT, * L_SELECT_IF_LTE, L_SELECT_IF_GTE * \param[out] pchanged [optional] 1 if changed; 0 if clone returned * \return pixd, or NULL on error * *
 * Notes:
 *      (1) The args specify constraints on the number of foreground
 *          pixels in the components that are kept.
 *      (2) If unchanged, returns a copy of pixs.  Otherwise,
 *          returns a new pix with the filtered components.
 *      (3) This filters components based on the number of fg pixels
 *          in each component.
 *      (4) Use L_SELECT_IF_LT or L_SELECT_IF_LTE to save components
 *          with less than the threshold number of fg pixels, and
 *          L_SELECT_IF_GT or L_SELECT_IF_GTE to remove them.
 * 
*/ PIX * pixSelectByArea(PIX *pixs, l_float32 thresh, l_int32 connectivity, l_int32 type, l_int32 *pchanged) { l_int32 w, h, empty, changed, count; BOXA *boxa; PIX *pixd; PIXA *pixas, *pixad; PROCNAME("pixSelectByArea"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (connectivity != 4 && connectivity != 8) return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); if (type != L_SELECT_IF_LT && type != L_SELECT_IF_GT && type != L_SELECT_IF_LTE && type != L_SELECT_IF_GTE) return (PIX *)ERROR_PTR("invalid type", procName, NULL); if (pchanged) *pchanged = FALSE; /* Check if any components exist */ pixZero(pixs, &empty); if (empty) return pixCopy(NULL, pixs); /* Filter components */ boxa = pixConnComp(pixs, &pixas, connectivity); pixad = pixaSelectByArea(pixas, thresh, type, &changed); boxaDestroy(&boxa); pixaDestroy(&pixas); if (!changed) { pixaDestroy(&pixad); return pixCopy(NULL, pixs); } /* Render the result */ if (pchanged) *pchanged = TRUE; pixGetDimensions(pixs, &w, &h, NULL); count = pixaGetCount(pixad); if (count == 0) { /* return empty pix */ pixd = pixCreateTemplate(pixs); } else { pixd = pixaDisplay(pixad, w, h); pixCopyResolution(pixd, pixs); pixCopyColormap(pixd, pixs); pixCopyText(pixd, pixs); pixCopyInputFormat(pixd, pixs); } pixaDestroy(&pixad); return pixd; } /*! * \brief pixaSelectByArea() * * \param[in] pixas * \param[in] thresh threshold number of fg pixels * \param[in] type L_SELECT_IF_LT, L_SELECT_IF_GT, * L_SELECT_IF_LTE, L_SELECT_IF_GTE * \param[out] pchanged [optional] 1 if changed; 0 if clone returned * \return pixad, or NULL on error * *
 * Notes:
 *      (1) Returns a pixa clone if no components are removed.
 *      (2) Uses pix and box clones in the new pixa.
 *      (3) This filters components based on the number of fg pixels
 *          in the component.
 *      (4) Use L_SELECT_IF_LT or L_SELECT_IF_LTE to save components
 *          with less than the threshold number of fg pixels, and
 *          L_SELECT_IF_GT or L_SELECT_IF_GTE to remove them.
 * 
*/ PIXA * pixaSelectByArea(PIXA *pixas, l_float32 thresh, l_int32 type, l_int32 *pchanged) { NUMA *na, *nai; PIXA *pixad; PROCNAME("pixaSelectByArea"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (type != L_SELECT_IF_LT && type != L_SELECT_IF_GT && type != L_SELECT_IF_LTE && type != L_SELECT_IF_GTE) return (PIXA *)ERROR_PTR("invalid type", procName, NULL); /* Compute area of each component */ na = pixaCountPixels(pixas); /* Generate indicator array for elements to be saved. */ nai = numaMakeThresholdIndicator(na, thresh, type); numaDestroy(&na); /* Filter to get output */ pixad = pixaSelectWithIndicator(pixas, nai, pchanged); numaDestroy(&nai); return pixad; } /*! * \brief pixSelectByWidthHeightRatio() * * \param[in] pixs 1 bpp * \param[in] thresh threshold ratio of width/height * \param[in] connectivity 4 or 8 * \param[in] type L_SELECT_IF_LT, L_SELECT_IF_GT, * L_SELECT_IF_LTE, L_SELECT_IF_GTE * \param[out] pchanged [optional] 1 if changed; 0 if clone returned * \return pixd, or NULL on error * *
 * Notes:
 *      (1) The args specify constraints on the width-to-height ratio
 *          for components that are kept.
 *      (2) If unchanged, returns a copy of pixs.  Otherwise,
 *          returns a new pix with the filtered components.
 *      (3) This filters components based on the width-to-height ratios.
 *      (4) Use L_SELECT_IF_LT or L_SELECT_IF_LTE to save components
 *          with less than the threshold ratio, and
 *          L_SELECT_IF_GT or L_SELECT_IF_GTE to remove them.
 * 
*/ PIX * pixSelectByWidthHeightRatio(PIX *pixs, l_float32 thresh, l_int32 connectivity, l_int32 type, l_int32 *pchanged) { l_int32 w, h, empty, changed, count; BOXA *boxa; PIX *pixd; PIXA *pixas, *pixad; PROCNAME("pixSelectByWidthHeightRatio"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (connectivity != 4 && connectivity != 8) return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); if (type != L_SELECT_IF_LT && type != L_SELECT_IF_GT && type != L_SELECT_IF_LTE && type != L_SELECT_IF_GTE) return (PIX *)ERROR_PTR("invalid type", procName, NULL); if (pchanged) *pchanged = FALSE; /* Check if any components exist */ pixZero(pixs, &empty); if (empty) return pixCopy(NULL, pixs); /* Filter components */ boxa = pixConnComp(pixs, &pixas, connectivity); pixad = pixaSelectByWidthHeightRatio(pixas, thresh, type, &changed); boxaDestroy(&boxa); pixaDestroy(&pixas); if (!changed) { pixaDestroy(&pixad); return pixCopy(NULL, pixs); } /* Render the result */ if (pchanged) *pchanged = TRUE; pixGetDimensions(pixs, &w, &h, NULL); count = pixaGetCount(pixad); if (count == 0) { /* return empty pix */ pixd = pixCreateTemplate(pixs); } else { pixd = pixaDisplay(pixad, w, h); pixCopyResolution(pixd, pixs); pixCopyColormap(pixd, pixs); pixCopyText(pixd, pixs); pixCopyInputFormat(pixd, pixs); } pixaDestroy(&pixad); return pixd; } /*! * \brief pixaSelectByWidthHeightRatio() * * \param[in] pixas * \param[in] thresh threshold ratio of width/height * \param[in] type L_SELECT_IF_LT, L_SELECT_IF_GT, * L_SELECT_IF_LTE, L_SELECT_IF_GTE * \param[out] pchanged [optional] 1 if changed; 0 if clone returned * \return pixad, or NULL on error * *
 * Notes:
 *      (1) Returns a pixa clone if no components are removed.
 *      (2) Uses pix and box clones in the new pixa.
 *      (3) This filters components based on the width-to-height ratio
 *          of each pix.
 *      (4) Use L_SELECT_IF_LT or L_SELECT_IF_LTE to save components
 *          with less than the threshold ratio, and
 *          L_SELECT_IF_GT or L_SELECT_IF_GTE to remove them.
 * 
*/ PIXA * pixaSelectByWidthHeightRatio(PIXA *pixas, l_float32 thresh, l_int32 type, l_int32 *pchanged) { NUMA *na, *nai; PIXA *pixad; PROCNAME("pixaSelectByWidthHeightRatio"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (type != L_SELECT_IF_LT && type != L_SELECT_IF_GT && type != L_SELECT_IF_LTE && type != L_SELECT_IF_GTE) return (PIXA *)ERROR_PTR("invalid type", procName, NULL); /* Compute component ratios. */ na = pixaFindWidthHeightRatio(pixas); /* Generate indicator array for elements to be saved. */ nai = numaMakeThresholdIndicator(na, thresh, type); numaDestroy(&na); /* Filter to get output */ pixad = pixaSelectWithIndicator(pixas, nai, pchanged); numaDestroy(&nai); return pixad; } /*! * \brief pixaSelectByNumConnComp() * * \param[in] pixas * \param[in] nmin minimum number of components * \param[in] nmax maximum number of components * \param[in] connectivity 4 or 8 * \param[out] pchanged [optional] 1 if changed; 0 if clone returned * \return pixad, or NULL on error * *
 * Notes:
 *      (1) Returns a pixa clone if no components are removed.
 *      (2) Uses pix and box clones in the new pixa.
 *      (3) This filters by the number of connected components in
 *          a given range.
 * 
*/ PIXA * pixaSelectByNumConnComp(PIXA *pixas, l_int32 nmin, l_int32 nmax, l_int32 connectivity, l_int32 *pchanged) { l_int32 n, i, count; NUMA *na; PIX *pix; PIXA *pixad; PROCNAME("pixaSelectByNumConnComp"); if (pchanged) *pchanged = 0; if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (nmin > nmax) return (PIXA *)ERROR_PTR("nmin > nmax", procName, NULL); if (connectivity != 4 && connectivity != 8) return (PIXA *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); /* Get indicator array based on number of c.c. */ n = pixaGetCount(pixas); na = numaCreate(n); for (i = 0; i < n; i++) { pix = pixaGetPix(pixas, i, L_CLONE); pixCountConnComp(pix, connectivity, &count); if (count >= nmin && count <= nmax) numaAddNumber(na, 1); else numaAddNumber(na, 0); pixDestroy(&pix); } /* Filter to get output */ pixad = pixaSelectWithIndicator(pixas, na, pchanged); numaDestroy(&na); return pixad; } /*! * \brief pixaSelectWithIndicator() * * \param[in] pixas * \param[in] na indicator numa * \param[out] pchanged [optional] 1 if changed; 0 if clone returned * \return pixad, or NULL on error * *
 * Notes:
 *      (1) Returns a pixa clone if no components are removed.
 *      (2) Uses pix and box clones in the new pixa.
 *      (3) The indicator numa has values 0 (ignore) and 1 (accept).
 *      (4) If the source boxa is not fully populated, it is left
 *          empty in the dest pixa.
 * 
*/ PIXA * pixaSelectWithIndicator(PIXA *pixas, NUMA *na, l_int32 *pchanged) { l_int32 i, n, nbox, ival, nsave; BOX *box; PIX *pix1; PIXA *pixad; PROCNAME("pixaSelectWithIndicator"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (!na) return (PIXA *)ERROR_PTR("na not defined", procName, NULL); nsave = 0; n = numaGetCount(na); for (i = 0; i < n; i++) { numaGetIValue(na, i, &ival); if (ival == 1) nsave++; } if (nsave == n) { if (pchanged) *pchanged = FALSE; return pixaCopy(pixas, L_CLONE); } if (pchanged) *pchanged = TRUE; pixad = pixaCreate(nsave); nbox = pixaGetBoxaCount(pixas); for (i = 0; i < n; i++) { numaGetIValue(na, i, &ival); if (ival == 0) continue; pix1 = pixaGetPix(pixas, i, L_CLONE); pixaAddPix(pixad, pix1, L_INSERT); if (nbox == n) { /* fully populated boxa */ box = pixaGetBox(pixas, i, L_CLONE); pixaAddBox(pixad, box, L_INSERT); } } return pixad; } /*! * \brief pixRemoveWithIndicator() * * \param[in] pixs 1 bpp pix from which components are removed; in-place * \param[in] pixa of connected components in pixs * \param[in] na numa indicator: remove components corresponding to 1s * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) This complements pixAddWithIndicator().   Here, the selected
 *          components are set subtracted from pixs.
 * 
*/ l_ok pixRemoveWithIndicator(PIX *pixs, PIXA *pixa, NUMA *na) { l_int32 i, n, ival, x, y, w, h; BOX *box; PIX *pix; PROCNAME("pixRemoveWithIndicator"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (!pixa) return ERROR_INT("pixa not defined", procName, 1); if (!na) return ERROR_INT("na not defined", procName, 1); n = pixaGetCount(pixa); if (n != numaGetCount(na)) return ERROR_INT("pixa and na sizes not equal", procName, 1); for (i = 0; i < n; i++) { numaGetIValue(na, i, &ival); if (ival == 1) { pix = pixaGetPix(pixa, i, L_CLONE); box = pixaGetBox(pixa, i, L_CLONE); boxGetGeometry(box, &x, &y, &w, &h); pixRasterop(pixs, x, y, w, h, PIX_DST & PIX_NOT(PIX_SRC), pix, 0, 0); boxDestroy(&box); pixDestroy(&pix); } } return 0; } /*! * \brief pixAddWithIndicator() * * \param[in] pixs 1 bpp pix from which components are added; in-place * \param[in] pixa of connected components, some of which will be put * into pixs * \param[in] na numa indicator: add components corresponding to 1s * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) This complements pixRemoveWithIndicator().   Here, the selected
 *          components are added to pixs.
 * 
*/ l_ok pixAddWithIndicator(PIX *pixs, PIXA *pixa, NUMA *na) { l_int32 i, n, ival, x, y, w, h; BOX *box; PIX *pix; PROCNAME("pixAddWithIndicator"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (!pixa) return ERROR_INT("pixa not defined", procName, 1); if (!na) return ERROR_INT("na not defined", procName, 1); n = pixaGetCount(pixa); if (n != numaGetCount(na)) return ERROR_INT("pixa and na sizes not equal", procName, 1); for (i = 0; i < n; i++) { numaGetIValue(na, i, &ival); if (ival == 1) { pix = pixaGetPix(pixa, i, L_CLONE); box = pixaGetBox(pixa, i, L_CLONE); boxGetGeometry(box, &x, &y, &w, &h); pixRasterop(pixs, x, y, w, h, PIX_SRC | PIX_DST, pix, 0, 0); boxDestroy(&box); pixDestroy(&pix); } } return 0; } /*! * \brief pixaSelectWithString() * * \param[in] pixas * \param[in] str string of indices into pixa, giving the pix to * be selected * \param[out] perror [optional] 1 if any indices are invalid; * 0 if all indices are valid * \return pixad, or NULL on error * *
 * Notes:
 *      (1) Returns a pixa with copies of selected pix.
 *      (2) Associated boxes are also copied, if fully populated.
 * 
*/ PIXA * pixaSelectWithString(PIXA *pixas, const char *str, l_int32 *perror) { l_int32 i, nval, npix, nbox, val, imaxval; l_float32 maxval; BOX *box; NUMA *na; PIX *pix1; PIXA *pixad; PROCNAME("pixaSelectWithString"); if (perror) *perror = 0; if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (!str) return (PIXA *)ERROR_PTR("str not defined", procName, NULL); if ((na = numaCreateFromString(str)) == NULL) return (PIXA *)ERROR_PTR("na not made", procName, NULL); if ((nval = numaGetCount(na)) == 0) { numaDestroy(&na); return (PIXA *)ERROR_PTR("no indices found", procName, NULL); } numaGetMax(na, &maxval, NULL); imaxval = (l_int32)(maxval + 0.1); nbox = pixaGetBoxaCount(pixas); npix = pixaGetCount(pixas); if (imaxval >= npix) { if (perror) *perror = 1; L_ERROR("max index = %d, size of pixa = %d\n", procName, imaxval, npix); } pixad = pixaCreate(nval); for (i = 0; i < nval; i++) { numaGetIValue(na, i, &val); if (val < 0 || val >= npix) { L_ERROR("index %d out of range of pix\n", procName, val); continue; } pix1 = pixaGetPix(pixas, val, L_COPY); pixaAddPix(pixad, pix1, L_INSERT); if (nbox == npix) { /* fully populated boxa */ box = pixaGetBox(pixas, val, L_COPY); pixaAddBox(pixad, box, L_INSERT); } } numaDestroy(&na); return pixad; } /*! * \brief pixaRenderComponent() * * \param[in] pixs [optional] 1 bpp pix * \param[in] pixa of 1 bpp connected components, one of which will * be rendered in pixs, with its origin determined * by the associated box. * \param[in] index of component to be rendered * \return pixd, or NULL on error * *
 * Notes:
 *      (1) If pixs is null, this generates an empty pix of a size determined
 *          by union of the component bounding boxes, and including the origin.
 *      (2) The selected component is blitted into pixs.
 * 
*/ PIX * pixaRenderComponent(PIX *pixs, PIXA *pixa, l_int32 index) { l_int32 n, x, y, w, h, same, maxd; BOX *box; BOXA *boxa; PIX *pix; PROCNAME("pixaRenderComponent"); if (!pixa) return (PIX *)ERROR_PTR("pixa not defined", procName, pixs); n = pixaGetCount(pixa); if (index < 0 || index >= n) return (PIX *)ERROR_PTR("invalid index", procName, pixs); if (pixs && (pixGetDepth(pixs) != 1)) return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixs); pixaVerifyDepth(pixa, &same, &maxd); if (maxd > 1) return (PIX *)ERROR_PTR("not all pix with d == 1", procName, pixs); boxa = pixaGetBoxa(pixa, L_CLONE); if (!pixs) { boxaGetExtent(boxa, &w, &h, NULL); pixs = pixCreate(w, h, 1); } pix = pixaGetPix(pixa, index, L_CLONE); box = boxaGetBox(boxa, index, L_CLONE); boxGetGeometry(box, &x, &y, &w, &h); pixRasterop(pixs, x, y, w, h, PIX_SRC | PIX_DST, pix, 0, 0); boxDestroy(&box); pixDestroy(&pix); boxaDestroy(&boxa); return pixs; } /*---------------------------------------------------------------------* * Sort functions * *---------------------------------------------------------------------*/ /*! * \brief pixaSort() * * \param[in] pixas * \param[in] sorttype L_SORT_BY_X, L_SORT_BY_Y, L_SORT_BY_WIDTH, * L_SORT_BY_HEIGHT, L_SORT_BY_MIN_DIMENSION, * L_SORT_BY_MAX_DIMENSION, L_SORT_BY_PERIMETER, * L_SORT_BY_AREA, L_SORT_BY_ASPECT_RATIO * \param[in] sortorder L_SORT_INCREASING, L_SORT_DECREASING * \param[out] pnaindex [optional] index of sorted order into * original array * \param[in] copyflag L_COPY, L_CLONE * \return pixad sorted version of pixas, or NULL on error * *
 * Notes:
 *      (1) This sorts based on the data in the boxa.  If the boxa
 *          count is not the same as the pixa count, this returns an error.
 *      (2) If the boxa is empty, it makes one corresponding to the
 *          dimensions of each pix, which allows meaningful sorting on
 *          all types except x and y.
 *      (3) The copyflag refers to the pix and box copies that are
 *          inserted into the sorted pixa.  These are either L_COPY
 *          or L_CLONE.
 * 
*/ PIXA * pixaSort(PIXA *pixas, l_int32 sorttype, l_int32 sortorder, NUMA **pnaindex, l_int32 copyflag) { l_int32 i, n, nb, x, y, w, h; BOXA *boxa; NUMA *na, *naindex; PIXA *pixad; PROCNAME("pixaSort"); if (pnaindex) *pnaindex = NULL; if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (sorttype != L_SORT_BY_X && sorttype != L_SORT_BY_Y && sorttype != L_SORT_BY_WIDTH && sorttype != L_SORT_BY_HEIGHT && sorttype != L_SORT_BY_MIN_DIMENSION && sorttype != L_SORT_BY_MAX_DIMENSION && sorttype != L_SORT_BY_PERIMETER && sorttype != L_SORT_BY_AREA && sorttype != L_SORT_BY_ASPECT_RATIO) return (PIXA *)ERROR_PTR("invalid sort type", procName, NULL); if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING) return (PIXA *)ERROR_PTR("invalid sort order", procName, NULL); if (copyflag != L_COPY && copyflag != L_CLONE) return (PIXA *)ERROR_PTR("invalid copy flag", procName, NULL); /* Check the pixa and boxa counts. Make a boxa if required. */ if ((n = pixaGetCount(pixas)) == 0) { L_INFO("no pix in pixa\n", procName); return pixaCopy(pixas, copyflag); } if ((boxa = pixas->boxa) == NULL) /* not owned; do not destroy */ return (PIXA *)ERROR_PTR("boxa not found!", procName, NULL); nb = boxaGetCount(boxa); if (nb == 0) { pixaSetFullSizeBoxa(pixas); nb = n; boxa = pixas->boxa; /* not owned */ if (sorttype == L_SORT_BY_X || sorttype == L_SORT_BY_Y) L_WARNING("sort by x or y where all values are 0\n", procName); } if (nb != n) return (PIXA *)ERROR_PTR("boxa and pixa counts differ", procName, NULL); /* Use O(n) binsort if possible */ if (n > MinCompsForBinSort && ((sorttype == L_SORT_BY_X) || (sorttype == L_SORT_BY_Y) || (sorttype == L_SORT_BY_WIDTH) || (sorttype == L_SORT_BY_HEIGHT) || (sorttype == L_SORT_BY_PERIMETER))) return pixaBinSort(pixas, sorttype, sortorder, pnaindex, copyflag); /* Build up numa of specific data */ if ((na = numaCreate(n)) == NULL) return (PIXA *)ERROR_PTR("na not made", procName, NULL); for (i = 0; i < n; i++) { boxaGetBoxGeometry(boxa, i, &x, &y, &w, &h); switch (sorttype) { case L_SORT_BY_X: numaAddNumber(na, x); break; case L_SORT_BY_Y: numaAddNumber(na, y); break; case L_SORT_BY_WIDTH: numaAddNumber(na, w); break; case L_SORT_BY_HEIGHT: numaAddNumber(na, h); break; case L_SORT_BY_MIN_DIMENSION: numaAddNumber(na, L_MIN(w, h)); break; case L_SORT_BY_MAX_DIMENSION: numaAddNumber(na, L_MAX(w, h)); break; case L_SORT_BY_PERIMETER: numaAddNumber(na, w + h); break; case L_SORT_BY_AREA: numaAddNumber(na, w * h); break; case L_SORT_BY_ASPECT_RATIO: numaAddNumber(na, (l_float32)w / (l_float32)h); break; default: L_WARNING("invalid sort type\n", procName); } } /* Get the sort index for data array */ naindex = numaGetSortIndex(na, sortorder); numaDestroy(&na); if (!naindex) return (PIXA *)ERROR_PTR("naindex not made", procName, NULL); /* Build up sorted pixa using sort index */ if ((pixad = pixaSortByIndex(pixas, naindex, copyflag)) == NULL) { numaDestroy(&naindex); return (PIXA *)ERROR_PTR("pixad not made", procName, NULL); } if (pnaindex) *pnaindex = naindex; else numaDestroy(&naindex); return pixad; } /*! * \brief pixaBinSort() * * \param[in] pixas * \param[in] sorttype L_SORT_BY_X, L_SORT_BY_Y, L_SORT_BY_WIDTH, * L_SORT_BY_HEIGHT, L_SORT_BY_PERIMETER * \param[in] sortorder L_SORT_INCREASING, L_SORT_DECREASING * \param[out] pnaindex [optional] index of sorted order into * original array * \param[in] copyflag L_COPY, L_CLONE * \return pixad sorted version of pixas, or NULL on error * *
 * Notes:
 *      (1) This sorts based on the data in the boxa.  If the boxa
 *          count is not the same as the pixa count, this returns an error.
 *      (2) The copyflag refers to the pix and box copies that are
 *          inserted into the sorted pixa.  These are either L_COPY
 *          or L_CLONE.
 *      (3) For a large number of boxes (say, greater than 1000), this
 *          O(n) binsort is much faster than the O(nlogn) shellsort.
 *          For 5000 components, this is over 20x faster than boxaSort().
 *      (4) Consequently, pixaSort() calls this function if it will
 *          likely go much faster.
 * 
*/ PIXA * pixaBinSort(PIXA *pixas, l_int32 sorttype, l_int32 sortorder, NUMA **pnaindex, l_int32 copyflag) { l_int32 i, n, x, y, w, h; BOXA *boxa; NUMA *na, *naindex; PIXA *pixad; PROCNAME("pixaBinSort"); if (pnaindex) *pnaindex = NULL; if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (sorttype != L_SORT_BY_X && sorttype != L_SORT_BY_Y && sorttype != L_SORT_BY_WIDTH && sorttype != L_SORT_BY_HEIGHT && sorttype != L_SORT_BY_PERIMETER) return (PIXA *)ERROR_PTR("invalid sort type", procName, NULL); if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING) return (PIXA *)ERROR_PTR("invalid sort order", procName, NULL); if (copyflag != L_COPY && copyflag != L_CLONE) return (PIXA *)ERROR_PTR("invalid copy flag", procName, NULL); /* Verify that the pixa and its boxa have the same count */ if ((boxa = pixas->boxa) == NULL) /* not owned; do not destroy */ return (PIXA *)ERROR_PTR("boxa not found", procName, NULL); n = pixaGetCount(pixas); if (boxaGetCount(boxa) != n) return (PIXA *)ERROR_PTR("boxa and pixa counts differ", procName, NULL); /* Generate Numa of appropriate box dimensions */ if ((na = numaCreate(n)) == NULL) return (PIXA *)ERROR_PTR("na not made", procName, NULL); for (i = 0; i < n; i++) { boxaGetBoxGeometry(boxa, i, &x, &y, &w, &h); switch (sorttype) { case L_SORT_BY_X: numaAddNumber(na, x); break; case L_SORT_BY_Y: numaAddNumber(na, y); break; case L_SORT_BY_WIDTH: numaAddNumber(na, w); break; case L_SORT_BY_HEIGHT: numaAddNumber(na, h); break; case L_SORT_BY_PERIMETER: numaAddNumber(na, w + h); break; default: L_WARNING("invalid sort type\n", procName); } } /* Get the sort index for data array */ naindex = numaGetBinSortIndex(na, sortorder); numaDestroy(&na); if (!naindex) return (PIXA *)ERROR_PTR("naindex not made", procName, NULL); /* Build up sorted pixa using sort index */ if ((pixad = pixaSortByIndex(pixas, naindex, copyflag)) == NULL) { numaDestroy(&naindex); return (PIXA *)ERROR_PTR("pixad not made", procName, NULL); } if (pnaindex) *pnaindex = naindex; else numaDestroy(&naindex); return pixad; } /*! * \brief pixaSortByIndex() * * \param[in] pixas * \param[in] naindex na that maps from the new pixa to the input pixa * \param[in] copyflag L_COPY, L_CLONE * \return pixad sorted, or NULL on error */ PIXA * pixaSortByIndex(PIXA *pixas, NUMA *naindex, l_int32 copyflag) { l_int32 i, n, index; BOX *box; PIX *pix; PIXA *pixad; PROCNAME("pixaSortByIndex"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (!naindex) return (PIXA *)ERROR_PTR("naindex not defined", procName, NULL); if (copyflag != L_CLONE && copyflag != L_COPY) return (PIXA *)ERROR_PTR("invalid copyflag", procName, NULL); n = pixaGetCount(pixas); pixad = pixaCreate(n); for (i = 0; i < n; i++) { numaGetIValue(naindex, i, &index); pix = pixaGetPix(pixas, index, copyflag); box = pixaGetBox(pixas, index, copyflag); pixaAddPix(pixad, pix, L_INSERT); pixaAddBox(pixad, box, L_INSERT); } return pixad; } /*! * \brief pixaSort2dByIndex() * * \param[in] pixas * \param[in] naa numaa that maps from the new pixaa to the input pixas * \param[in] copyflag L_CLONE or L_COPY * \return paa sorted, or NULL on error */ PIXAA * pixaSort2dByIndex(PIXA *pixas, NUMAA *naa, l_int32 copyflag) { l_int32 pixtot, ntot, i, j, n, nn, index; BOX *box; NUMA *na; PIX *pix; PIXA *pixa; PIXAA *paa; PROCNAME("pixaSort2dByIndex"); if (!pixas) return (PIXAA *)ERROR_PTR("pixas not defined", procName, NULL); if (!naa) return (PIXAA *)ERROR_PTR("naindex not defined", procName, NULL); /* Check counts */ ntot = numaaGetNumberCount(naa); pixtot = pixaGetCount(pixas); if (ntot != pixtot) return (PIXAA *)ERROR_PTR("element count mismatch", procName, NULL); n = numaaGetCount(naa); paa = pixaaCreate(n); for (i = 0; i < n; i++) { na = numaaGetNuma(naa, i, L_CLONE); nn = numaGetCount(na); pixa = pixaCreate(nn); for (j = 0; j < nn; j++) { numaGetIValue(na, j, &index); pix = pixaGetPix(pixas, index, copyflag); box = pixaGetBox(pixas, index, copyflag); pixaAddPix(pixa, pix, L_INSERT); pixaAddBox(pixa, box, L_INSERT); } pixaaAddPixa(paa, pixa, L_INSERT); numaDestroy(&na); } return paa; } /*---------------------------------------------------------------------* * Pixa and Pixaa range selection * *---------------------------------------------------------------------*/ /*! * \brief pixaSelectRange() * * \param[in] pixas * \param[in] first use 0 to select from the beginning * \param[in] last use -1 to select to the end * \param[in] copyflag L_COPY, L_CLONE * \return pixad, or NULL on error * *
 * Notes:
 *      (1) The copyflag specifies what we do with each pix from pixas.
 *          Specifically, L_CLONE inserts a clone into pixad of each
 *          selected pix from pixas.
 * 
*/ PIXA * pixaSelectRange(PIXA *pixas, l_int32 first, l_int32 last, l_int32 copyflag) { l_int32 n, npix, i; PIX *pix; PIXA *pixad; PROCNAME("pixaSelectRange"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (copyflag != L_COPY && copyflag != L_CLONE) return (PIXA *)ERROR_PTR("invalid copyflag", procName, NULL); n = pixaGetCount(pixas); first = L_MAX(0, first); if (last < 0) last = n - 1; if (first >= n) return (PIXA *)ERROR_PTR("invalid first", procName, NULL); if (last >= n) { L_WARNING("last = %d is beyond max index = %d; adjusting\n", procName, last, n - 1); last = n - 1; } if (first > last) return (PIXA *)ERROR_PTR("first > last", procName, NULL); npix = last - first + 1; pixad = pixaCreate(npix); for (i = first; i <= last; i++) { pix = pixaGetPix(pixas, i, copyflag); pixaAddPix(pixad, pix, L_INSERT); } return pixad; } /*! * \brief pixaaSelectRange() * * \param[in] paas * \param[in] first use 0 to select from the beginning * \param[in] last use -1 to select to the end * \param[in] copyflag L_COPY, L_CLONE * \return paad, or NULL on error * *
 * Notes:
 *      (1) The copyflag specifies what we do with each pixa from paas.
 *          Specifically, L_CLONE inserts a clone into paad of each
 *          selected pixa from paas.
 * 
*/ PIXAA * pixaaSelectRange(PIXAA *paas, l_int32 first, l_int32 last, l_int32 copyflag) { l_int32 n, npixa, i; PIXA *pixa; PIXAA *paad; PROCNAME("pixaaSelectRange"); if (!paas) return (PIXAA *)ERROR_PTR("paas not defined", procName, NULL); if (copyflag != L_COPY && copyflag != L_CLONE) return (PIXAA *)ERROR_PTR("invalid copyflag", procName, NULL); n = pixaaGetCount(paas, NULL); first = L_MAX(0, first); if (last < 0) last = n - 1; if (first >= n) return (PIXAA *)ERROR_PTR("invalid first", procName, NULL); if (last >= n) { L_WARNING("last = %d is beyond max index = %d; adjusting\n", procName, last, n - 1); last = n - 1; } if (first > last) return (PIXAA *)ERROR_PTR("first > last", procName, NULL); npixa = last - first + 1; paad = pixaaCreate(npixa); for (i = first; i <= last; i++) { pixa = pixaaGetPixa(paas, i, copyflag); pixaaAddPixa(paad, pixa, L_INSERT); } return paad; } /*---------------------------------------------------------------------* * Pixa and Pixaa scaling * *---------------------------------------------------------------------*/ /*! * \brief pixaaScaleToSize() * * \param[in] paas * \param[in] wd target width; use 0 if using height as target * \param[in] hd target height; use 0 if using width as target * \return paad, or NULL on error * *
 * Notes:
 *      (1) This guarantees that each output scaled image has the
 *          dimension(s) you specify.
 *           ~ To specify the width with isotropic scaling, set %hd = 0.
 *           ~ To specify the height with isotropic scaling, set %wd = 0.
 *           ~ If both %wd and %hd are specified, the image is scaled
 *             (in general, anisotropically) to that size.
 *           ~ It is an error to set both %wd and %hd to 0.
 * 
*/ PIXAA * pixaaScaleToSize(PIXAA *paas, l_int32 wd, l_int32 hd) { l_int32 n, i; PIXA *pixa1, *pixa2; PIXAA *paad; PROCNAME("pixaaScaleToSize"); if (!paas) return (PIXAA *)ERROR_PTR("paas not defined", procName, NULL); if (wd <= 0 && hd <= 0) return (PIXAA *)ERROR_PTR("neither wd nor hd > 0", procName, NULL); n = pixaaGetCount(paas, NULL); paad = pixaaCreate(n); for (i = 0; i < n; i++) { pixa1 = pixaaGetPixa(paas, i, L_CLONE); pixa2 = pixaScaleToSize(pixa1, wd, hd); pixaaAddPixa(paad, pixa2, L_INSERT); pixaDestroy(&pixa1); } return paad; } /*! * \brief pixaaScaleToSizeVar() * * \param[in] paas * \param[in] nawd [optional] target widths; use NULL if using height * \param[in] nahd [optional] target height; use NULL if using width * \return paad, or NULL on error * *
 * Notes:
 *      (1) This guarantees that the scaled images in each pixa have the
 *          dimension(s) you specify in the numas.
 *           ~ To specify the width with isotropic scaling, set %nahd = NULL.
 *           ~ To specify the height with isotropic scaling, set %nawd = NULL.
 *           ~ If both %nawd and %nahd are specified, the image is scaled
 *             (in general, anisotropically) to that size.
 *           ~ It is an error to set both %nawd and %nahd to NULL.
 *      (2) If either nawd and/or nahd is defined, it must have the same
 *          count as the number of pixa in paas.
 * 
*/ PIXAA * pixaaScaleToSizeVar(PIXAA *paas, NUMA *nawd, NUMA *nahd) { l_int32 n, i, wd, hd; PIXA *pixa1, *pixa2; PIXAA *paad; PROCNAME("pixaaScaleToSizeVar"); if (!paas) return (PIXAA *)ERROR_PTR("paas not defined", procName, NULL); if (!nawd && !nahd) return (PIXAA *)ERROR_PTR("!nawd && !nahd", procName, NULL); n = pixaaGetCount(paas, NULL); if (nawd && (n != numaGetCount(nawd))) return (PIXAA *)ERROR_PTR("nawd wrong size", procName, NULL); if (nahd && (n != numaGetCount(nahd))) return (PIXAA *)ERROR_PTR("nahd wrong size", procName, NULL); paad = pixaaCreate(n); for (i = 0; i < n; i++) { wd = hd = 0; if (nawd) numaGetIValue(nawd, i, &wd); if (nahd) numaGetIValue(nahd, i, &hd); pixa1 = pixaaGetPixa(paas, i, L_CLONE); pixa2 = pixaScaleToSize(pixa1, wd, hd); pixaaAddPixa(paad, pixa2, L_INSERT); pixaDestroy(&pixa1); } return paad; } /*! * \brief pixaScaleToSize() * * \param[in] pixas * \param[in] wd target width; use 0 if using height as target * \param[in] hd target height; use 0 if using width as target * \return pixad, or NULL on error * *
 * Notes:
 *      (1) See pixaaScaleToSize()
 * 
*/ PIXA * pixaScaleToSize(PIXA *pixas, l_int32 wd, l_int32 hd) { l_int32 n, i; PIX *pix1, *pix2; PIXA *pixad; PROCNAME("pixaScaleToSize"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (wd <= 0 && hd <= 0) /* no scaling requested */ return pixaCopy(pixas, L_CLONE); n = pixaGetCount(pixas); pixad = pixaCreate(n); for (i = 0; i < n; i++) { pix1 = pixaGetPix(pixas, i, L_CLONE); pix2 = pixScaleToSize(pix1, wd, hd); pixCopyText(pix2, pix1); pixaAddPix(pixad, pix2, L_INSERT); pixDestroy(&pix1); } return pixad; } /*! * \brief pixaScaleToSizeRel() * * \param[in] pixas * \param[in] delw change in width, in pixels; 0 means no change * \param[in] delh change in height, in pixels; 0 means no change * return pixad, or NULL on error * *
 * Notes:
 *      (1) If a requested change in a pix is not possible because
 *          either the requested width or height is <= 0, issue a
 *          warning and return a copy.
 * 
*/ PIXA * pixaScaleToSizeRel(PIXA *pixas, l_int32 delw, l_int32 delh) { l_int32 n, i; PIX *pix1, *pix2; PIXA *pixad; PROCNAME("pixaScaleToSizeRel"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); n = pixaGetCount(pixas); pixad = pixaCreate(n); for (i = 0; i < n; i++) { pix1 = pixaGetPix(pixas, i, L_CLONE); pix2 = pixScaleToSizeRel(pix1, delw, delh); if (pix2) { pixaAddPix(pixad, pix2, L_INSERT); } else { L_WARNING("relative scale to size failed; use a copy\n", procName); pixaAddPix(pixad, pix1, L_COPY); } pixDestroy(&pix1); } return pixad; } /*! * \brief pixaScale() * * \param[in] pixas * \param[in] scalex * \param[in] scaley * \return pixad, or NULL on error * *
 * Notes:
 *      (1) If pixas has a full boxes, it is scaled as well.
 * 
*/ PIXA * pixaScale(PIXA *pixas, l_float32 scalex, l_float32 scaley) { l_int32 i, n, nb; BOXA *boxa1, *boxa2; PIX *pix1, *pix2; PIXA *pixad; PROCNAME("pixaScale"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (scalex <= 0.0 || scaley <= 0.0) return (PIXA *)ERROR_PTR("invalid scaling parameters", procName, NULL); n = pixaGetCount(pixas); pixad = pixaCreate(n); for (i = 0; i < n; i++) { pix1 = pixaGetPix(pixas, i, L_CLONE); pix2 = pixScale(pix1, scalex, scaley); pixCopyText(pix2, pix1); pixaAddPix(pixad, pix2, L_INSERT); pixDestroy(&pix1); } boxa1 = pixaGetBoxa(pixas, L_CLONE); nb = boxaGetCount(boxa1); if (nb == n) { boxa2 = boxaTransform(boxa1, 0, 0, scalex, scaley); pixaSetBoxa(pixad, boxa2, L_INSERT); } boxaDestroy(&boxa1); return pixad; } /*! * \brief pixaScaleBySampling() * * \param[in] pixas * \param[in] scalex * \param[in] scaley * \return pixad, or NULL on error * *
 * Notes:
 *      (1) If pixas has a full boxes, it is scaled as well.
 * 
*/ PIXA * pixaScaleBySampling(PIXA *pixas, l_float32 scalex, l_float32 scaley) { l_int32 i, n, nb; BOXA *boxa1, *boxa2; PIX *pix1, *pix2; PIXA *pixad; PROCNAME("pixaScaleBySampling"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (scalex <= 0.0 || scaley <= 0.0) return (PIXA *)ERROR_PTR("invalid scaling parameters", procName, NULL); n = pixaGetCount(pixas); pixad = pixaCreate(n); for (i = 0; i < n; i++) { pix1 = pixaGetPix(pixas, i, L_CLONE); pix2 = pixScaleBySampling(pix1, scalex, scaley); pixCopyText(pix2, pix1); pixaAddPix(pixad, pix2, L_INSERT); pixDestroy(&pix1); } boxa1 = pixaGetBoxa(pixas, L_CLONE); nb = boxaGetCount(boxa1); if (nb == n) { boxa2 = boxaTransform(boxa1, 0, 0, scalex, scaley); pixaSetBoxa(pixad, boxa2, L_INSERT); } boxaDestroy(&boxa1); return pixad; } /*---------------------------------------------------------------------* * Pixa rotation and translation * *---------------------------------------------------------------------*/ /*! * \brief pixaRotate() * * \param[in] pixas 1, 2, 4, 8, 32 bpp rgb * \param[in] angle rotation angle in radians; clockwise is positive * \param[in] type L_ROTATE_AREA_MAP, L_ROTATE_SHEAR, L_ROTATE_SAMPLING * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK * \param[in] width original width; use 0 to avoid embedding * \param[in] height original height; use 0 to avoid embedding * \return pixad, or NULL on error * *
 * Notes:
 *      (1) Each pix is rotated about its center.  See pixRotate() for details.
 *      (2) The boxa array is copied.  Why is it not rotated?
 *          If a boxa exists, the array of boxes is in 1-to-1
 *          correspondence with the array of pix, and each box typically
 *          represents the location of the pix relative to an image from
 *          which it has been extracted.  Like the pix, we could rotate
 *          each box around its center, and then generate a box that
 *          contains all four corners, as is done in boxaRotate(), but
 *          this seems unnecessary.
 * 
*/ PIXA * pixaRotate(PIXA *pixas, l_float32 angle, l_int32 type, l_int32 incolor, l_int32 width, l_int32 height) { l_int32 i, n; BOXA *boxa; PIX *pixs, *pixd; PIXA *pixad; PROCNAME("pixaRotate"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (type != L_ROTATE_SHEAR && type != L_ROTATE_AREA_MAP && type != L_ROTATE_SAMPLING) return (PIXA *)ERROR_PTR("invalid type", procName, NULL); if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK) return (PIXA *)ERROR_PTR("invalid incolor", procName, NULL); if (L_ABS(angle) < MinAngleToRotate) return pixaCopy(pixas, L_COPY); n = pixaGetCount(pixas); if ((pixad = pixaCreate(n)) == NULL) return (PIXA *)ERROR_PTR("pixad not made", procName, NULL); boxa = pixaGetBoxa(pixad, L_COPY); pixaSetBoxa(pixad, boxa, L_INSERT); for (i = 0; i < n; i++) { if ((pixs = pixaGetPix(pixas, i, L_CLONE)) == NULL) { pixaDestroy(&pixad); return (PIXA *)ERROR_PTR("pixs not found", procName, NULL); } pixd = pixRotate(pixs, angle, type, incolor, width, height); pixaAddPix(pixad, pixd, L_INSERT); pixDestroy(&pixs); } return pixad; } /*! * \brief pixaRotateOrth() * * \param[in] pixas * \param[in] rotation 0 = noop, 1 = 90 deg, 2 = 180 deg, 3 = 270 deg; * all rotations are clockwise * \return pixad, or NULL on error * *
 * Notes:
 *      (1) Rotates each pix in the pixa.  Rotates and saves the boxes in
 *          the boxa if the boxa is full.
 * 
*/ PIXA * pixaRotateOrth(PIXA *pixas, l_int32 rotation) { l_int32 i, n, nb, w, h; BOX *boxs, *boxd; PIX *pixs, *pixd; PIXA *pixad; PROCNAME("pixaRotateOrth"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (rotation < 0 || rotation > 3) return (PIXA *)ERROR_PTR("rotation not in {0,1,2,3}", procName, NULL); if (rotation == 0) return pixaCopy(pixas, L_COPY); n = pixaGetCount(pixas); nb = pixaGetBoxaCount(pixas); if ((pixad = pixaCreate(n)) == NULL) return (PIXA *)ERROR_PTR("pixad not made", procName, NULL); for (i = 0; i < n; i++) { if ((pixs = pixaGetPix(pixas, i, L_CLONE)) == NULL) { pixaDestroy(&pixad); return (PIXA *)ERROR_PTR("pixs not found", procName, NULL); } pixd = pixRotateOrth(pixs, rotation); pixaAddPix(pixad, pixd, L_INSERT); if (n == nb) { boxs = pixaGetBox(pixas, i, L_COPY); pixGetDimensions(pixs, &w, &h, NULL); boxd = boxRotateOrth(boxs, w, h, rotation); pixaAddBox(pixad, boxd, L_INSERT); boxDestroy(&boxs); } pixDestroy(&pixs); } return pixad; } /*! * \brief pixaTranslate() * * \param[in] pixas * \param[in] hshift horizontal shift; hshift > 0 is to right * \param[in] vshift vertical shift; vshift > 0 is down * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK * \return pixad, or NULL on error. */ PIXA * pixaTranslate(PIXA *pixas, l_int32 hshift, l_int32 vshift, l_int32 incolor) { l_int32 i, n, nb; BOXA *boxas, *boxad; PIX *pixs, *pixd; PIXA *pixad; PROCNAME("pixaTranslate"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (hshift == 0 && vshift == 0) return pixaCopy(pixas, L_COPY); n = pixaGetCount(pixas); nb = pixaGetBoxaCount(pixas); if ((pixad = pixaCreate(n)) == NULL) return (PIXA *)ERROR_PTR("pixad not made", procName, NULL); for (i = 0; i < n; i++) { if ((pixs = pixaGetPix(pixas, i, L_CLONE)) == NULL) { pixaDestroy(&pixad); return (PIXA *)ERROR_PTR("pixs not found", procName, NULL); } pixd = pixTranslate(NULL, pixs, hshift, vshift, incolor); pixaAddPix(pixad, pixd, L_INSERT); pixDestroy(&pixs); } if (n == nb) { boxas = pixaGetBoxa(pixas, L_CLONE); boxad = boxaTransform(boxas, hshift, vshift, 1.0, 1.0); pixaSetBoxa(pixad, boxad, L_INSERT); boxaDestroy(&boxas); } return pixad; } /*---------------------------------------------------------------------* * Miscellaneous functions * *---------------------------------------------------------------------*/ /*! * \brief pixaAddBorderGeneral() * * \param[in] pixad can be null or equal to pixas * \param[in] pixas containing pix of all depths; colormap ok * \param[in] left, right, top, bot number of pixels added * \param[in] val value of added border pixels * \return pixad with border added to each pix, including on error * *
 * Notes:
 *      (1) For binary images:
 *             white:  val = 0
 *             black:  val = 1
 *          For grayscale images:
 *             white:  val = 2 ** d - 1
 *             black:  val = 0
 *          For rgb color images:
 *             white:  val = 0xffffff00
 *             black:  val = 0
 *          For colormapped images, use 'index' found this way:
 *             white: pixcmapGetRankIntensity(cmap, 1.0, &index);
 *             black: pixcmapGetRankIntensity(cmap, 0.0, &index);
 *      (2) For in-place replacement of each pix with a bordered version,
 *          use %pixad = %pixas.  To make a new pixa, use %pixad = NULL.
 *      (3) In both cases, the boxa has sides adjusted as if it were
 *          expanded by the border.
 * 
*/ PIXA * pixaAddBorderGeneral(PIXA *pixad, PIXA *pixas, l_int32 left, l_int32 right, l_int32 top, l_int32 bot, l_uint32 val) { l_int32 i, n, nbox; BOX *box; BOXA *boxad; PIX *pixs, *pixd; PROCNAME("pixaAddBorderGeneral"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, pixad); if (left < 0 || right < 0 || top < 0 || bot < 0) return (PIXA *)ERROR_PTR("negative border added!", procName, pixad); if (pixad && (pixad != pixas)) return (PIXA *)ERROR_PTR("pixad defined but != pixas", procName, pixad); n = pixaGetCount(pixas); if (!pixad) pixad = pixaCreate(n); for (i = 0; i < n; i++) { pixs = pixaGetPix(pixas, i, L_CLONE); pixd = pixAddBorderGeneral(pixs, left, right, top, bot, val); if (pixad == pixas) /* replace */ pixaReplacePix(pixad, i, pixd, NULL); else pixaAddPix(pixad, pixd, L_INSERT); pixDestroy(&pixs); } nbox = pixaGetBoxaCount(pixas); boxad = pixaGetBoxa(pixad, L_CLONE); for (i = 0; i < nbox; i++) { if ((box = pixaGetBox(pixas, i, L_COPY)) == NULL) { L_WARNING("box %d not found\n", procName, i); break; } boxAdjustSides(box, box, -left, right, -top, bot); if (pixad == pixas) /* replace */ boxaReplaceBox(boxad, i, box); else boxaAddBox(boxad, box, L_INSERT); } boxaDestroy(&boxad); return pixad; } /*! * \brief pixaaFlattenToPixa() * * \param[in] paa * \param[out] pnaindex [optional] the pixa index in the pixaa * \param[in] copyflag L_COPY or L_CLONE * \return pixa, or NULL on error * *
 * Notes:
 *      (1) This 'flattens' the pixaa to a pixa, taking the pix in
 *          order in the first pixa, then the second, etc.
 *      (2) If &naindex is defined, we generate a Numa that gives, for
 *          each pix in the pixaa, the index of the pixa to which it belongs.
 * 
*/ PIXA * pixaaFlattenToPixa(PIXAA *paa, NUMA **pnaindex, l_int32 copyflag) { l_int32 i, j, m, mb, n; BOX *box; NUMA *naindex; PIX *pix; PIXA *pixa, *pixat; PROCNAME("pixaaFlattenToPixa"); if (pnaindex) *pnaindex = NULL; if (!paa) return (PIXA *)ERROR_PTR("paa not defined", procName, NULL); if (copyflag != L_COPY && copyflag != L_CLONE) return (PIXA *)ERROR_PTR("invalid copyflag", procName, NULL); if (pnaindex) { naindex = numaCreate(0); *pnaindex = naindex; } n = pixaaGetCount(paa, NULL); pixa = pixaCreate(n); for (i = 0; i < n; i++) { pixat = pixaaGetPixa(paa, i, L_CLONE); m = pixaGetCount(pixat); mb = pixaGetBoxaCount(pixat); for (j = 0; j < m; j++) { pix = pixaGetPix(pixat, j, copyflag); pixaAddPix(pixa, pix, L_INSERT); if (j < mb) { box = pixaGetBox(pixat, j, copyflag); pixaAddBox(pixa, box, L_INSERT); } if (pnaindex) numaAddNumber(naindex, i); /* save 'row' number */ } pixaDestroy(&pixat); } return pixa; } /*! * \brief pixaaSizeRange() * * \param[in] paa * \param[out] pminw, pminh, pmaxw, pmaxh [optional] range of * dimensions of all boxes * \return 0 if OK, 1 on error */ l_ok pixaaSizeRange(PIXAA *paa, l_int32 *pminw, l_int32 *pminh, l_int32 *pmaxw, l_int32 *pmaxh) { l_int32 minw, minh, maxw, maxh, minpw, minph, maxpw, maxph, i, n; PIXA *pixa; PROCNAME("pixaaSizeRange"); if (pminw) *pminw = 0; if (pminh) *pminh = 0; if (pmaxw) *pmaxw = 0; if (pmaxh) *pmaxh = 0; if (!paa) return ERROR_INT("paa not defined", procName, 1); if (!pminw && !pmaxw && !pminh && !pmaxh) return ERROR_INT("no data can be returned", procName, 1); minw = minh = 100000000; maxw = maxh = 0; n = pixaaGetCount(paa, NULL); for (i = 0; i < n; i++) { pixa = pixaaGetPixa(paa, i, L_CLONE); pixaSizeRange(pixa, &minpw, &minph, &maxpw, &maxph); if (minpw < minw) minw = minpw; if (minph < minh) minh = minph; if (maxpw > maxw) maxw = maxpw; if (maxph > maxh) maxh = maxph; pixaDestroy(&pixa); } if (pminw) *pminw = minw; if (pminh) *pminh = minh; if (pmaxw) *pmaxw = maxw; if (pmaxh) *pmaxh = maxh; return 0; } /*! * \brief pixaSizeRange() * * \param[in] pixa * \param[out] pminw, pminh, pmaxw, pmaxh [optional] range of * dimensions of pix in the array * \return 0 if OK, 1 on error */ l_ok pixaSizeRange(PIXA *pixa, l_int32 *pminw, l_int32 *pminh, l_int32 *pmaxw, l_int32 *pmaxh) { l_int32 minw, minh, maxw, maxh, i, n, w, h; PIX *pix; PROCNAME("pixaSizeRange"); if (pminw) *pminw = 0; if (pminh) *pminh = 0; if (pmaxw) *pmaxw = 0; if (pmaxh) *pmaxh = 0; if (!pixa) return ERROR_INT("pixa not defined", procName, 1); if (!pminw && !pmaxw && !pminh && !pmaxh) return ERROR_INT("no data can be returned", procName, 1); minw = minh = 1000000; maxw = maxh = 0; n = pixaGetCount(pixa); for (i = 0; i < n; i++) { pix = pixaGetPix(pixa, i, L_CLONE); w = pixGetWidth(pix); h = pixGetHeight(pix); if (w < minw) minw = w; if (h < minh) minh = h; if (w > maxw) maxw = w; if (h > maxh) maxh = h; pixDestroy(&pix); } if (pminw) *pminw = minw; if (pminh) *pminh = minh; if (pmaxw) *pmaxw = maxw; if (pmaxh) *pmaxh = maxh; return 0; } /*! * \brief pixaClipToPix() * * \param[in] pixas * \param[in] pixs * \return pixad, or NULL on error * *
 * Notes:
 *      (1) This is intended for use in situations where pixas
 *          was originally generated from the input pixs.
 *      (2) Returns a pixad where each pix in pixas is ANDed
 *          with its associated region of the input pixs.  This
 *          region is specified by the the box that is associated
 *          with the pix.
 *      (3) In a typical application of this function, pixas has
 *          a set of region masks, so this generates a pixa of
 *          the parts of pixs that correspond to each region
 *          mask component, along with the bounding box for
 *          the region.
 * 
*/ PIXA * pixaClipToPix(PIXA *pixas, PIX *pixs) { l_int32 i, n; BOX *box; PIX *pix, *pixc; PIXA *pixad; PROCNAME("pixaClipToPix"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (!pixs) return (PIXA *)ERROR_PTR("pixs not defined", procName, NULL); n = pixaGetCount(pixas); if ((pixad = pixaCreate(n)) == NULL) return (PIXA *)ERROR_PTR("pixad not made", procName, NULL); for (i = 0; i < n; i++) { pix = pixaGetPix(pixas, i, L_CLONE); box = pixaGetBox(pixas, i, L_COPY); pixc = pixClipRectangle(pixs, box, NULL); pixAnd(pixc, pixc, pix); pixaAddPix(pixad, pixc, L_INSERT); pixaAddBox(pixad, box, L_INSERT); pixDestroy(&pix); } return pixad; } /*! * \brief pixaClipToForeground() * * \param[in] pixas * \param[out] ppixad [optional] pixa of clipped pix returned * \param[out] pboxa [optional] clipping boxes returned * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) At least one of [&pixd, &boxa] must be specified.
 *      (2) Any pix with no fg pixels is skipped.
 *      (3) See pixClipToForeground().
 * 
*/ l_ok pixaClipToForeground(PIXA *pixas, PIXA **ppixad, BOXA **pboxa) { l_int32 i, n; BOX *box1; PIX *pix1, *pix2; PROCNAME("pixaClipToForeground"); if (ppixad) *ppixad = NULL; if (pboxa) *pboxa = NULL; if (!pixas) return ERROR_INT("pixas not defined", procName, 1); if (!ppixad && !pboxa) return ERROR_INT("no output requested", procName, 1); n = pixaGetCount(pixas); if (ppixad) *ppixad = pixaCreate(n); if (pboxa) *pboxa = boxaCreate(n); for (i = 0; i < n; i++) { pix1 = pixaGetPix(pixas, i, L_CLONE); pixClipToForeground(pix1, &pix2, &box1); pixDestroy(&pix1); if (ppixad) pixaAddPix(*ppixad, pix2, L_INSERT); else pixDestroy(&pix2); if (pboxa) boxaAddBox(*pboxa, box1, L_INSERT); else boxDestroy(&box1); } return 0; } /*! * \brief pixaGetRenderingDepth() * * \param[in] pixa * \param[out] pdepth depth required to render if all colormaps are removed * \return 0 if OK; 1 on error */ l_ok pixaGetRenderingDepth(PIXA *pixa, l_int32 *pdepth) { l_int32 hascolor, maxdepth; PROCNAME("pixaGetRenderingDepth"); if (!pdepth) return ERROR_INT("&depth not defined", procName, 1); *pdepth = 0; if (!pixa) return ERROR_INT("pixa not defined", procName, 1); pixaHasColor(pixa, &hascolor); if (hascolor) { *pdepth = 32; return 0; } pixaGetDepthInfo(pixa, &maxdepth, NULL); if (maxdepth == 1) *pdepth = 1; else /* 2, 4, 8 or 16 */ *pdepth = 8; return 0; } /*! * \brief pixaHasColor() * * \param[in] pixa * \param[out] phascolor 1 if any pix is rgb or has a colormap with color; * 0 otherwise * \return 0 if OK; 1 on error */ l_ok pixaHasColor(PIXA *pixa, l_int32 *phascolor) { l_int32 i, n, hascolor, d; PIX *pix; PIXCMAP *cmap; PROCNAME("pixaHasColor"); if (!phascolor) return ERROR_INT("&hascolor not defined", procName, 1); *phascolor = 0; if (!pixa) return ERROR_INT("pixa not defined", procName, 1); n = pixaGetCount(pixa); hascolor = 0; for (i = 0; i < n; i++) { pix = pixaGetPix(pixa, i, L_CLONE); if ((cmap = pixGetColormap(pix)) != NULL) pixcmapHasColor(cmap, &hascolor); d = pixGetDepth(pix); pixDestroy(&pix); if (d == 32 || hascolor == 1) { *phascolor = 1; break; } } return 0; } /*! * \brief pixaAnyColormaps() * * \param[in] pixa * \param[out] phascmap 1 if any pix has a colormap; 0 otherwise * \return 0 if OK; 1 on error */ l_ok pixaAnyColormaps(PIXA *pixa, l_int32 *phascmap) { l_int32 i, n; PIX *pix; PIXCMAP *cmap; PROCNAME("pixaAnyColormaps"); if (!phascmap) return ERROR_INT("&hascmap not defined", procName, 1); *phascmap = 0; if (!pixa) return ERROR_INT("pixa not defined", procName, 1); n = pixaGetCount(pixa); for (i = 0; i < n; i++) { pix = pixaGetPix(pixa, i, L_CLONE); cmap = pixGetColormap(pix); pixDestroy(&pix); if (cmap) { *phascmap = 1; return 0; } } return 0; } /*! * \brief pixaGetDepthInfo() * * \param[in] pixa * \param[out] pmaxdepth [optional] max pixel depth of pix in pixa * \param[out] psame [optional] true if all depths are equal * \return 0 if OK; 1 on error */ l_ok pixaGetDepthInfo(PIXA *pixa, l_int32 *pmaxdepth, l_int32 *psame) { l_int32 i, n, d, d0; l_int32 maxd, same; /* depth info */ PROCNAME("pixaGetDepthInfo"); if (pmaxdepth) *pmaxdepth = 0; if (psame) *psame = TRUE; if (!pmaxdepth && !psame) return 0; if (!pixa) return ERROR_INT("pixa not defined", procName, 1); if ((n = pixaGetCount(pixa)) == 0) return ERROR_INT("pixa is empty", procName, 1); same = TRUE; maxd = 0; for (i = 0; i < n; i++) { pixaGetPixDimensions(pixa, i, NULL, NULL, &d); if (i == 0) d0 = d; else if (d != d0) same = FALSE; if (d > maxd) maxd = d; } if (pmaxdepth) *pmaxdepth = maxd; if (psame) *psame = same; return 0; } /*! * \brief pixaConvertToSameDepth() * * \param[in] pixas * \return pixad, or NULL on error * *
 * Notes:
 *      (1) If any pix has a colormap, they are all converted to rgb.
 *          Otherwise, they are all converted to the maximum depth of
 *          all the pix.
 *      (2) This can be used to allow lossless rendering onto a single pix.
 * 
*/ PIXA * pixaConvertToSameDepth(PIXA *pixas) { l_int32 i, n, same, hascmap, maxdepth; BOXA *boxa; PIX *pix1, *pix2; PIXA *pixa1, *pixad; PROCNAME("pixaConvertToSameDepth"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); /* Remove colormaps to rgb */ if ((n = pixaGetCount(pixas)) == 0) return (PIXA *)ERROR_PTR("no components", procName, NULL); pixaAnyColormaps(pixas, &hascmap); if (hascmap) { pixa1 = pixaCreate(n); for (i = 0; i < n; i++) { pix1 = pixaGetPix(pixas, i, L_CLONE); pix2 = pixConvertTo32(pix1); pixaAddPix(pixa1, pix2, L_INSERT); pixDestroy(&pix1); } } else { pixa1 = pixaCopy(pixas, L_CLONE); } pixaGetDepthInfo(pixa1, &maxdepth, &same); if (!same) { /* at least one pix has depth < maxdepth */ pixad = pixaCreate(n); for (i = 0; i < n; i++) { pix1 = pixaGetPix(pixa1, i, L_CLONE); if (maxdepth <= 8) pix2 = pixConvertTo8(pix1, 0); else pix2 = pixConvertTo32(pix1); pixaAddPix(pixad, pix2, L_INSERT); pixDestroy(&pix1); } } else { pixad = pixaCopy(pixa1, L_CLONE); } boxa = pixaGetBoxa(pixas, L_COPY); pixaSetBoxa(pixad, boxa, L_INSERT); pixaDestroy(&pixa1); return pixad; } /*! * \brief pixaEqual() * * \param[in] pixa1 * \param[in] pixa2 * \param[in] maxdist * \param[out] pnaindex [optional] index array of correspondences * \param[out] psame 1 if equal; 0 otherwise * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) The two pixa are the "same" if they contain the same
 *          boxa and the same ordered set of pix.  However, if they
 *          have boxa, the pix in each pixa can differ in ordering
 *          by an amount given by the parameter %maxdist.  If they
 *          don't have a boxa, the %maxdist parameter is ignored,
 *          and the ordering of the pix must be identical.
 *      (2) This applies only to boxa geometry, pixels and ordering;
 *          other fields in the pix are ignored.
 *      (3) naindex[i] gives the position of the box in pixa2 that
 *          corresponds to box i in pixa1.  It is only returned if the
 *          pixa have boxa and the boxa are equal.
 *      (4) In situations where the ordering is very different, so that
 *          a large %maxdist is required for "equality", this should be
 *          implemented with a hash function for efficiency.
 * 
*/ l_ok pixaEqual(PIXA *pixa1, PIXA *pixa2, l_int32 maxdist, NUMA **pnaindex, l_int32 *psame) { l_int32 i, j, n, empty1, empty2, same, sameboxa; BOXA *boxa1, *boxa2; NUMA *na; PIX *pix1, *pix2; PROCNAME("pixaEqual"); if (pnaindex) *pnaindex = NULL; if (!psame) return ERROR_INT("&same not defined", procName, 1); *psame = 0; sameboxa = 0; na = NULL; if (!pixa1 || !pixa2) return ERROR_INT("pixa1 and pixa2 not both defined", procName, 1); n = pixaGetCount(pixa1); if (n != pixaGetCount(pixa2)) return 0; /* If there are no boxes, strict ordering of the pix in each * pixa is required. */ boxa1 = pixaGetBoxa(pixa1, L_CLONE); boxa2 = pixaGetBoxa(pixa2, L_CLONE); empty1 = (boxaGetCount(boxa1) == 0) ? 1 : 0; empty2 = (boxaGetCount(boxa2) == 0) ? 1 : 0; if (!empty1 && !empty2) { boxaEqual(boxa1, boxa2, maxdist, &na, &sameboxa); if (!sameboxa) { boxaDestroy(&boxa1); boxaDestroy(&boxa2); numaDestroy(&na); return 0; } } boxaDestroy(&boxa1); boxaDestroy(&boxa2); if ((!empty1 && empty2) || (empty1 && !empty2)) return 0; for (i = 0; i < n; i++) { pix1 = pixaGetPix(pixa1, i, L_CLONE); if (na) numaGetIValue(na, i, &j); else j = i; pix2 = pixaGetPix(pixa2, j, L_CLONE); pixEqual(pix1, pix2, &same); pixDestroy(&pix1); pixDestroy(&pix2); if (!same) { numaDestroy(&na); return 0; } } *psame = 1; if (pnaindex) *pnaindex = na; else numaDestroy(&na); return 0; } /*! * \brief pixaSetFullSizeBoxa() * * \param[in] pixa * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) Replaces the existing boxa.  Each box gives the dimensions
 *          of the corresponding pix.  This is needed for functions
 *          like pixaSort() that sort based on the boxes.
 * 
*/ l_ok pixaSetFullSizeBoxa(PIXA *pixa) { l_int32 i, n, w, h; BOX *box; BOXA *boxa; PIX *pix; PROCNAME("pixaSetFullSizeBoxa"); if (!pixa) return ERROR_INT("pixa not defined", procName, 1); if ((n = pixaGetCount(pixa)) == 0) { L_INFO("pixa contains no pix\n", procName); return 0; } boxa = boxaCreate(n); pixaSetBoxa(pixa, boxa, L_INSERT); for (i = 0; i < n; i++) { pix = pixaGetPix(pixa, i, L_CLONE); pixGetDimensions(pix, &w, &h, NULL); box = boxCreate(0, 0, w, h); boxaAddBox(boxa, box, L_INSERT); pixDestroy(&pix); } return 0; }