Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/thirdparty/leptonica/src/boxfunc1.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/thirdparty/leptonica/src/boxfunc1.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,2652 @@ +/*====================================================================* + - 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 boxfunc1.c + * <pre> + * + * Box geometry + * l_int32 boxContains() + * l_int32 boxIntersects() + * BOXA *boxaContainedInBox() + * l_int32 boxaContainedInBoxCount() + * l_int32 boxaContainedInBoxa() + * BOXA *boxaIntersectsBox() + * l_int32 boxaIntersectsBoxCount() + * BOXA *boxaClipToBox() + * BOXA *boxaCombineOverlaps() + * l_int32 boxaCombineOverlapsInPair() + * BOX *boxOverlapRegion() + * BOX *boxBoundingRegion() + * l_int32 boxOverlapFraction() + * l_int32 boxOverlapArea() + * BOXA *boxaHandleOverlaps() + * l_int32 boxOverlapDistance() + * l_int32 boxSeparationDistance() + * l_int32 boxCompareSize() + * l_int32 boxContainsPt() + * BOX *boxaGetNearestToPt() + * BOX *boxaGetNearestToLine() + * l_int32 boxaFindNearestBoxes() + * l_int32 boxaGetNearestByDirection() + * static l_int32 boxHasOverlapInXorY() + * static l_int32 boxGetDistanceInXorY() + * l_int32 boxIntersectByLine() + * l_int32 boxGetCenter() + * BOX *boxClipToRectangle() + * l_int32 boxClipToRectangleParams() + * BOX *boxRelocateOneSide() + * BOXA *boxaAdjustSides() + * BOXA *boxaAdjustBoxSides() + * BOX *boxAdjustSides() + * BOXA *boxaSetSide() + * l_int32 boxSetSide() + * BOXA *boxaAdjustWidthToTarget() + * BOXA *boxaAdjustHeightToTarget() + * l_int32 boxEqual() + * l_int32 boxaEqual() + * l_int32 boxSimilar() + * l_int32 boxaSimilar() + * + * Boxa combine and split + * l_int32 boxaJoin() + * l_int32 boxaaJoin() + * l_int32 boxaSplitEvenOdd() + * BOXA *boxaMergeEvenOdd() + * </pre> + */ + +#ifdef HAVE_CONFIG_H +#include <config_auto.h> +#endif /* HAVE_CONFIG_H */ + +#include "allheaders.h" +#include "pix_internal.h" + +static l_int32 boxHasOverlapInXorY(l_int32 c1, l_int32 s1, l_int32 c2, + l_int32 s2); +static l_int32 boxGetDistanceInXorY(l_int32 c1, l_int32 s1, l_int32 c2, + l_int32 s2); + + +/*---------------------------------------------------------------------* + * Box geometry * + *---------------------------------------------------------------------*/ +/*! + * \brief boxContains() + * + * \param[in] box1, box2 + * \param[out] presult 1 if box2 is entirely contained within box1; + * 0 otherwise + * \return 0 if OK, 1 on error + */ +l_ok +boxContains(BOX *box1, + BOX *box2, + l_int32 *presult) +{ +l_int32 x1, y1, w1, h1, x2, y2, w2, h2, valid1, valid2; + + if (!presult) + return ERROR_INT("&result not defined", __func__, 1); + *presult = 0; + if (!box1 || !box2) + return ERROR_INT("boxes not both defined", __func__, 1); + boxIsValid(box1, &valid1); + boxIsValid(box2, &valid2); + if (!valid1 || !valid2) + return ERROR_INT("boxes not both valid", __func__, 1); + + boxGetGeometry(box1, &x1, &y1, &w1, &h1); + boxGetGeometry(box2, &x2, &y2, &w2, &h2); + if (x1 <= x2 && y1 <= y2 && (x1 + w1 >= x2 + w2) && (y1 + h1 >= y2 + h2)) + *presult = 1; + return 0; +} + + +/*! + * \brief boxIntersects() + * + * \param[in] box1, box2 + * \param[out] presult 1 if any part of box2 is contained in box1; + * 0 otherwise + * \return 0 if OK, 1 on error + */ +l_ok +boxIntersects(BOX *box1, + BOX *box2, + l_int32 *presult) +{ +l_int32 l1, l2, r1, r2, t1, t2, b1, b2, w1, h1, w2, h2, valid1, valid2; + + if (!presult) + return ERROR_INT("&result not defined", __func__, 1); + *presult = 0; + if (!box1 || !box2) + return ERROR_INT("boxes not both defined", __func__, 1); + boxIsValid(box1, &valid1); + boxIsValid(box2, &valid2); + if (!valid1 || !valid2) + return ERROR_INT("boxes not both valid", __func__, 1); + + boxGetGeometry(box1, &l1, &t1, &w1, &h1); + boxGetGeometry(box2, &l2, &t2, &w2, &h2); + r1 = l1 + w1 - 1; + r2 = l2 + w2 - 1; + b1 = t1 + h1 - 1; + b2 = t2 + h2 - 1; + if (b2 < t1 || b1 < t2 || r1 < l2 || r2 < l1) + *presult = 0; + else + *presult = 1; + return 0; +} + + +/*! + * \brief boxaContainedInBox() + * + * \param[in] boxas + * \param[in] box for containment + * \return boxad boxa with all boxes in boxas that are entirely + * contained in box, or NULL on error + * + * <pre> + * Notes: + * (1) All boxes in %boxas that are entirely outside box are removed. + * (2) If %box is not valid, returns an empty boxa. + * </pre> + */ +BOXA * +boxaContainedInBox(BOXA *boxas, + BOX *box) +{ +l_int32 i, n, val, valid; +BOX *box1; +BOXA *boxad; + + if (!boxas) + return (BOXA *)ERROR_PTR("boxas not defined", __func__, NULL); + if (!box) + return (BOXA *)ERROR_PTR("box not defined", __func__, NULL); + n = boxaGetCount(boxas); + boxIsValid(box, &valid); + if (n == 0 || !valid) + return boxaCreate(1); /* empty */ + + boxad = boxaCreate(0); + for (i = 0; i < n; i++) { + if ((box1 = boxaGetValidBox(boxas, i, L_CLONE)) == NULL) + continue; + boxContains(box, box1, &val); + if (val == 1) + boxaAddBox(boxad, box1, L_COPY); + boxDestroy(&box1); /* destroy the clone */ + } + + return boxad; +} + + +/*! + * \brief boxaContainedInBoxCount() + * + * \param[in] boxa + * \param[in] box for selecting contained boxes in %boxa + * \param[out] pcount number of boxes intersecting the box + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) If %box is not valid, returns a zero count. + * </pre> + */ +l_ok +boxaContainedInBoxCount(BOXA *boxa, + BOX *box, + l_int32 *pcount) +{ +l_int32 i, n, val, valid; +BOX *box1; + + if (!pcount) + return ERROR_INT("&count not defined", __func__, 1); + *pcount = 0; + if (!boxa) + return ERROR_INT("boxa not defined", __func__, 1); + if (!box) + return ERROR_INT("box not defined", __func__, 1); + n = boxaGetCount(boxa); + boxIsValid(box, &valid); + if (n == 0 || !valid) + return 0; + + for (i = 0; i < n; i++) { + if ((box1 = boxaGetValidBox(boxa, i, L_CLONE)) == NULL) + continue; + boxContains(box, box1, &val); + if (val == 1) + (*pcount)++; + boxDestroy(&box1); + } + return 0; +} + + +/*! + * \brief boxaContainedInBoxa() + * + * \param[in] boxa1, boxa2 + * \param[out] pcontained 1 if every box in boxa2 is contained in + * some box in boxa1; 0 otherwise + * \return 0 if OK, 1 on error + */ +l_ok +boxaContainedInBoxa(BOXA *boxa1, + BOXA *boxa2, + l_int32 *pcontained) +{ +l_int32 i, j, n1, n2, cont, result; +BOX *box1, *box2; + + if (!pcontained) + return ERROR_INT("&contained not defined", __func__, 1); + *pcontained = 0; + if (!boxa1 || !boxa2) + return ERROR_INT("boxa1 and boxa2 not both defined", __func__, 1); + + n1 = boxaGetCount(boxa1); + n2 = boxaGetCount(boxa2); + for (i = 0; i < n2; i++) { + if ((box2 = boxaGetValidBox(boxa2, i, L_CLONE)) == NULL) + continue; + cont = 0; + for (j = 0; j < n1; j++) { + if ((box1 = boxaGetValidBox(boxa1, j, L_CLONE)) == NULL) + continue; + boxContains(box1, box2, &result); + boxDestroy(&box1); + if (result) { + cont = 1; + break; + } + } + boxDestroy(&box2); + if (!cont) return 0; + } + + *pcontained = 1; + return 0; +} + + +/*! + * \brief boxaIntersectsBox() + * + * \param[in] boxas + * \param[in] box for intersecting + * \return boxad boxa with all boxes in boxas that intersect box, + * or NULL on error + * + * <pre> + * Notes: + * (1) All boxes in boxa that intersect with box (i.e., are completely + * or partially contained in box) are retained. + * </pre> + */ +BOXA * +boxaIntersectsBox(BOXA *boxas, + BOX *box) +{ +l_int32 i, n, val, valid; +BOX *box1; +BOXA *boxad; + + if (!boxas) + return (BOXA *)ERROR_PTR("boxas not defined", __func__, NULL); + if (!box) + return (BOXA *)ERROR_PTR("box not defined", __func__, NULL); + n = boxaGetCount(boxas); + boxIsValid(box, &valid); + if (n == 0 || !valid) + return boxaCreate(1); /* empty */ + + boxad = boxaCreate(0); + for (i = 0; i < n; i++) { + if ((box1 = boxaGetValidBox(boxas, i, L_CLONE)) == NULL) + continue; + boxIntersects(box, box1, &val); + if (val == 1) + boxaAddBox(boxad, box1, L_COPY); + boxDestroy(&box1); /* destroy the clone */ + } + + return boxad; +} + + +/*! + * \brief boxaIntersectsBoxCount() + * + * \param[in] boxa + * \param[in] box for selecting intersecting boxes in %boxa + * \param[out] pcount number of boxes intersecting the box + * \return 0 if OK, 1 on error + */ +l_ok +boxaIntersectsBoxCount(BOXA *boxa, + BOX *box, + l_int32 *pcount) +{ +l_int32 i, n, val, valid; +BOX *box1; + + if (!pcount) + return ERROR_INT("&count not defined", __func__, 1); + *pcount = 0; + if (!boxa) + return ERROR_INT("boxa not defined", __func__, 1); + if (!box) + return ERROR_INT("box not defined", __func__, 1); + n = boxaGetCount(boxa); + boxIsValid(box, &valid); + if (n == 0 || !valid) + return 0; + + for (i = 0; i < n; i++) { + if ((box1 = boxaGetValidBox(boxa, i, L_CLONE)) == NULL) + continue; + boxIntersects(box, box1, &val); + if (val == 1) + (*pcount)++; + boxDestroy(&box1); + } + return 0; +} + + +/*! + * \brief boxaClipToBox() + * + * \param[in] boxas + * \param[in] box for clipping + * \return boxad boxa with boxes in boxas clipped to box, or NULL on error + * + * <pre> + * Notes: + * (1) All boxes in boxa not intersecting with box are removed, and + * the remaining boxes are clipped to box. + * </pre> + */ +BOXA * +boxaClipToBox(BOXA *boxas, + BOX *box) +{ +l_int32 i, n, valid; +BOX *box1, *boxo; +BOXA *boxad; + + if (!boxas) + return (BOXA *)ERROR_PTR("boxas not defined", __func__, NULL); + if (!box) + return (BOXA *)ERROR_PTR("box not defined", __func__, NULL); + n = boxaGetCount(boxas); + boxIsValid(box, &valid); + if (n == 0 || !valid) + return boxaCreate(1); /* empty */ + + boxad = boxaCreate(0); + for (i = 0; i < n; i++) { + if ((box1 = boxaGetValidBox(boxas, i, L_CLONE)) == NULL) + continue; + if ((boxo = boxOverlapRegion(box, box1)) != NULL) + boxaAddBox(boxad, boxo, L_INSERT); + boxDestroy(&box1); + } + + return boxad; +} + + +/*! + * \brief boxaCombineOverlaps() + * + * \param[in] boxas + * \param[in,out] pixadb debug output + * \return boxad where each set of boxes in boxas that overlap are combined + * into a single bounding box in boxad, or NULL on error. + * + * <pre> + * Notes: + * (1) If there are no overlapping boxes, it simply returns a copy + * of %boxas. + * (2) Input an empty %pixadb, using pixaCreate(0), for debug output. + * The output gives 2 visualizations of the boxes per iteration; + * boxes in red before, and added boxes in green after. Note that + * all pixels in the red boxes are contained in the green ones. + * (3) The alternative method of painting each rectangle and finding + * the 4-connected components gives a different result in + * general, because two non-overlapping (but touching) + * rectangles, when rendered, are 4-connected and will be joined. + * (4) A bad case computationally is to have n boxes, none of which + * overlap. Then you have one iteration with O(n^2) compares. + * This is still faster than painting each rectangle and finding + * the bounding boxes of the connected components, even for + * thousands of rectangles. + * </pre> + */ +BOXA * +boxaCombineOverlaps(BOXA *boxas, + PIXA *pixadb) +{ +l_int32 i, j, w, h, n1, n2, overlap, niters; +BOX *box1, *box2, *box3; +BOXA *boxa1, *boxa2; +PIX *pix1 = NULL; + + if (!boxas) + return (BOXA *)ERROR_PTR("boxas not defined", __func__, NULL); + + if (pixadb) boxaGetExtent(boxas, &w, &h, NULL); + + boxa1 = boxaCopy(boxas, L_COPY); + n1 = boxaGetCount(boxa1); + niters = 0; + while (1) { /* loop until no change from previous iteration */ + niters++; + if (pixadb) { + pix1 = pixCreate(w + 5, h + 5, 32); + pixSetAll(pix1); + pixRenderBoxaArb(pix1, boxa1, 2, 255, 0, 0); + pixaAddPix(pixadb, pix1, L_COPY); + } + + /* Combine overlaps for this iteration */ + for (i = 0; i < n1; i++) { + if ((box1 = boxaGetValidBox(boxa1, i, L_COPY)) == NULL) + continue; + for (j = i + 1; j < n1; j++) { + if ((box2 = boxaGetValidBox(boxa1, j, L_COPY)) == NULL) + continue; + boxIntersects(box1, box2, &overlap); + if (overlap) { + box3 = boxBoundingRegion(box1, box2); + boxaReplaceBox(boxa1, i, box3); + boxaReplaceBox(boxa1, j, boxCreate(0, 0, 0, 0)); + boxDestroy(&box1); + box1 = boxCopy(box3); + } + boxDestroy(&box2); + } + boxDestroy(&box1); + } + boxa2 = boxaSaveValid(boxa1, L_COPY); + n2 = boxaGetCount(boxa2); + boxaDestroy(&boxa1); + boxa1 = boxa2; + if (n1 == n2) { + if (pixadb) pixDestroy(&pix1); + break; + } + n1 = n2; + if (pixadb) { + pixRenderBoxaArb(pix1, boxa1, 2, 0, 255, 0); + pixaAddPix(pixadb, pix1, L_INSERT); + } + } + + if (pixadb) + L_INFO("number of iterations: %d\n", __func__, niters); + return boxa1; +} + + +/*! + * \brief boxaCombineOverlapsInPair() + * + * \param[in] boxas1 input boxa1 + * \param[in] boxas2 input boxa2 + * \param[out] pboxad1 output boxa1 + * \param[out] pboxad2 output boxa2 + * \param[in,out] pixadb debug output + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) One of three things happens to each box in %boxa1 and %boxa2: + * * it gets absorbed into a larger box that it overlaps with + * * it absorbs a smaller (by area) box that it overlaps with + * and gets larger, using the bounding region of the 2 boxes + * * it is unchanged (including absorbing smaller boxes that + * are contained within it). + * (2) If all the boxes from one of the input boxa are absorbed, this + * returns an empty boxa. + * (3) Input an empty %pixadb, using pixaCreate(0), for debug output + * (4) This is useful if different operations are to be carried out + * on possibly overlapping rectangular regions, and it is desired + * to have only one operation on any rectangular region. + * </pre> + */ +l_ok +boxaCombineOverlapsInPair(BOXA *boxas1, + BOXA *boxas2, + BOXA **pboxad1, + BOXA **pboxad2, + PIXA *pixadb) +{ +l_int32 i, j, w, h, w2, h2, n1, n2, n1i, n2i, niters; +l_int32 overlap, bigger, area1, area2; +BOX *box1, *box2, *box3; +BOXA *boxa1, *boxa2, *boxac1, *boxac2; +PIX *pix1; + + if (pboxad1) *pboxad1 = NULL; + if (pboxad2) *pboxad2 = NULL; + if (!boxas1 || !boxas2) + return ERROR_INT("boxas1 and boxas2 not both defined", __func__, 1); + if (!pboxad1 || !pboxad2) + return ERROR_INT("&boxad1 and &boxad2 not both defined", __func__, 1); + + if (pixadb) { + boxaGetExtent(boxas1, &w, &h, NULL); + boxaGetExtent(boxas2, &w2, &h2, NULL); + w = L_MAX(w, w2); + h = L_MAX(h, w2); + } + + /* Let the boxa with the largest area have first crack at the other */ + boxaGetArea(boxas1, &area1); + boxaGetArea(boxas2, &area2); + if (area1 >= area2) { + boxac1 = boxaCopy(boxas1, L_COPY); + boxac2 = boxaCopy(boxas2, L_COPY); + } else { + boxac1 = boxaCopy(boxas2, L_COPY); + boxac2 = boxaCopy(boxas1, L_COPY); + } + + n1i = boxaGetCount(boxac1); + n2i = boxaGetCount(boxac2); + niters = 0; + while (1) { + niters++; + if (pixadb) { + pix1 = pixCreate(w + 5, h + 5, 32); + pixSetAll(pix1); + pixRenderBoxaArb(pix1, boxac1, 2, 255, 0, 0); + pixRenderBoxaArb(pix1, boxac2, 2, 0, 255, 0); + pixaAddPix(pixadb, pix1, L_INSERT); + } + + /* First combine boxes in each set */ + boxa1 = boxaCombineOverlaps(boxac1, NULL); + boxa2 = boxaCombineOverlaps(boxac2, NULL); + + /* Now combine boxes between sets */ + n1 = boxaGetCount(boxa1); + n2 = boxaGetCount(boxa2); + for (i = 0; i < n1; i++) { /* 1 eats 2 */ + if ((box1 = boxaGetValidBox(boxa1, i, L_COPY)) == NULL) + continue; + for (j = 0; j < n2; j++) { + if ((box2 = boxaGetValidBox(boxa2, j, L_COPY)) == NULL) + continue; + boxIntersects(box1, box2, &overlap); + boxCompareSize(box1, box2, L_SORT_BY_AREA, &bigger); + if (overlap && (bigger == 1)) { + box3 = boxBoundingRegion(box1, box2); + boxaReplaceBox(boxa1, i, box3); + boxaReplaceBox(boxa2, j, boxCreate(0, 0, 0, 0)); + boxDestroy(&box1); + box1 = boxCopy(box3); + } + boxDestroy(&box2); + } + boxDestroy(&box1); + } + for (i = 0; i < n2; i++) { /* 2 eats 1 */ + if ((box2 = boxaGetValidBox(boxa2, i, L_COPY)) == NULL) + continue; + for (j = 0; j < n1; j++) { + if ((box1 = boxaGetValidBox(boxa1, j, L_COPY)) == NULL) + continue; + boxIntersects(box1, box2, &overlap); + boxCompareSize(box2, box1, L_SORT_BY_AREA, &bigger); + if (overlap && (bigger == 1)) { + box3 = boxBoundingRegion(box1, box2); + boxaReplaceBox(boxa2, i, box3); + boxaReplaceBox(boxa1, j, boxCreate(0, 0, 0, 0)); + boxDestroy(&box2); + box2 = boxCopy(box3); + } + boxDestroy(&box1); + } + boxDestroy(&box2); + } + boxaDestroy(&boxac1); + boxaDestroy(&boxac2); + boxac1 = boxaSaveValid(boxa1, L_COPY); /* remove invalid boxes */ + boxac2 = boxaSaveValid(boxa2, L_COPY); + boxaDestroy(&boxa1); + boxaDestroy(&boxa2); + n1 = boxaGetCount(boxac1); + n2 = boxaGetCount(boxac2); + if (n1 == n1i && n2 == n2i) break; + n1i = n1; + n2i = n2; + if (pixadb) { + pix1 = pixCreate(w + 5, h + 5, 32); + pixSetAll(pix1); + pixRenderBoxaArb(pix1, boxac1, 2, 255, 0, 0); + pixRenderBoxaArb(pix1, boxac2, 2, 0, 255, 0); + pixaAddPix(pixadb, pix1, L_INSERT); + } + } + + if (pixadb) + L_INFO("number of iterations: %d\n", __func__, niters); + *pboxad1 = boxac1; + *pboxad2 = boxac2; + return 0; +} + + +/*! + * \brief boxOverlapRegion() + * + * \param[in] box1, box2 + * \return box of overlap region between input boxes; + * NULL if no overlap or on error + * + * <pre> + * Notes: + * (1) This is the geometric intersection of the two rectangles. + * </pre> + */ +BOX * +boxOverlapRegion(BOX *box1, + BOX *box2) +{ +l_int32 l1, l2, r1, r2, t1, t2, b1, b2, w1, h1, w2, h2, ld, td, rd, bd; +l_int32 valid1, valid2; + + if (!box1 || !box2) + return (BOX *)ERROR_PTR("boxes not both defined", __func__, NULL); + boxIsValid(box1, &valid1); + boxIsValid(box2, &valid2); + if (!valid1 || !valid2) { + L_WARNING("at least one box is invalid\n", __func__); + return NULL; + } + + boxGetGeometry(box1, &l1, &t1, &w1, &h1); + boxGetGeometry(box2, &l2, &t2, &w2, &h2); + r1 = l1 + w1 - 1; + r2 = l2 + w2 - 1; + b1 = t1 + h1 - 1; + b2 = t2 + h2 - 1; + if (b2 < t1 || b1 < t2 || r1 < l2 || r2 < l1) + return NULL; + + ld = L_MAX(l1, l2); + td = L_MAX(t1, t2); + rd = L_MIN(r1, r2); + bd = L_MIN(b1, b2); + return boxCreate(ld, td, rd - ld + 1, bd - td + 1); +} + + +/*! + * \brief boxBoundingRegion() + * + * \param[in] box1, box2 + * \return box of bounding region containing the input boxes; + * NULL on error + * + * <pre> + * Notes: + * (1) This is the geometric union of the two rectangles. + * (2) Invalid boxes are ignored. This returns an invalid box + * if both input boxes are invalid. + * (3) For the geometric union of a boxa, use boxaGetExtent(). + * </pre> + */ +BOX * +boxBoundingRegion(BOX *box1, + BOX *box2) +{ +l_int32 l1, l2, r1, r2, t1, t2, b1, b2, w1, h1, w2, h2, ld, td, rd, bd; +l_int32 valid1, valid2; + + if (!box1 || !box2) + return (BOX *)ERROR_PTR("boxes not both defined", __func__, NULL); + boxIsValid(box1, &valid1); + boxIsValid(box2, &valid2); + if (!valid1 && !valid2) { + L_WARNING("both boxes are invalid\n", __func__); + return boxCreate(0, 0, 0, 0); + } + if (valid1 && !valid2) + return boxCopy(box1); + if (!valid1 && valid2) + return boxCopy(box2); + + boxGetGeometry(box1, &l1, &t1, &w1, &h1); + boxGetGeometry(box2, &l2, &t2, &w2, &h2); + r1 = l1 + w1 - 1; + r2 = l2 + w2 - 1; + b1 = t1 + h1 - 1; + b2 = t2 + h2 - 1; + ld = L_MIN(l1, l2); + td = L_MIN(t1, t2); + rd = L_MAX(r1, r2); + bd = L_MAX(b1, b2); + return boxCreate(ld, td, rd - ld + 1, bd - td + 1); +} + + +/*! + * \brief boxOverlapFraction() + * + * \param[in] box1, box2 + * \param[out] pfract the fraction of box2 overlapped by box1 + * \return 0 if OK, 1 on error. + * + * <pre> + * Notes: + * (1) The result depends on the order of the input boxes, + * because the overlap is taken as a fraction of box2. + * (2) If at least one box is not valid, there is no overlap. + * </pre> + */ +l_ok +boxOverlapFraction(BOX *box1, + BOX *box2, + l_float32 *pfract) +{ +l_int32 w2, h2, w, h, valid1, valid2; +BOX *boxo; + + if (!pfract) + return ERROR_INT("&fract not defined", __func__, 1); + *pfract = 0.0; + if (!box1 || !box2) + return ERROR_INT("boxes not both defined", __func__, 1); + boxIsValid(box1, &valid1); + boxIsValid(box2, &valid2); + if (!valid1 || !valid2) { + L_WARNING("boxes not both valid\n", __func__); + return 0; + } + + if ((boxo = boxOverlapRegion(box1, box2)) == NULL) /* no overlap */ + return 0; + + boxGetGeometry(box2, NULL, NULL, &w2, &h2); + boxGetGeometry(boxo, NULL, NULL, &w, &h); + *pfract = (l_float32)(w * h) / (l_float32)(w2 * h2); + boxDestroy(&boxo); + return 0; +} + + +/*! + * \brief boxOverlapArea() + * + * \param[in] box1, box2 + * \param[out] parea the number of pixels in the overlap + * \return 0 if OK, 1 on error. + */ +l_ok +boxOverlapArea(BOX *box1, + BOX *box2, + l_int32 *parea) +{ +l_int32 w, h, valid1, valid2; +BOX *box; + + if (!parea) + return ERROR_INT("&area not defined", __func__, 1); + *parea = 0; + if (!box1 || !box2) + return ERROR_INT("boxes not both defined", __func__, 1); + boxIsValid(box1, &valid1); + boxIsValid(box2, &valid2); + if (!valid1 || !valid2) + return ERROR_INT("boxes not both valid", __func__, 1); + + if ((box = boxOverlapRegion(box1, box2)) == NULL) /* no overlap */ + return 0; + + boxGetGeometry(box, NULL, NULL, &w, &h); + *parea = w * h; + boxDestroy(&box); + return 0; +} + + +/*! + * \brief boxaHandleOverlaps() + * + * \param[in] boxas + * \param[in] op L_COMBINE, L_REMOVE_SMALL + * \param[in] range forward distance over which overlaps + * are checked; > 0 + * \param[in] min_overlap minimum fraction of smaller box required for + * overlap to count; 0.0 to ignore + * \param[in] max_ratio maximum fraction of small/large areas for + * overlap to count; 1.0 to ignore + * \param[out] pnamap [optional] combining map + * \return boxad, or NULL on error. + * + * <pre> + * Notes: + * (1) For all n(n-1)/2 box pairings, if two boxes overlap, either: + * (a) op == L_COMBINE: get the bounding region for the two, + * replace the larger with the bounding region, and remove + * the smaller of the two, or + * (b) op == L_REMOVE_SMALL: just remove the smaller. + * (2) If boxas is 2D sorted, range can be small, but if it is + * not spatially sorted, range should be large to allow all + * pairwise comparisons to be made. + * (3) The %min_overlap parameter allows ignoring small overlaps. + * If %min_overlap == 1.0, only boxes fully contained in larger + * boxes can be considered for removal; if %min_overlap == 0.0, + * this constraint is ignored. + * (4) The %max_ratio parameter allows ignoring overlaps between + * boxes that are not too different in size. If %max_ratio == 0.0, + * no boxes can be removed; if %max_ratio == 1.0, this constraint + * is ignored. + * </pre> + */ +BOXA * +boxaHandleOverlaps(BOXA *boxas, + l_int32 op, + l_int32 range, + l_float32 min_overlap, + l_float32 max_ratio, + NUMA **pnamap) +{ +l_int32 i, j, n, w, h, area1, area2, val; +l_int32 overlap_area; +l_float32 overlap_ratio, area_ratio; +BOX *box1, *box2, *box3; +BOXA *boxat, *boxad; +NUMA *namap; + + if (pnamap) *pnamap = NULL; + if (!boxas) + return (BOXA *)ERROR_PTR("boxas not defined", __func__, NULL); + if (op != L_COMBINE && op != L_REMOVE_SMALL) + return (BOXA *)ERROR_PTR("invalid op", __func__, NULL); + + n = boxaGetCount(boxas); + if (n == 0) + return boxaCreate(1); /* empty */ + if (range == 0) { + L_WARNING("range is 0\n", __func__); + return boxaCopy(boxas, L_COPY); + } + + /* Identify smaller boxes in overlap pairs, and mark to eliminate. */ + namap = numaMakeConstant(-1, n); + for (i = 0; i < n; i++) { + if ((box1 = boxaGetValidBox(boxas, i, L_CLONE)) == NULL) + continue; + boxGetGeometry(box1, NULL, NULL, &w, &h); + area1 = w * h; + if (area1 == 0) { + boxDestroy(&box1); + continue; + } + for (j = i + 1; j < i + 1 + range && j < n; j++) { + if ((box2 = boxaGetValidBox(boxas, j, L_CLONE)) == NULL) + continue; + boxOverlapArea(box1, box2, &overlap_area); + if (overlap_area > 0) { + boxGetGeometry(box2, NULL, NULL, &w, &h); + area2 = w * h; + if (area2 == 0) { + /* do nothing */ + } else if (area1 >= area2) { + overlap_ratio = (l_float32)overlap_area / (l_float32)area2; + area_ratio = (l_float32)area2 / (l_float32)area1; + if (overlap_ratio >= min_overlap && + area_ratio <= max_ratio) { + numaSetValue(namap, j, i); + } + } else { + overlap_ratio = (l_float32)overlap_area / (l_float32)area1; + area_ratio = (l_float32)area1 / (l_float32)area2; + if (overlap_ratio >= min_overlap && + area_ratio <= max_ratio) { + numaSetValue(namap, i, j); + } + } + } + boxDestroy(&box2); + } + boxDestroy(&box1); + } + + boxat = boxaCopy(boxas, L_COPY); + if (op == L_COMBINE) { + /* Resize the larger of the pair to the bounding region */ + for (i = 0; i < n; i++) { + numaGetIValue(namap, i, &val); + if (val >= 0) { + box1 = boxaGetBox(boxas, i, L_CLONE); /* smaller */ + box2 = boxaGetBox(boxas, val, L_CLONE); /* larger */ + box3 = boxBoundingRegion(box1, box2); + boxaReplaceBox(boxat, val, box3); + boxDestroy(&box1); + boxDestroy(&box2); + } + } + } + + /* Remove the smaller of the pairs */ + boxad = boxaCreate(n); + for (i = 0; i < n; i++) { + numaGetIValue(namap, i, &val); + if (val == -1) { + box1 = boxaGetBox(boxat, i, L_COPY); + boxaAddBox(boxad, box1, L_INSERT); + } + } + boxaDestroy(&boxat); + if (pnamap) + *pnamap = namap; + else + numaDestroy(&namap); + return boxad; +} + + +/*! + * \brief boxOverlapDistance() + * + * \param[in] box1, box2 two boxes, in any order + * \param[out] ph_ovl [optional] horizontal overlap + * \param[out] pv_ovl [optional] vertical overlap + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This measures horizontal and vertical overlap of the + * two boxes. Horizontal and vertical overlap are measured + * independently. We need to consider several cases to clarify. + * (2) A positive horizontal overlap means that there is at least + * one point on the the %box1 boundary with the same x-component + * as some point on the %box2 boundary. Conversely, with a zero + * or negative horizontal overlap, there are no boundary pixels + * in %box1 that share an x-component with a boundary pixel in %box2. + * (3) For a zero or negative horizontal overlap, o <= 0, the minimum + * difference in the x-component between pixels on the boundaries + * of the two boxes is d = -o + 1. + * (4) Likewise for vertical overlaps. + * </pre> + */ +l_ok +boxOverlapDistance(BOX *box1, + BOX *box2, + l_int32 *ph_ovl, + l_int32 *pv_ovl) +{ +l_int32 l1, t1, w1, h1, r1, b1, l2, t2, w2, h2, r2, b2, valid1, valid2; + + if (!ph_ovl && !pv_ovl) + return ERROR_INT("nothing to do", __func__, 1); + if (ph_ovl) *ph_ovl = 0; + if (pv_ovl) *pv_ovl = 0; + if (!box1 || !box2) + return ERROR_INT("boxes not both defined", __func__, 1); + boxIsValid(box1, &valid1); + boxIsValid(box2, &valid2); + if (!valid1 || !valid2) + return ERROR_INT("boxes not both valid", __func__, 1); + + if (ph_ovl) { + boxGetGeometry(box1, &l1, NULL, &w1, NULL); + boxGetGeometry(box2, &l2, NULL, &w2, NULL); + r1 = l1 + w1; /* 1 pixel to the right of box 1 */ + r2 = l2 + w2; + if (l2 >= l1) + *ph_ovl = r1 - l2; + else + *ph_ovl = r2 - l1; + } + if (pv_ovl) { + boxGetGeometry(box1, NULL, &t1, NULL, &h1); + boxGetGeometry(box2, NULL, &t2, NULL, &h2); + b1 = t1 + h1; /* 1 pixel below box 1 */ + b2 = t2 + h2; + if (t2 >= t1) + *pv_ovl = b1 - t2; + else + *pv_ovl = b2 - t1; + } + return 0; +} + + +/*! + * \brief boxSeparationDistance() + * + * \param[in] box1, box2 two boxes, in any order + * \param[out] ph_sep horizontal separation + * \param[out] pv_sep vertical separation + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This measures the Manhattan distance between the closest points + * on the boundaries of the two boxes. When the boxes overlap + * (including touching along a line or at a corner), the + * horizontal and vertical distances are 0. + * (2) The distances represent the horizontal and vertical separation + * of the two boxes. The boxes have a nonzero intersection when + * both the horizontal and vertical overlaps are positive, and + * for that case both horizontal and vertical separation + * distances are 0. + * (3) If the horizontal overlap of the boxes is positive, the + * horizontal separation between nearest points on respective + * boundaries is 0, and likewise for the vertical overlap. + * (4) If the horizontal overlap ho <= 0, the horizontal + * separation between nearest points is d = -ho + 1. + * Likewise, if the vertical overlap vo <= 0, the vertical + * separation between nearest points is d = -vo + 1. + * </pre> + */ +l_ok +boxSeparationDistance(BOX *box1, + BOX *box2, + l_int32 *ph_sep, + l_int32 *pv_sep) +{ +l_int32 h_ovl, v_ovl, valid1, valid2; + + if (ph_sep) *ph_sep = 0; + if (pv_sep) *pv_sep = 0; + if (!ph_sep || !pv_sep) + return ERROR_INT("&h_sep and &v_sep not both defined", __func__, 1); + if (!box1 || !box2) + return ERROR_INT("boxes not both defined", __func__, 1); + boxIsValid(box1, &valid1); + boxIsValid(box2, &valid2); + if (!valid1 || !valid2) + return ERROR_INT("boxes not both valid", __func__, 1); + + boxOverlapDistance(box1, box2, &h_ovl, &v_ovl); + if (h_ovl <= 0) + *ph_sep = -h_ovl + 1; + if (v_ovl <= 0) + *pv_sep = -v_ovl + 1; + return 0; +} + + +/*! + * \brief boxCompareSize() + * + * \param[in] box1, box2 + * \param[in] type L_SORT_BY_WIDTH, L_SORT_BY_HEIGHT, + * L_SORT_BY_MAX_DIMENSION, L_SORT_BY_PERIMETER, + * L_SORT_BY_AREA, + * \param[out] prel 1 if box1 > box2, 0 if the same, -1 if box1 < box2 + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) We're re-using the SORT enum for these comparisons. + * </pre> + */ +l_ok +boxCompareSize(BOX *box1, + BOX *box2, + l_int32 type, + l_int32 *prel) +{ +l_int32 w1, h1, w2, h2, size1, size2, valid1, valid2; + + if (!prel) + return ERROR_INT("&rel not defined", __func__, 1); + *prel = 0; + if (!box1 || !box2) + return ERROR_INT("boxes not both defined", __func__, 1); + boxIsValid(box1, &valid1); + boxIsValid(box2, &valid2); + if (!valid1 || !valid2) + return ERROR_INT("boxes not both valid", __func__, 1); + if (type != L_SORT_BY_WIDTH && type != L_SORT_BY_HEIGHT && + type != L_SORT_BY_MAX_DIMENSION && type != L_SORT_BY_PERIMETER && + type != L_SORT_BY_AREA) + return ERROR_INT("invalid compare type", __func__, 1); + + boxGetGeometry(box1, NULL, NULL, &w1, &h1); + boxGetGeometry(box2, NULL, NULL, &w2, &h2); + if (type == L_SORT_BY_WIDTH) { + *prel = (w1 > w2) ? 1 : ((w1 == w2) ? 0 : -1); + } else if (type == L_SORT_BY_HEIGHT) { + *prel = (h1 > h2) ? 1 : ((h1 == h2) ? 0 : -1); + } else if (type == L_SORT_BY_MAX_DIMENSION) { + size1 = L_MAX(w1, h1); + size2 = L_MAX(w2, h2); + *prel = (size1 > size2) ? 1 : ((size1 == size2) ? 0 : -1); + } else if (type == L_SORT_BY_PERIMETER) { + size1 = w1 + h1; + size2 = w2 + h2; + *prel = (size1 > size2) ? 1 : ((size1 == size2) ? 0 : -1); + } else if (type == L_SORT_BY_AREA) { + size1 = w1 * h1; + size2 = w2 * h2; + *prel = (size1 > size2) ? 1 : ((size1 == size2) ? 0 : -1); + } + return 0; +} + + +/*! + * \brief boxContainsPt() + * + * \param[in] box + * \param[in] x, y a point + * \param[out] pcontains 1 if box contains point; 0 otherwise + * \return 0 if OK, 1 on error. + */ +l_ok +boxContainsPt(BOX *box, + l_float32 x, + l_float32 y, + l_int32 *pcontains) +{ +l_int32 bx, by, bw, bh; + + if (!pcontains) + return ERROR_INT("&contains not defined", __func__, 1); + *pcontains = 0; + if (!box) + return ERROR_INT("&box not defined", __func__, 1); + boxGetGeometry(box, &bx, &by, &bw, &bh); + if (x >= bx && x < bx + bw && y >= by && y < by + bh) + *pcontains = 1; + return 0; +} + + +/*! + * \brief boxaGetNearestToPt() + * + * \param[in] boxa + * \param[in] x, y point + * \return box with centroid closest to the given point [x,y], + * or NULL if no boxes in boxa + * + * <pre> + * Notes: + * (1) Uses euclidean distance between centroid and point. + * </pre> + */ +BOX * +boxaGetNearestToPt(BOXA *boxa, + l_int32 x, + l_int32 y) +{ +l_int32 i, n, minindex; +l_float32 delx, dely, dist, mindist, cx, cy; +BOX *box; + + if (!boxa) + return (BOX *)ERROR_PTR("boxa not defined", __func__, NULL); + if ((n = boxaGetCount(boxa)) == 0) + return (BOX *)ERROR_PTR("n = 0", __func__, NULL); + + mindist = 1000000000.; + minindex = 0; + for (i = 0; i < n; i++) { + if ((box = boxaGetValidBox(boxa, i, L_CLONE)) == NULL) + continue; + boxGetCenter(box, &cx, &cy); + delx = (l_float32)(cx - x); + dely = (l_float32)(cy - y); + dist = delx * delx + dely * dely; + if (dist < mindist) { + minindex = i; + mindist = dist; + } + boxDestroy(&box); + } + + return boxaGetBox(boxa, minindex, L_COPY); +} + + +/*! + * \brief boxaGetNearestToLine() + * + * \param[in] boxa + * \param[in] x, y (y = -1 for vertical line; x = -1 for horiz line) + * \return box with centroid closest to the given line, + * or NULL if no boxes in boxa + * + * <pre> + * Notes: + * (1) For a horizontal line at some value y, get the minimum of the + * distance |yc - y| from the box centroid yc value to y; + * likewise minimize |xc - x| for a vertical line at x. + * (2) Input y < 0, x >= 0 to indicate a vertical line at x, and + * x < 0, y >= 0 for a horizontal line at y. + * </pre> + */ +BOX * +boxaGetNearestToLine(BOXA *boxa, + l_int32 x, + l_int32 y) +{ +l_int32 i, n, minindex; +l_float32 dist, mindist, cx, cy; +BOX *box; + + if (!boxa) + return (BOX *)ERROR_PTR("boxa not defined", __func__, NULL); + if ((n = boxaGetCount(boxa)) == 0) + return (BOX *)ERROR_PTR("n = 0", __func__, NULL); + if (y >= 0 && x >= 0) + return (BOX *)ERROR_PTR("either x or y must be < 0", __func__, NULL); + if (y < 0 && x < 0) + return (BOX *)ERROR_PTR("either x or y must be >= 0", __func__, NULL); + + mindist = 1000000000.; + minindex = 0; + for (i = 0; i < n; i++) { + if ((box = boxaGetValidBox(boxa, i, L_CLONE)) == NULL) + continue; + boxGetCenter(box, &cx, &cy); + if (x >= 0) + dist = L_ABS(cx - (l_float32)x); + else /* y >= 0 */ + dist = L_ABS(cy - (l_float32)y); + if (dist < mindist) { + minindex = i; + mindist = dist; + } + boxDestroy(&box); + } + + return boxaGetBox(boxa, minindex, L_COPY); +} + + +/*! + * \brief boxaFindNearestBoxes() + * + * \param[in] boxa either unsorted, or 2D sorted in LR/TB scan order + * \param[in] dist_select L_NON_NEGATIVE, L_ALL + * \param[in] range search distance from box i; use 0 to search + * entire boxa (e.g., if it's not 2D sorted) + * \param[out] pnaaindex for each box in %boxa, contains a numa of 4 + * box indices (per direction) of the nearest box + * \param[out] pnaadist for each box in %boxa, this contains a numa + * \return 0 if OK, 1 on error + * <pre> + * Notes: + * (1) See boxaGetNearestByDirection() for usage of %dist_select + * and %range. + * </pre> + */ +l_ok +boxaFindNearestBoxes(BOXA *boxa, + l_int32 dist_select, + l_int32 range, + NUMAA **pnaaindex, + NUMAA **pnaadist) +{ +l_int32 i, n, index, dist; +NUMA *nai, *nad; +NUMAA *naai, *naad; + + if (pnaaindex) *pnaaindex = NULL; + if (pnaadist) *pnaadist = NULL; + if (!pnaaindex) + return ERROR_INT("&naaindex not defined", __func__, 1); + if (!pnaadist) + return ERROR_INT("&naadist not defined", __func__, 1); + if (!boxa) + return ERROR_INT("boxa not defined", __func__, 1); + + n = boxaGetCount(boxa); + naai = numaaCreate(n); + naad = numaaCreate(n); + *pnaaindex = naai; + *pnaadist = naad; + for (i = 0; i < n; i++) { + nai = numaCreate(4); + nad = numaCreate(4); + boxaGetNearestByDirection(boxa, i, L_FROM_LEFT, dist_select, + range, &index, &dist); + numaAddNumber(nai, index); + numaAddNumber(nad, dist); + boxaGetNearestByDirection(boxa, i, L_FROM_RIGHT, dist_select, + range, &index, &dist); + numaAddNumber(nai, index); + numaAddNumber(nad, dist); + boxaGetNearestByDirection(boxa, i, L_FROM_TOP, dist_select, + range, &index, &dist); + numaAddNumber(nai, index); + numaAddNumber(nad, dist); + boxaGetNearestByDirection(boxa, i, L_FROM_BOT, dist_select, + range, &index, &dist); + numaAddNumber(nai, index); + numaAddNumber(nad, dist); + numaaAddNuma(naai, nai, L_INSERT); + numaaAddNuma(naad, nad, L_INSERT); + } + return 0; +} + + +/*! + * \brief boxaGetNearestByDirection() + * + * \param[in] boxa either unsorted, or 2D sorted in LR/TB scan order + * \param[in] i box we test against + * \param[in] dir direction to look: L_FROM_LEFT, L_FROM_RIGHT, + * L_FROM_TOP, L_FROM_BOT + * \param[in] dist_select L_NON_NEGATIVE, L_ALL + * \param[in] range search distance from box i; use 0 to search + * entire boxa (e.g., if it's not 2D sorted) + * \param[out] pindex index in boxa of nearest box with overlapping + * coordinates in the indicated direction; + * -1 if there is no box + * \param[out] pdist distance of the nearest box in the indicated + * direction; 100000 if no box + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) For efficiency, use a LR/TD sorted %boxa, which can be + * made by flattening a 2D sorted boxaa. In that case, + * %range can be some positive integer like 50. + * (2) If boxes overlap, the distance will be < 0. Use %dist_select + * to determine if these should count or not. If L_ALL, then + * one box will match as the nearest to another in 2 or more + * directions. + * </pre> + */ +l_ok +boxaGetNearestByDirection(BOXA *boxa, + l_int32 i, + l_int32 dir, + l_int32 dist_select, + l_int32 range, + l_int32 *pindex, + l_int32 *pdist) +{ +l_int32 j, jmin, jmax, n, mindist, dist, index; +l_int32 x, y, w, h, bx, by, bw, bh; + + if (pindex) *pindex = -1; + if (pdist) *pdist = 100000; + if (!pindex) + return ERROR_INT("&index not defined", __func__, 1); + if (!pdist) + return ERROR_INT("&dist not defined", __func__, 1); + if (!boxa) + return ERROR_INT("boxa not defined", __func__, 1); + if (dir != L_FROM_LEFT && dir != L_FROM_RIGHT && + dir != L_FROM_TOP && dir != L_FROM_BOT) + return ERROR_INT("invalid dir", __func__, 1); + if (dist_select != L_NON_NEGATIVE && dist_select != L_ALL) + return ERROR_INT("invalid dist_select", __func__, 1); + n = boxaGetCount(boxa); + if (i < 0 || i >= n) + return ERROR_INT("invalid box index", __func__, 1); + + jmin = (range <= 0) ? 0 : L_MAX(0, i - range); + jmax = (range <= 0) ? n - 1 : L_MIN(n -1, i + range); + boxaGetBoxGeometry(boxa, i, &x, &y, &w, &h); + mindist = 100000; + index = -1; + if (dir == L_FROM_LEFT || dir == L_FROM_RIGHT) { + for (j = jmin; j <= jmax; j++) { + if (j == i) continue; + boxaGetBoxGeometry(boxa, j, &bx, &by, &bw, &bh); + if ((bx >= x && dir == L_FROM_LEFT) || /* not to the left */ + (x >= bx && dir == L_FROM_RIGHT)) /* not to the right */ + continue; + if (boxHasOverlapInXorY(y, h, by, bh) == 1) { + dist = boxGetDistanceInXorY(x, w, bx, bw); + if (dist_select == L_NON_NEGATIVE && dist < 0) continue; + if (dist < mindist) { + mindist = dist; + index = j; + } + } + } + } else if (dir == L_FROM_TOP || dir == L_FROM_BOT) { + for (j = jmin; j <= jmax; j++) { + if (j == i) continue; + boxaGetBoxGeometry(boxa, j, &bx, &by, &bw, &bh); + if ((by >= y && dir == L_FROM_TOP) || /* not above */ + (y >= by && dir == L_FROM_BOT)) /* not below */ + continue; + if (boxHasOverlapInXorY(x, w, bx, bw) == 1) { + dist = boxGetDistanceInXorY(y, h, by, bh); + if (dist_select == L_NON_NEGATIVE && dist < 0) continue; + if (dist < mindist) { + mindist = dist; + index = j; + } + } + } + } + *pindex = index; + *pdist = mindist; + return 0; +} + + +/*! + * \brief boxHasOverlapInXorY() + * + * \param[in] c1 left or top coordinate of box1 + * \param[in] s1 width or height of box1 + * \param[in] c2 left or top coordinate of box2 + * \param[in] s2 width or height of box2 + * \return 0 if no overlap; 1 if any overlap + * + * <pre> + * Notes: + * (1) Like boxGetDistanceInXorY(), this is used for overlaps both in + * x (which projected vertically) and in y (projected horizontally) + * </pre> + */ +static l_int32 +boxHasOverlapInXorY(l_int32 c1, + l_int32 s1, + l_int32 c2, + l_int32 s2) +{ +l_int32 ovlp; + + if (c1 > c2) + ovlp = c2 + s2 - 1 - c1; + else + ovlp = c1 + s1 - 1 - c2; + return (ovlp < 0) ? 0 : 1; +} + + +/*! + * \brief boxGetDistanceInXorY() + * + * \param[in] c1 left or top coordinate of box1 + * \param[in] s1 width or height of box1 + * \param[in] c2 left or top coordinate of box2 + * \param[in] s2 width or height of box2 + * \return distance between them (if < 0, box2 overlaps box1 in the + * dimension considered) + */ +static l_int32 +boxGetDistanceInXorY(l_int32 c1, + l_int32 s1, + l_int32 c2, + l_int32 s2) +{ +l_int32 dist; + + if (c1 > c2) + dist = c1 - (c2 + s2 - 1); + else + dist = c2 - (c1 + s1 - 1); + return dist; +} + + +/*! + * \brief boxGetCenter() + * + * \param[in] box + * \param[out] pcx, pcy location of center of box + * \return 0 if OK, 1 on error or if box is not valid + */ +l_ok +boxGetCenter(const BOX *box, + l_float32 *pcx, + l_float32 *pcy) +{ +l_int32 x, y, w, h; + + if (pcx) *pcx = 0; + if (pcy) *pcy = 0; + if (!pcx || !pcy) + return ERROR_INT("&cx, &cy not both defined", __func__, 1); + if (!box) + return ERROR_INT("box not defined", __func__, 1); + boxGetGeometry(box, &x, &y, &w, &h); + if (w == 0 || h == 0) return 1; + *pcx = (l_float32)(x + 0.5 * w); + *pcy = (l_float32)(y + 0.5 * h); + + return 0; +} + + +/*! + * \brief boxIntersectByLine() + * + * \param[in] box + * \param[in] x, y point that line goes through + * \param[in] slope of line + * \param[out] px1, py1 1st point of intersection with box + * \param[out] px2, py2 2nd point of intersection with box + * \param[out] pn number of points of intersection + * \return 0 if OK, 1 on error or if box is not valid + * + * <pre> + * Notes: + * (1) If the intersection is at only one point (a corner), the + * coordinates are returned in (x1, y1). + * (2) Represent a vertical line by one with a large but finite slope. + * </pre> + */ +l_ok +boxIntersectByLine(const BOX *box, + l_int32 x, + l_int32 y, + l_float32 slope, + l_int32 *px1, + l_int32 *py1, + l_int32 *px2, + l_int32 *py2, + l_int32 *pn) +{ +l_int32 bx, by, bw, bh, xp, yp, xt, yt, i, n; +l_float32 invslope; +PTA *pta; + + if (px1) *px1 = 0; + if (px2) *px2 = 0; + if (py1) *py1 = 0; + if (py2) *py2 = 0; + if (pn) *pn = 0; + if (!px1 || !py1 || !px2 || !py2) + return ERROR_INT("&x1, &y1, &x2, &y2 not all defined", __func__, 1); + if (!pn) + return ERROR_INT("&n not defined", __func__, 1); + if (!box) + return ERROR_INT("box not defined", __func__, 1); + boxGetGeometry(box, &bx, &by, &bw, &bh); + if (bw == 0 || bh == 0) return 1; + + if (slope == 0.0) { + if (y >= by && y < by + bh) { + *py1 = *py2 = y; + *px1 = bx; + *px2 = bx + bw - 1; + } + return 0; + } + + if (slope > 1000000.0) { + if (x >= bx && x < bx + bw) { + *px1 = *px2 = x; + *py1 = by; + *py2 = by + bh - 1; + } + return 0; + } + + /* Intersection with top and bottom lines of box */ + pta = ptaCreate(2); + invslope = 1.0 / slope; + xp = (l_int32)(x + invslope * (y - by)); + if (xp >= bx && xp < bx + bw) + ptaAddPt(pta, xp, by); + xp = (l_int32)(x + invslope * (y - by - bh + 1)); + if (xp >= bx && xp < bx + bw) + ptaAddPt(pta, xp, by + bh - 1); + + /* Intersection with left and right lines of box */ + yp = (l_int32)(y + slope * (x - bx)); + if (yp >= by && yp < by + bh) + ptaAddPt(pta, bx, yp); + yp = (l_int32)(y + slope * (x - bx - bw + 1)); + if (yp >= by && yp < by + bh) + ptaAddPt(pta, bx + bw - 1, yp); + + /* There is a maximum of 2 unique points; remove duplicates. */ + n = ptaGetCount(pta); + if (n > 0) { + ptaGetIPt(pta, 0, px1, py1); /* accept the first one */ + *pn = 1; + } + for (i = 1; i < n; i++) { + ptaGetIPt(pta, i, &xt, &yt); + if ((*px1 != xt) || (*py1 != yt)) { + *px2 = xt; + *py2 = yt; + *pn = 2; + break; + } + } + + ptaDestroy(&pta); + return 0; +} + + +/*! + * \brief boxClipToRectangle() + * + * \param[in] box + * \param[in] wi, hi rectangle representing image + * \return part of box within given rectangle, or NULL on error + * or if box is entirely outside the rectangle + * + * <pre> + * Notes: + * (1) This can be used to clip a rectangle to an image. + * The clipping rectangle is assumed to have a UL corner at (0, 0), + * and a LR corner at (wi - 1, hi - 1). + * </pre> + */ +BOX * +boxClipToRectangle(BOX *box, + l_int32 wi, + l_int32 hi) +{ +BOX *boxd; + + if (!box) + return (BOX *)ERROR_PTR("box not defined", __func__, NULL); + if (box->x >= wi || box->y >= hi || + box->x + box->w <= 0 || box->y + box->h <= 0) + return (BOX *)ERROR_PTR("box outside rectangle", __func__, NULL); + + boxd = boxCopy(box); + if (boxd->x < 0) { + boxd->w += boxd->x; + boxd->x = 0; + } + if (boxd->y < 0) { + boxd->h += boxd->y; + boxd->y = 0; + } + if (boxd->x + boxd->w > wi) + boxd->w = wi - boxd->x; + if (boxd->y + boxd->h > hi) + boxd->h = hi - boxd->y; + return boxd; +} + + +/*! + * \brief boxClipToRectangleParams() + * + * \param[in] box [optional] requested box; can be null + * \param[in] w, h clipping box size; typ. the size of an image + * \param[out] pxstart start x coordinate + * \param[out] pystart start y coordinate + * \param[out] pxend one pixel beyond clipping box + * \param[out] pyend one pixel beyond clipping box + * \param[out] pbw [optional] clipped width + * \param[out] pbh [optional] clipped height + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) The return value should be checked. If it is 1, the + * returned parameter values are bogus. + * (2) This simplifies the selection of pixel locations within + * a given rectangle: + * for (i = ystart; i < yend; i++ { + * ... + * for (j = xstart; j < xend; j++ { + * .... + * </pre> + */ +l_ok +boxClipToRectangleParams(BOX *box, + l_int32 w, + l_int32 h, + l_int32 *pxstart, + l_int32 *pystart, + l_int32 *pxend, + l_int32 *pyend, + l_int32 *pbw, + l_int32 *pbh) +{ +l_int32 bw, bh; +BOX *boxc; + + if (pxstart) *pxstart = 0; + if (pystart) *pystart = 0; + if (pxend) *pxend = w; + if (pyend) *pyend = h; + if (pbw) *pbw = w; + if (pbh) *pbh = h; + if (!pxstart || !pystart || !pxend || !pyend) + return ERROR_INT("invalid ptr input", __func__, 1); + if (!box) return 0; + + if ((boxc = boxClipToRectangle(box, w, h)) == NULL) + return ERROR_INT("box outside image", __func__, 1); + boxGetGeometry(boxc, pxstart, pystart, &bw, &bh); + boxDestroy(&boxc); + + if (pbw) *pbw = bw; + if (pbh) *pbh = bh; + if (bw == 0 || bh == 0) + return ERROR_INT("invalid clipping box", __func__, 1); + *pxend = *pxstart + bw; /* 1 past the end */ + *pyend = *pystart + bh; /* 1 past the end */ + return 0; +} + + +/*! + * \brief boxRelocateOneSide() + * + * \param[in] boxd [optional]; this can be null, equal to boxs, + * or different from boxs; + * \param[in] boxs starting box; to have one side relocated + * \param[in] loc new location of the side that is changing + * \param[in] sideflag L_FROM_LEFT, etc., indicating the side that moves + * \return boxd, or NULL on error or if the computed boxd has + * width or height <= 0. + * + * <pre> + * Notes: + * (1) Set boxd == NULL to get new box; boxd == boxs for in-place; + * or otherwise to resize existing boxd. + * (2) For usage, suggest one of these: + * boxd = boxRelocateOneSide(NULL, boxs, ...); // new + * boxRelocateOneSide(boxs, boxs, ...); // in-place + * boxRelocateOneSide(boxd, boxs, ...); // other + * </pre> + */ +BOX * +boxRelocateOneSide(BOX *boxd, + BOX *boxs, + l_int32 loc, + l_int32 sideflag) +{ +l_int32 x, y, w, h; + + if (!boxs) + return (BOX *)ERROR_PTR("boxs not defined", __func__, NULL); + if (!boxd) + boxd = boxCopy(boxs); + + boxGetGeometry(boxs, &x, &y, &w, &h); + if (w == 0 || h == 0) + return boxd; + if (sideflag == L_FROM_LEFT) + boxSetGeometry(boxd, loc, -1, w + x - loc, -1); + else if (sideflag == L_FROM_RIGHT) + boxSetGeometry(boxd, -1, -1, loc - x + 1, -1); + else if (sideflag == L_FROM_TOP) + boxSetGeometry(boxd, -1, loc, -1, h + y - loc); + else if (sideflag == L_FROM_BOT) + boxSetGeometry(boxd, -1, -1, -1, loc - y + 1); + return boxd; +} + + +/*! + * \brief boxaAdjustSides() + * + * \param[in] boxas + * \param[in] delleft, delright, deltop, delbot changes in location of + * each side for each box + * \return boxad, or NULL on error + * + * <pre> + * Notes: + * (1) New box dimensions are cropped at left and top to x >= 0 and y >= 0. + * (2) If the width or height of a box goes to 0, we generate a box with + * w == 1 and h == 1, as a placeholder. + * (3) See boxAdjustSides(). + * </pre> + */ +BOXA * +boxaAdjustSides(BOXA *boxas, + l_int32 delleft, + l_int32 delright, + l_int32 deltop, + l_int32 delbot) +{ +l_int32 n, i, x, y; +BOX *box1, *box2; +BOXA *boxad; + + if (!boxas) + return (BOXA *)ERROR_PTR("boxas not defined", __func__, NULL); + + n = boxaGetCount(boxas); + boxad = boxaCreate(n); + for (i = 0; i < n; i++) { + box1 = boxaGetBox(boxas, i, L_COPY); + box2 = boxAdjustSides(NULL, box1, delleft, delright, deltop, delbot); + if (!box2) { + boxGetGeometry(box1, &x, &y, NULL, NULL); + box2 = boxCreate(x, y, 1, 1); + } + boxaAddBox(boxad, box2, L_INSERT); + boxDestroy(&box1); + } + + return boxad; +} + + +/*! + * \brief boxaAdjustBoxSides() + * + * \param[in] boxas + * \param[in] index + * \param[in] delleft, delright, deltop, delbot changes to box side locs + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) In-place operation on a box in a boxa. + * (2) New box dimensions are cropped at left and top to x >= 0 and y >= 0. + * (3) If a box ends up with no area, an error message is emitted, + * but the box dimensions are not changed. + * (4) See boxaAdjustSides(). + * </pre> + */ +l_ok +boxaAdjustBoxSides(BOXA *boxa, + l_int32 index, + l_int32 delleft, + l_int32 delright, + l_int32 deltop, + l_int32 delbot) +{ +BOX *box; + + if (!boxa) + return ERROR_INT("boxa not defined", __func__, 1); + + if ((box = boxaGetBox(boxa, index, L_CLONE)) == NULL) + return ERROR_INT("invalid index", __func__, 1); + + boxAdjustSides(box, box, delleft, delright, deltop, delbot); + boxDestroy(&box); /* the clone */ + return 0; +} + + +/*! + * \brief boxAdjustSides() + * + * \param[in] boxd [optional]; this can be null, equal to boxs, + * or different from boxs + * \param[in] boxs starting box; to have sides adjusted + * \param[in] delleft, delright, deltop, delbot changes in location + * of each side + * \return boxd, or NULL on error or if the computed boxd has + * width or height <= 0. + * + * <pre> + * Notes: + * (1) Set boxd == NULL to get new box; boxd == boxs for in-place; + * or otherwise to resize existing boxd. + * (2) For usage, suggest one of these: + * boxd = boxAdjustSides(NULL, boxs, ...); // new + * boxAdjustSides(boxs, boxs, ...); // in-place + * boxAdjustSides(boxd, boxs, ...); // other + * (3) New box dimensions are cropped at left and top to x >= 0 and y >= 0. + * (4) For example, to expand in-place by 20 pixels on each side, use + * boxAdjustSides(box, box, -20, 20, -20, 20); + * </pre> + */ +BOX * +boxAdjustSides(BOX *boxd, + BOX *boxs, + l_int32 delleft, + l_int32 delright, + l_int32 deltop, + l_int32 delbot) +{ +l_int32 x, y, w, h, xl, xr, yt, yb, wnew, hnew; + + if (!boxs) + return (BOX *)ERROR_PTR("boxs not defined", __func__, NULL); + + boxGetGeometry(boxs, &x, &y, &w, &h); + xl = L_MAX(0, x + delleft); + yt = L_MAX(0, y + deltop); + xr = x + w + delright; /* one pixel beyond right edge */ + yb = y + h + delbot; /* one pixel below bottom edge */ + wnew = xr - xl; + hnew = yb - yt; + + if (wnew < 1 || hnew < 1) + return (BOX *)ERROR_PTR("boxd has 0 area", __func__, NULL); + if (!boxd) + return boxCreate(xl, yt, wnew, hnew); + + boxSetGeometry(boxd, xl, yt, wnew, hnew); + return boxd; +} + + +/*! + * \brief boxaSetSide() + * + * \param[in] boxad use NULL to get a new one; same as boxas for in-place + * \param[in] boxas + * \param[in] side L_SET_LEFT, L_SET_RIGHT, L_SET_TOP, L_SET_BOT + * \param[in] val location to set for given side, for each box + * \param[in] thresh min abs difference to cause resetting to %val + * \return boxad, or NULL on error + * + * <pre> + * Notes: + * (1) Sets the given side of each box. Use boxad == NULL for a new + * boxa, and boxad == boxas for in-place. + * (2) Use one of these: + * boxad = boxaSetSide(NULL, boxas, ...); // new + * boxaSetSide(boxas, boxas, ...); // in-place + * </pre> + */ +BOXA * +boxaSetSide(BOXA *boxad, + BOXA *boxas, + l_int32 side, + l_int32 val, + l_int32 thresh) +{ +l_int32 n, i; +BOX *box; + + if (!boxas) + return (BOXA *)ERROR_PTR("boxas not defined", __func__, NULL); + if (boxad && (boxas != boxad)) + return (BOXA *)ERROR_PTR("not in-place", __func__, NULL); + if (side != L_SET_LEFT && side != L_SET_RIGHT && + side != L_SET_TOP && side != L_SET_BOT) + return (BOXA *)ERROR_PTR("invalid side", __func__, NULL); + if (val < 0) + return (BOXA *)ERROR_PTR("val < 0", __func__, NULL); + + if (!boxad) + boxad = boxaCopy(boxas, L_COPY); + n = boxaGetCount(boxad); + for (i = 0; i < n; i++) { + box = boxaGetBox(boxad, i, L_CLONE); + boxSetSide(box, side, val, thresh); + boxDestroy(&box); /* the clone */ + } + + return boxad; +} + + +/*! + * \brief boxSetSide() + * + * \param[in] boxs + * \param[in] side L_SET_LEFT, L_SET_RIGHT, L_SET_TOP, L_SET_BOT + * \param[in] val location to set for given side, for each box + * \param[in] thresh min abs difference to cause resetting to %val + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) In-place operation. + * (2) Use %thresh = 0 to definitely set the side to %val. + * </pre> + */ +l_ok +boxSetSide(BOX *boxs, + l_int32 side, + l_int32 val, + l_int32 thresh) +{ +l_int32 x, y, w, h, diff; + + if (!boxs) + return ERROR_INT("box not defined", __func__, 1); + if (side != L_SET_LEFT && side != L_SET_RIGHT && + side != L_SET_TOP && side != L_SET_BOT) + return ERROR_INT("invalid side", __func__, 1); + if (val < 0) + return ERROR_INT("val < 0", __func__, 1); + + boxGetGeometry(boxs, &x, &y, &w, &h); + if (side == L_SET_LEFT) { + diff = x - val; + if (L_ABS(diff) >= thresh) + boxSetGeometry(boxs, val, y, w + diff, h); + } else if (side == L_SET_RIGHT) { + diff = x + w -1 - val; + if (L_ABS(diff) >= thresh) + boxSetGeometry(boxs, x, y, val - x + 1, h); + } else if (side == L_SET_TOP) { + diff = y - val; + if (L_ABS(diff) >= thresh) + boxSetGeometry(boxs, x, val, w, h + diff); + } else { /* side == L_SET_BOT */ + diff = y + h - 1 - val; + if (L_ABS(diff) >= thresh) + boxSetGeometry(boxs, x, y, w, val - y + 1); + } + + return 0; +} + + +/*! + * \brief boxaAdjustWidthToTarget() + * + * \param[in] boxad use NULL to get a new one; same as boxas for in-place + * \param[in] boxas + * \param[in] sides L_ADJUST_LEFT, L_ADJUST_RIGHT, L_ADJUST_LEFT_AND_RIGHT + * \param[in] target target width if differs by more than thresh + * \param[in] thresh min abs difference in width to cause adjustment + * \return boxad, or NULL on error + * + * <pre> + * Notes: + * (1) Conditionally adjusts the width of each box, by moving + * the indicated edges (left and/or right) if the width differs + * by %thresh or more from %target. + * (2) Use boxad == NULL for a new boxa, and boxad == boxas for in-place. + * Use one of these: + * boxad = boxaAdjustWidthToTarget(NULL, boxas, ...); // new + * boxaAdjustWidthToTarget(boxas, boxas, ...); // in-place + * </pre> + */ +BOXA * +boxaAdjustWidthToTarget(BOXA *boxad, + BOXA *boxas, + l_int32 sides, + l_int32 target, + l_int32 thresh) +{ +l_int32 x, y, w, h, n, i, diff; +BOX *box; + + if (!boxas) + return (BOXA *)ERROR_PTR("boxas not defined", __func__, NULL); + if (boxad && (boxas != boxad)) + return (BOXA *)ERROR_PTR("not in-place", __func__, NULL); + if (sides != L_ADJUST_LEFT && sides != L_ADJUST_RIGHT && + sides != L_ADJUST_LEFT_AND_RIGHT) + return (BOXA *)ERROR_PTR("invalid sides", __func__, NULL); + if (target < 1) + return (BOXA *)ERROR_PTR("target < 1", __func__, NULL); + + if (!boxad) + boxad = boxaCopy(boxas, L_COPY); + n = boxaGetCount(boxad); + for (i = 0; i < n; i++) { + if ((box = boxaGetValidBox(boxad, i, L_CLONE)) == NULL) + continue; + boxGetGeometry(box, &x, &y, &w, &h); + diff = w - target; + if (sides == L_ADJUST_LEFT) { + if (L_ABS(diff) >= thresh) + boxSetGeometry(box, L_MAX(0, x + diff), y, target, h); + } else if (sides == L_ADJUST_RIGHT) { + if (L_ABS(diff) >= thresh) + boxSetGeometry(box, x, y, target, h); + } else { /* sides == L_ADJUST_LEFT_AND_RIGHT */ + if (L_ABS(diff) >= thresh) + boxSetGeometry(box, L_MAX(0, x + diff/2), y, target, h); + } + boxDestroy(&box); + } + + return boxad; +} + + +/*! + * \brief boxaAdjustHeightToTarget() + * + * \param[in] boxad use NULL to get a new one + * \param[in] boxas + * \param[in] sides L_ADJUST_TOP, L_ADJUST_BOT, L_ADJUST_TOP_AND_BOT + * \param[in] target target height if differs by more than thresh + * \param[in] thresh min abs difference in height to cause adjustment + * \return boxad, or NULL on error + * + * <pre> + * Notes: + * (1) Conditionally adjusts the height of each box, by moving + * the indicated edges (top and/or bot) if the height differs + * by %thresh or more from %target. + * (2) Use boxad == NULL for a new boxa, and boxad == boxas for in-place. + * Use one of these: + * boxad = boxaAdjustHeightToTarget(NULL, boxas, ...); // new + * boxaAdjustHeightToTarget(boxas, boxas, ...); // in-place + * </pre> + */ +BOXA * +boxaAdjustHeightToTarget(BOXA *boxad, + BOXA *boxas, + l_int32 sides, + l_int32 target, + l_int32 thresh) +{ +l_int32 x, y, w, h, n, i, diff; +BOX *box; + + if (!boxas) + return (BOXA *)ERROR_PTR("boxas not defined", __func__, NULL); + if (boxad && (boxas != boxad)) + return (BOXA *)ERROR_PTR("not in-place", __func__, NULL); + if (sides != L_ADJUST_TOP && sides != L_ADJUST_BOT && + sides != L_ADJUST_TOP_AND_BOT) + return (BOXA *)ERROR_PTR("invalid sides", __func__, NULL); + if (target < 1) + return (BOXA *)ERROR_PTR("target < 1", __func__, NULL); + + if (!boxad) + boxad = boxaCopy(boxas, L_COPY); + n = boxaGetCount(boxad); + for (i = 0; i < n; i++) { + if ((box = boxaGetValidBox(boxad, i, L_CLONE)) == NULL) + continue; + boxGetGeometry(box, &x, &y, &w, &h); + diff = h - target; + if (sides == L_ADJUST_TOP) { + if (L_ABS(diff) >= thresh) + boxSetGeometry(box, x, L_MAX(0, y + diff), w, target); + } else if (sides == L_ADJUST_BOT) { + if (L_ABS(diff) >= thresh) + boxSetGeometry(box, x, y, w, target); + } else { /* sides == L_ADJUST_TOP_AND_BOT */ + if (L_ABS(diff) >= thresh) + boxSetGeometry(box, x, L_MAX(0, y + diff/2), w, target); + } + boxDestroy(&box); + } + + return boxad; +} + + +/*! + * \brief boxEqual() + * + * \param[in] box1 + * \param[in] box2 + * \param[out] psame 1 if equal; 0 otherwise + * \return 0 if OK, 1 on error + */ +l_ok +boxEqual(BOX *box1, + BOX *box2, + l_int32 *psame) +{ + if (!psame) + return ERROR_INT("&same not defined", __func__, 1); + *psame = 0; + if (!box1 || !box2) + return ERROR_INT("boxes not both defined", __func__, 1); + if (box1->x == box2->x && box1->y == box2->y && + box1->w == box2->w && box1->h == box2->h) + *psame = 1; + return 0; +} + + +/*! + * \brief boxaEqual() + * + * \param[in] boxa1 + * \param[in] boxa2 + * \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 + * + * <pre> + * Notes: + * (1) The two boxa are the "same" if they contain the same + * boxes and each box is within %maxdist of its counterpart + * in their positions within the boxa. This allows for + * small rearrangements. Use 0 for maxdist if the boxa + * must be identical. + * (2) This applies only to geometry and ordering; refcounts + * are not considered. + * (3) %maxdist allows some latitude in the ordering of the boxes. + * For the boxa to be the "same", corresponding boxes must + * be within %maxdist of each other. Note that for large + * %maxdist, we should use a hash function for efficiency. + * (4) naindex[i] gives the position of the box in boxa2 that + * corresponds to box i in boxa1. It is only returned if the + * boxa are equal. + * </pre> + */ +l_ok +boxaEqual(BOXA *boxa1, + BOXA *boxa2, + l_int32 maxdist, + NUMA **pnaindex, + l_int32 *psame) +{ +l_int32 i, j, n, jstart, jend, found, samebox; +l_int32 *countarray; +BOX *box1, *box2; +NUMA *na; + + if (pnaindex) *pnaindex = NULL; + if (!psame) + return ERROR_INT("&same not defined", __func__, 1); + *psame = 0; + if (!boxa1 || !boxa2) + return ERROR_INT("boxa1 and boxa2 not both defined", __func__, 1); + n = boxaGetCount(boxa1); + if (n != boxaGetCount(boxa2)) + return 0; + + if ((countarray = (l_int32 *)LEPT_CALLOC(n, sizeof(l_int32))) == NULL) + return ERROR_INT("calloc fail for countarray", __func__, 1); + na = numaMakeConstant(0.0, n); + + for (i = 0; i < n; i++) { + box1 = boxaGetBox(boxa1, i, L_CLONE); + jstart = L_MAX(0, i - maxdist); + jend = L_MIN(n-1, i + maxdist); + found = FALSE; + for (j = jstart; j <= jend; j++) { + box2 = boxaGetBox(boxa2, j, L_CLONE); + boxEqual(box1, box2, &samebox); + if (samebox && countarray[j] == 0) { + countarray[j] = 1; + numaReplaceNumber(na, i, j); + found = TRUE; + boxDestroy(&box2); + break; + } + boxDestroy(&box2); + } + boxDestroy(&box1); + if (!found) { + numaDestroy(&na); + LEPT_FREE(countarray); + return 0; + } + } + + *psame = 1; + if (pnaindex) + *pnaindex = na; + else + numaDestroy(&na); + LEPT_FREE(countarray); + return 0; +} + + +/*! + * \brief boxSimilar() + * + * \param[in] box1 + * \param[in] box2 + * \param[in] leftdiff, rightdiff, topdiff, botdiff + * \param[out] psimilar 1 if similar; 0 otherwise + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) The values of leftdiff (etc) are the maximum allowed deviations + * between the locations of the left (etc) sides. If any side + * pairs differ by more than this amount, the boxes are not similar. + * </pre> + */ +l_ok +boxSimilar(BOX *box1, + BOX *box2, + l_int32 leftdiff, + l_int32 rightdiff, + l_int32 topdiff, + l_int32 botdiff, + l_int32 *psimilar) +{ +l_int32 l1, l2, r1, r2, t1, t2, b1, b2, valid1, valid2; + + if (!psimilar) + return ERROR_INT("&similar not defined", __func__, 1); + *psimilar = 0; + if (!box1 || !box2) + return ERROR_INT("boxes not both defined", __func__, 1); + boxIsValid(box1, &valid1); + boxIsValid(box2, &valid2); + if (!valid1 || !valid2) + return ERROR_INT("boxes not both valid", __func__, 1); + + boxGetSideLocations(box1, &l1, &r1, &t1, &b1); + boxGetSideLocations(box2, &l2, &r2, &t2, &b2); + if (L_ABS(l1 - l2) > leftdiff) + return 0; + if (L_ABS(r1 - r2) > rightdiff) + return 0; + if (L_ABS(t1 - t2) > topdiff) + return 0; + if (L_ABS(b1 - b2) > botdiff) + return 0; + + *psimilar = 1; + return 0; +} + + +/*! + * \brief boxaSimilar() + * + * \param[in] boxa1 + * \param[in] boxa2 + * \param[in] leftdiff, rightdiff, topdiff, botdiff + * \param[in] debug output details of non-similar boxes + * \param[out] psimilar 1 if similar; 0 otherwise + * \param[out] pnasim [optional] na containing 1 if similar; else 0 + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) See boxSimilar() for parameter usage. + * (2) Corresponding boxes are taken in order in the two boxa. + * (3) %nasim is an indicator array with a (0/1) for each box pair. + * (4) With %nasim or debug == 1, boxes continue to be tested + * after failure. + * </pre> + */ +l_ok +boxaSimilar(BOXA *boxa1, + BOXA *boxa2, + l_int32 leftdiff, + l_int32 rightdiff, + l_int32 topdiff, + l_int32 botdiff, + l_int32 debug, + l_int32 *psimilar, + NUMA **pnasim) +{ +l_int32 i, n1, n2, match, mismatch; +BOX *box1, *box2; + + if (psimilar) *psimilar = 0; + if (pnasim) *pnasim = NULL; + if (!boxa1 || !boxa2) + return ERROR_INT("boxa1 and boxa2 not both defined", __func__, 1); + if (!psimilar) + return ERROR_INT("&similar not defined", __func__, 1); + n1 = boxaGetCount(boxa1); + n2 = boxaGetCount(boxa2); + if (n1 != n2) { + L_ERROR("boxa counts differ: %d vs %d\n", __func__, n1, n2); + return 1; + } + if (pnasim) *pnasim = numaCreate(n1); + + mismatch = FALSE; + for (i = 0; i < n1; i++) { + box1 = boxaGetBox(boxa1, i, L_CLONE); + box2 = boxaGetBox(boxa2, i, L_CLONE); + boxSimilar(box1, box2, leftdiff, rightdiff, topdiff, botdiff, + &match); + boxDestroy(&box1); + boxDestroy(&box2); + if (pnasim) + numaAddNumber(*pnasim, match); + if (!match) { + mismatch = TRUE; + if (!debug && pnasim == NULL) + return 0; + else if (debug) + L_INFO("box %d not similar\n", __func__, i); + } + } + + if (!mismatch) *psimilar = 1; + return 0; +} + + +/*----------------------------------------------------------------------* + * Boxa combine and split * + *----------------------------------------------------------------------*/ +/*! + * \brief boxaJoin() + * + * \param[in] boxad dest boxa; add to this one + * \param[in] boxas source boxa; add from this one + * \param[in] istart starting index in boxas + * \param[in] iend ending index in boxas; use -1 to cat all + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This appends a clone of each indicated box in boxas to boxad + * (2) istart < 0 is taken to mean 'read from the start' (istart = 0) + * (3) iend < 0 means 'read to the end' + * (4) if boxas == NULL or has no boxes, this is a no-op. + * </pre> + */ +l_ok +boxaJoin(BOXA *boxad, + BOXA *boxas, + l_int32 istart, + l_int32 iend) +{ +l_int32 n, i; +BOX *box; + + if (!boxad) + return ERROR_INT("boxad not defined", __func__, 1); + if (!boxas || ((n = boxaGetCount(boxas)) == 0)) + return 0; + + if (istart < 0) + istart = 0; + if (iend < 0 || iend >= n) + iend = n - 1; + if (istart > iend) + return ERROR_INT("istart > iend; nothing to add", __func__, 1); + + for (i = istart; i <= iend; i++) { + box = boxaGetBox(boxas, i, L_CLONE); + boxaAddBox(boxad, box, L_INSERT); + } + + return 0; +} + + +/*! + * \brief boxaaJoin() + * + * \param[in] baad dest boxaa; add to this one + * \param[in] baas source boxaa; add from this one + * \param[in] istart starting index in baas + * \param[in] iend ending index in baas; use -1 to cat all + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This appends a clone of each indicated boxa in baas to baad + * (2) istart < 0 is taken to mean 'read from the start' (istart = 0) + * (3) iend < 0 means 'read to the end' + * (4) if baas == NULL, this is a no-op. + * </pre> + */ +l_ok +boxaaJoin(BOXAA *baad, + BOXAA *baas, + l_int32 istart, + l_int32 iend) +{ +l_int32 n, i; +BOXA *boxa; + + if (!baad) + return ERROR_INT("baad not defined", __func__, 1); + if (!baas) + return 0; + + if (istart < 0) + istart = 0; + n = boxaaGetCount(baas); + if (iend < 0 || iend >= n) + iend = n - 1; + if (istart > iend) + return ERROR_INT("istart > iend; nothing to add", __func__, 1); + + for (i = istart; i <= iend; i++) { + boxa = boxaaGetBoxa(baas, i, L_CLONE); + boxaaAddBoxa(baad, boxa, L_INSERT); + } + + return 0; +} + + +/*! + * \brief boxaSplitEvenOdd() + * + * \param[in] boxa + * \param[in] fillflag 1 to put invalid boxes in place; 0 to omit + * \param[out] pboxae, pboxao save even and odd boxes in their separate + * boxa, setting the other type to invalid boxes. + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) If %fillflag == 1, boxae has copies of the even boxes + * in their original location, and nvalid boxes are placed + * in the odd array locations. And v.v. + * (2) If %fillflag == 0, boxae has only copies of the even boxes. + * </pre> + */ +l_ok +boxaSplitEvenOdd(BOXA *boxa, + l_int32 fillflag, + BOXA **pboxae, + BOXA **pboxao) +{ +l_int32 i, n; +BOX *box, *box1; + + if (pboxae) *pboxae = NULL; + if (pboxao) *pboxao = NULL; + if (!pboxae || !pboxao) + return ERROR_INT("&boxae and &boxao not both defined", __func__, 1); + if (!boxa) + return ERROR_INT("boxa not defined", __func__, 1); + + n = boxaGetCount(boxa); + *pboxae = boxaCreate(n); + *pboxao = boxaCreate(n); + if (fillflag == 0) { + /* don't fill with invalid boxes; end up with half-size boxa */ + for (i = 0; i < n; i++) { + box = boxaGetBox(boxa, i, L_COPY); + if ((i & 1) == 0) + boxaAddBox(*pboxae, box, L_INSERT); + else + boxaAddBox(*pboxao, box, L_INSERT); + } + } else { + for (i = 0; i < n; i++) { + box = boxaGetBox(boxa, i, L_COPY); + box1 = boxCreate(0, 0, 0, 0); /* empty placeholder */ + if ((i & 1) == 0) { + boxaAddBox(*pboxae, box, L_INSERT); + boxaAddBox(*pboxao, box1, L_INSERT); + } else { + boxaAddBox(*pboxae, box1, L_INSERT); + boxaAddBox(*pboxao, box, L_INSERT); + } + } + } + return 0; +} + + +/*! + * \brief boxaMergeEvenOdd() + * + * \param[in] boxae boxes to go in even positions in merged boxa + * \param[in] boxao boxes to go in odd positions in merged boxa + * \param[in] fillflag 1 if there are invalid boxes in placeholders + * \return boxad merged, or NULL on error + * + * <pre> + * Notes: + * (1) This is essentially the inverse of boxaSplitEvenOdd(). + * Typically, boxae and boxao were generated by boxaSplitEvenOdd(), + * and the value of %fillflag needs to be the same in both calls. + * (2) If %fillflag == 1, both boxae and boxao are of the same size; + * otherwise boxae may have one more box than boxao. + * </pre> + */ +BOXA * +boxaMergeEvenOdd(BOXA *boxae, + BOXA *boxao, + l_int32 fillflag) +{ +l_int32 i, n, ne, no; +BOX *box; +BOXA *boxad; + + if (!boxae || !boxao) + return (BOXA *)ERROR_PTR("boxae and boxao not defined", __func__, NULL); + ne = boxaGetCount(boxae); + no = boxaGetCount(boxao); + if (ne < no || ne > no + 1) + return (BOXA *)ERROR_PTR("boxa sizes invalid", __func__, NULL); + + boxad = boxaCreate(ne); + if (fillflag == 0) { /* both are approx. half-sized; all valid boxes */ + n = ne + no; + for (i = 0; i < n; i++) { + if ((i & 1) == 0) + box = boxaGetBox(boxae, i / 2, L_COPY); + else + box = boxaGetBox(boxao, i / 2, L_COPY); + boxaAddBox(boxad, box, L_INSERT); + } + } else { /* both are full size and have invalid placeholders */ + for (i = 0; i < ne; i++) { + if ((i & 1) == 0) + box = boxaGetBox(boxae, i, L_COPY); + else + box = boxaGetBox(boxao, i, L_COPY); + boxaAddBox(boxad, box, L_INSERT); + } + } + return boxad; +}
