/*====================================================================* - 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 boxfunc3.c *
 *
 *      Boxa/Boxaa painting into pix
 *           PIX             *pixMaskConnComp()
 *           PIX             *pixMaskBoxa()
 *           PIX             *pixPaintBoxa()
 *           PIX             *pixSetBlackOrWhiteBoxa()
 *           PIX             *pixPaintBoxaRandom()
 *           PIX             *pixBlendBoxaRandom()
 *           PIX             *pixDrawBoxa()
 *           PIX             *pixDrawBoxaRandom()
 *           PIX             *boxaaDisplay()
 *           PIXA            *pixaDisplayBoxaa()
 *
 *      Split mask components into Boxa
 *           BOXA            *pixSplitIntoBoxa()
 *           BOXA            *pixSplitComponentIntoBoxa()
 *           static l_int32   pixSearchForRectangle()
 *
 *      Represent horizontal or vertical mosaic strips
 *           BOXA            *makeMosaicStrips()
 *
 *      Comparison between boxa
 *           l_int32          boxaCompareRegions()
 *
 *      Reliable selection of a single large box
 *           BOX             *pixSelectLargeULComp()
 *           BOX             *boxaSelectLargeULBox()
 *
 *  See summary in pixPaintBoxa() of various ways to paint and draw
 *  boxes on images.
 * 
*/ #ifdef HAVE_CONFIG_H #include #endif /* HAVE_CONFIG_H */ #include "allheaders.h" static l_int32 pixSearchForRectangle(PIX *pixs, BOX *boxs, l_int32 minsum, l_int32 skipdist, l_int32 delta, l_int32 maxbg, l_int32 sideflag, BOXA *boxat, NUMA *nascore); #ifndef NO_CONSOLE_IO #define DEBUG_SPLIT 0 #endif /* ~NO_CONSOLE_IO */ /*---------------------------------------------------------------------* * Boxa/Boxaa painting into Pix * *---------------------------------------------------------------------*/ /*! * \brief pixMaskConnComp() * * \param[in] pixs 1 bpp * \param[in] connectivity 4 or 8 * \param[out] pboxa [optional] bounding boxes of c.c. * \return pixd 1 bpp mask over the c.c., or NULL on error * *
 * Notes:
 *      (1) This generates a mask image with ON pixels over the
 *          b.b. of the c.c. in pixs.  If there are no ON pixels in pixs,
 *          pixd will also have no ON pixels.
 * 
*/ PIX * pixMaskConnComp(PIX *pixs, l_int32 connectivity, BOXA **pboxa) { BOXA *boxa; PIX *pixd; PROCNAME("pixMaskConnComp"); if (pboxa) *pboxa = NULL; if (!pixs || pixGetDepth(pixs) != 1) return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); if (connectivity != 4 && connectivity != 8) return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); boxa = pixConnComp(pixs, NULL, connectivity); pixd = pixCreateTemplate(pixs); if (boxaGetCount(boxa) != 0) pixMaskBoxa(pixd, pixd, boxa, L_SET_PIXELS); if (pboxa) *pboxa = boxa; else boxaDestroy(&boxa); return pixd; } /*! * \brief pixMaskBoxa() * * \param[in] pixd [optional] may be NULL * \param[in] pixs any depth; not cmapped * \param[in] boxa of boxes, to paint * \param[in] op L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS * \return pixd with masking op over the boxes, or NULL on error * *
 * Notes:
 *      (1) This can be used with:
 *              pixd = NULL  (makes a new pixd)
 *              pixd = pixs  (in-place)
 *      (2) If pixd == NULL, this first makes a copy of pixs, and then
 *          bit-twiddles over the boxes.  Otherwise, it operates directly
 *          on pixs.
 *      (3) This simple function is typically used with 1 bpp images.
 *          It uses the 1-image rasterop function, rasteropUniLow(),
 *          to set, clear or flip the pixels in pixd.
 *      (4) If you want to generate a 1 bpp mask of ON pixels from the boxes
 *          in a Boxa, in a pix of size (w,h):
 *              pix = pixCreate(w, h, 1);
 *              pixMaskBoxa(pix, pix, boxa, L_SET_PIXELS);
 * 
*/ PIX * pixMaskBoxa(PIX *pixd, PIX *pixs, BOXA *boxa, l_int32 op) { l_int32 i, n, x, y, w, h; BOX *box; PROCNAME("pixMaskBoxa"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetColormap(pixs)) return (PIX *)ERROR_PTR("pixs is cmapped", procName, NULL); if (pixd && (pixd != pixs)) return (PIX *)ERROR_PTR("if pixd, must be in-place", procName, NULL); if (!boxa) return (PIX *)ERROR_PTR("boxa not defined", procName, NULL); if (op != L_SET_PIXELS && op != L_CLEAR_PIXELS && op != L_FLIP_PIXELS) return (PIX *)ERROR_PTR("invalid op", procName, NULL); pixd = pixCopy(pixd, pixs); if ((n = boxaGetCount(boxa)) == 0) { L_WARNING("no boxes to mask\n", procName); return pixd; } for (i = 0; i < n; i++) { box = boxaGetBox(boxa, i, L_CLONE); boxGetGeometry(box, &x, &y, &w, &h); if (op == L_SET_PIXELS) pixRasterop(pixd, x, y, w, h, PIX_SET, NULL, 0, 0); else if (op == L_CLEAR_PIXELS) pixRasterop(pixd, x, y, w, h, PIX_CLR, NULL, 0, 0); else /* op == L_FLIP_PIXELS */ pixRasterop(pixd, x, y, w, h, PIX_NOT(PIX_DST), NULL, 0, 0); boxDestroy(&box); } return pixd; } /*! * \brief pixPaintBoxa() * * \param[in] pixs any depth, can be cmapped * \param[in] boxa of boxes, to paint * \param[in] val rgba color to paint * \return pixd with painted boxes, or NULL on error * *
 * Notes:
 *      (1) If pixs is 1 bpp or is colormapped, it is converted to 8 bpp
 *          and the boxa is painted using a colormap; otherwise,
 *          it is converted to 32 bpp rgb.
 *      (2) There are several ways to display a box on an image:
 *            * Paint it as a solid color
 *            * Draw the outline
 *            * Blend the outline or region with the existing image
 *          We provide painting and drawing here; blending is in blend.c.
 *          When painting or drawing, the result can be either a
 *          cmapped image or an rgb image.  The dest will be cmapped
 *          if the src is either 1 bpp or has a cmap that is not full.
 *          To force RGB output, use pixConvertTo8(pixs, FALSE)
 *          before calling any of these paint and draw functions.
 * 
*/ PIX * pixPaintBoxa(PIX *pixs, BOXA *boxa, l_uint32 val) { l_int32 i, n, d, rval, gval, bval, newindex; l_int32 mapvacancy; /* true only if cmap and not full */ BOX *box; PIX *pixd; PIXCMAP *cmap; PROCNAME("pixPaintBoxa"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (!boxa) return (PIX *)ERROR_PTR("boxa not defined", procName, NULL); if ((n = boxaGetCount(boxa)) == 0) { L_WARNING("no boxes to paint; returning a copy\n", procName); return pixCopy(NULL, pixs); } mapvacancy = FALSE; if ((cmap = pixGetColormap(pixs)) != NULL) { if (pixcmapGetCount(cmap) < 256) mapvacancy = TRUE; } if (pixGetDepth(pixs) == 1 || mapvacancy) pixd = pixConvertTo8(pixs, TRUE); else pixd = pixConvertTo32(pixs); if (!pixd) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); d = pixGetDepth(pixd); if (d == 8) { /* colormapped */ cmap = pixGetColormap(pixd); extractRGBValues(val, &rval, &gval, &bval); if (pixcmapAddNewColor(cmap, rval, gval, bval, &newindex)) { pixDestroy(&pixd); return (PIX *)ERROR_PTR("cmap full; can't add", procName, NULL); } } for (i = 0; i < n; i++) { box = boxaGetBox(boxa, i, L_CLONE); if (d == 8) pixSetInRectArbitrary(pixd, box, newindex); else pixSetInRectArbitrary(pixd, box, val); boxDestroy(&box); } return pixd; } /*! * \brief pixSetBlackOrWhiteBoxa() * * \param[in] pixs any depth, can be cmapped * \param[in] boxa [optional] of boxes, to clear or set * \param[in] op L_SET_BLACK, L_SET_WHITE * \return pixd with boxes filled with white or black, or NULL on error */ PIX * pixSetBlackOrWhiteBoxa(PIX *pixs, BOXA *boxa, l_int32 op) { l_int32 i, n, d, index; l_uint32 color; BOX *box; PIX *pixd; PIXCMAP *cmap; PROCNAME("pixSetBlackOrWhiteBoxa"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (!boxa) return pixCopy(NULL, pixs); if ((n = boxaGetCount(boxa)) == 0) return pixCopy(NULL, pixs); pixd = pixCopy(NULL, pixs); d = pixGetDepth(pixd); if (d == 1) { for (i = 0; i < n; i++) { box = boxaGetBox(boxa, i, L_CLONE); if (op == L_SET_WHITE) pixClearInRect(pixd, box); else pixSetInRect(pixd, box); boxDestroy(&box); } return pixd; } cmap = pixGetColormap(pixs); if (cmap) { color = (op == L_SET_WHITE) ? 1 : 0; pixcmapAddBlackOrWhite(cmap, color, &index); } else if (d == 8) { color = (op == L_SET_WHITE) ? 0xff : 0x0; } else if (d == 32) { color = (op == L_SET_WHITE) ? 0xffffff00 : 0x0; } else if (d == 2) { color = (op == L_SET_WHITE) ? 0x3 : 0x0; } else if (d == 4) { color = (op == L_SET_WHITE) ? 0xf : 0x0; } else if (d == 16) { color = (op == L_SET_WHITE) ? 0xffff : 0x0; } else { pixDestroy(&pixd); return (PIX *)ERROR_PTR("invalid depth", procName, NULL); } for (i = 0; i < n; i++) { box = boxaGetBox(boxa, i, L_CLONE); if (cmap) pixSetInRectArbitrary(pixd, box, index); else pixSetInRectArbitrary(pixd, box, color); boxDestroy(&box); } return pixd; } /*! * \brief pixPaintBoxaRandom() * * \param[in] pixs any depth, can be cmapped * \param[in] boxa of boxes, to paint * \return pixd with painted boxes, or NULL on error * *
 * Notes:
 *      (1) If pixs is 1 bpp, we paint the boxa using a colormap;
 *          otherwise, we convert to 32 bpp.
 *      (2) We use up to 254 different colors for painting the regions.
 *      (3) If boxes overlap, the later ones paint over earlier ones.
 * 
*/ PIX * pixPaintBoxaRandom(PIX *pixs, BOXA *boxa) { l_int32 i, n, d, rval, gval, bval, index; l_uint32 val; BOX *box; PIX *pixd; PIXCMAP *cmap; PROCNAME("pixPaintBoxaRandom"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (!boxa) return (PIX *)ERROR_PTR("boxa not defined", procName, NULL); if ((n = boxaGetCount(boxa)) == 0) { L_WARNING("no boxes to paint; returning a copy\n", procName); return pixCopy(NULL, pixs); } if (pixGetDepth(pixs) == 1) pixd = pixConvert1To8(NULL, pixs, 255, 0); else pixd = pixConvertTo32(pixs); if (!pixd) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); cmap = pixcmapCreateRandom(8, 1, 1); d = pixGetDepth(pixd); /* either 8 or 32 */ if (d == 8) /* colormapped */ pixSetColormap(pixd, cmap); for (i = 0; i < n; i++) { box = boxaGetBox(boxa, i, L_CLONE); index = 1 + (i % 254); if (d == 8) { pixSetInRectArbitrary(pixd, box, index); } else { /* d == 32 */ pixcmapGetColor(cmap, index, &rval, &gval, &bval); composeRGBPixel(rval, gval, bval, &val); pixSetInRectArbitrary(pixd, box, val); } boxDestroy(&box); } if (d == 32) pixcmapDestroy(&cmap); return pixd; } /*! * \brief pixBlendBoxaRandom() * * \param[in] pixs any depth; can be cmapped * \param[in] boxa of boxes, to blend/paint * \param[in] fract of box color to use * \return pixd 32 bpp, with blend/painted boxes, or NULL on error * *
 * Notes:
 *      (1) pixs is converted to 32 bpp.
 *      (2) This differs from pixPaintBoxaRandom(), in that the
 *          colors here are blended with the color of pixs.
 *      (3) We use up to 254 different colors for painting the regions.
 *      (4) If boxes overlap, the final color depends only on the last
 *          rect that is used.
 * 
*/ PIX * pixBlendBoxaRandom(PIX *pixs, BOXA *boxa, l_float32 fract) { l_int32 i, n, rval, gval, bval, index; l_uint32 val; BOX *box; PIX *pixd; PIXCMAP *cmap; PROCNAME("pixBlendBoxaRandom"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (!boxa) return (PIX *)ERROR_PTR("boxa not defined", procName, NULL); if (fract < 0.0 || fract > 1.0) { L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5\n", procName); fract = 0.5; } if ((n = boxaGetCount(boxa)) == 0) { L_WARNING("no boxes to paint; returning a copy\n", procName); return pixCopy(NULL, pixs); } if ((pixd = pixConvertTo32(pixs)) == NULL) return (PIX *)ERROR_PTR("pixd not defined", procName, NULL); cmap = pixcmapCreateRandom(8, 1, 1); for (i = 0; i < n; i++) { box = boxaGetBox(boxa, i, L_CLONE); index = 1 + (i % 254); pixcmapGetColor(cmap, index, &rval, &gval, &bval); composeRGBPixel(rval, gval, bval, &val); pixBlendInRect(pixd, box, val, fract); boxDestroy(&box); } pixcmapDestroy(&cmap); return pixd; } /*! * \brief pixDrawBoxa() * * \param[in] pixs any depth; can be cmapped * \param[in] boxa of boxes, to draw * \param[in] width of lines * \param[in] val rgba color to draw * \return pixd with outlines of boxes added, or NULL on error * *
 * Notes:
 *      (1) If pixs is 1 bpp or is colormapped, it is converted to 8 bpp
 *          and the boxa is drawn using a colormap; otherwise,
 *          it is converted to 32 bpp rgb.
 * 
*/ PIX * pixDrawBoxa(PIX *pixs, BOXA *boxa, l_int32 width, l_uint32 val) { l_int32 rval, gval, bval, newindex; l_int32 mapvacancy; /* true only if cmap and not full */ PIX *pixd; PIXCMAP *cmap; PROCNAME("pixDrawBoxa"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (!boxa) return (PIX *)ERROR_PTR("boxa not defined", procName, NULL); if (width < 1) return (PIX *)ERROR_PTR("width must be >= 1", procName, NULL); if (boxaGetCount(boxa) == 0) { L_WARNING("no boxes to draw; returning a copy\n", procName); return pixCopy(NULL, pixs); } mapvacancy = FALSE; if ((cmap = pixGetColormap(pixs)) != NULL) { if (pixcmapGetCount(cmap) < 256) mapvacancy = TRUE; } if (pixGetDepth(pixs) == 1 || mapvacancy) pixd = pixConvertTo8(pixs, TRUE); else pixd = pixConvertTo32(pixs); if (!pixd) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); extractRGBValues(val, &rval, &gval, &bval); if (pixGetDepth(pixd) == 8) { /* colormapped */ cmap = pixGetColormap(pixd); pixcmapAddNewColor(cmap, rval, gval, bval, &newindex); } pixRenderBoxaArb(pixd, boxa, width, rval, gval, bval); return pixd; } /*! * \brief pixDrawBoxaRandom() * * \param[in] pixs any depth, can be cmapped * \param[in] boxa of boxes, to draw * \param[in] width thickness of line * \return pixd with box outlines drawn, or NULL on error * *
 * Notes:
 *      (1) If pixs is 1 bpp, we draw the boxa using a colormap;
 *          otherwise, we convert to 32 bpp.
 *      (2) We use up to 254 different colors for drawing the boxes.
 *      (3) If boxes overlap, the later ones draw over earlier ones.
 * 
*/ PIX * pixDrawBoxaRandom(PIX *pixs, BOXA *boxa, l_int32 width) { l_int32 i, n, rval, gval, bval, index; BOX *box; PIX *pixd; PIXCMAP *cmap; PTAA *ptaa; PROCNAME("pixDrawBoxaRandom"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (!boxa) return (PIX *)ERROR_PTR("boxa not defined", procName, NULL); if (width < 1) return (PIX *)ERROR_PTR("width must be >= 1", procName, NULL); if ((n = boxaGetCount(boxa)) == 0) { L_WARNING("no boxes to draw; returning a copy\n", procName); return pixCopy(NULL, pixs); } /* Input depth = 1 bpp; generate cmapped output */ if (pixGetDepth(pixs) == 1) { ptaa = generatePtaaBoxa(boxa); pixd = pixRenderRandomCmapPtaa(pixs, ptaa, 1, width, 1); ptaaDestroy(&ptaa); return pixd; } /* Generate rgb output */ pixd = pixConvertTo32(pixs); cmap = pixcmapCreateRandom(8, 1, 1); for (i = 0; i < n; i++) { box = boxaGetBox(boxa, i, L_CLONE); index = 1 + (i % 254); pixcmapGetColor(cmap, index, &rval, &gval, &bval); pixRenderBoxArb(pixd, box, width, rval, gval, bval); boxDestroy(&box); } pixcmapDestroy(&cmap); return pixd; } /*! * \brief boxaaDisplay() * * \param[in] pixs [optional] 1 bpp * \param[in] baa boxaa, typically from a 2d sort * \param[in] linewba line width to display outline of each boxa * \param[in] linewb line width to display outline of each box * \param[in] colorba color to display boxa * \param[in] colorb color to display box * \param[in] w width of outupt pix; use 0 if determined by %pixs or %baa * \param[in] h height of outupt pix; use 0 if determined by %pixs or %baa * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) If %pixs exists, this renders the boxes over an 8 bpp version
 *          of it.  Otherwise, it renders the boxes over an empty image
 *          with a white background.
 *      (2) If %pixs exists, the dimensions of %pixd are the same,
 *          and input values of %w and %h are ignored.
 *          If %pixs is NULL, the dimensions of %pixd are determined by
 *            - %w and %h if both are > 0, or
 *            - the minimum size required using all boxes in %baa.
 *
 * 
*/ PIX * boxaaDisplay(PIX *pixs, BOXAA *baa, l_int32 linewba, l_int32 linewb, l_uint32 colorba, l_uint32 colorb, l_int32 w, l_int32 h) { l_int32 i, j, n, m, rbox, gbox, bbox, rboxa, gboxa, bboxa; BOX *box; BOXA *boxa; PIX *pixd; PIXCMAP *cmap; PROCNAME("boxaaDisplay"); if (!baa) return (PIX *)ERROR_PTR("baa not defined", procName, NULL); if (w <= 0 || h <= 0) { if (pixs) pixGetDimensions(pixs, &w, &h, NULL); else boxaaGetExtent(baa, &w, &h, NULL, NULL); } if (pixs) { pixd = pixConvertTo8(pixs, 1); cmap = pixGetColormap(pixd); } else { pixd = pixCreate(w, h, 8); cmap = pixcmapCreate(8); pixSetColormap(pixd, cmap); pixcmapAddColor(cmap, 255, 255, 255); } extractRGBValues(colorb, &rbox, &gbox, &bbox); extractRGBValues(colorba, &rboxa, &gboxa, &bboxa); pixcmapAddColor(cmap, rbox, gbox, bbox); pixcmapAddColor(cmap, rboxa, gboxa, bboxa); n = boxaaGetCount(baa); for (i = 0; i < n; i++) { boxa = boxaaGetBoxa(baa, i, L_CLONE); boxaGetExtent(boxa, NULL, NULL, &box); pixRenderBoxArb(pixd, box, linewba, rboxa, gboxa, bboxa); boxDestroy(&box); m = boxaGetCount(boxa); for (j = 0; j < m; j++) { box = boxaGetBox(boxa, j, L_CLONE); pixRenderBoxArb(pixd, box, linewb, rbox, gbox, bbox); boxDestroy(&box); } boxaDestroy(&boxa); } return pixd; } /*! * \brief pixaDisplayBoxaa() * * \param[in] pixas any depth, can be cmapped * \param[in] baa boxes to draw on input pixa * \param[in] colorflag L_DRAW_RED, L_DRAW_GREEN, etc * \param[in] width thickness of lines * \return pixa with box outlines drawn on each pix, or NULL on error * *
 * Notes:
 *      (1) All pix in %pixas that are not rgb are converted to rgb.
 *      (2) Each boxa in %baa contains boxes that will be drawn on
 *          the corresponding pix in %pixas.
 *      (3) The color of the boxes drawn on each pix are selected with
 *          %colorflag:
 *            * For red, green or blue: use L_DRAW_RED, etc.
 *            * For sequential r, g, b: use L_DRAW_RGB
 *            * For random colors: use L_DRAW_RANDOM
 * 
*/ PIXA * pixaDisplayBoxaa(PIXA *pixas, BOXAA *baa, l_int32 colorflag, l_int32 width) { l_int32 i, j, nba, n, nbox, rval, gval, bval; l_uint32 color; l_uint32 colors[255]; BOXA *boxa; BOX *box; PIX *pix; PIXA *pixad; PROCNAME("pixaDisplayBoxaa"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (!baa) return (PIXA *)ERROR_PTR("baa not defined", procName, NULL); if (width < 1) return (PIXA *)ERROR_PTR("width must be >= 1", procName, NULL); if ((nba = boxaaGetCount(baa)) < 1) return (PIXA *)ERROR_PTR("no boxa in baa", procName, NULL); if ((n = pixaGetCount(pixas)) == 0) return (PIXA *)ERROR_PTR("no pix in pixas", procName, NULL); if (n != nba) return (PIXA *)ERROR_PTR("num pix != num boxa", procName, NULL); if (colorflag == L_DRAW_RED) color = 0xff000000; else if (colorflag == L_DRAW_GREEN) color = 0x00ff0000; else if (colorflag == L_DRAW_BLUE) color = 0x0000ff00; else if (colorflag == L_DRAW_RGB) color = 0x000000ff; else if (colorflag == L_DRAW_RANDOM) color = 0x00000000; else return (PIXA *)ERROR_PTR("invalid colorflag", procName, NULL); if (colorflag == L_DRAW_RED || colorflag == L_DRAW_GREEN || colorflag == L_DRAW_BLUE) { for (i = 0; i < 255; i++) colors[i] = color; } else if (colorflag == L_DRAW_RGB) { for (i = 0; i < 255; i++) { if (i % 3 == L_DRAW_RED) colors[i] = 0xff000000; else if (i % 3 == L_DRAW_GREEN) colors[i] = 0x00ff0000; else /* i % 3 == L_DRAW_BLUE) */ colors[i] = 0x0000ff00; } } else if (colorflag == L_DRAW_RANDOM) { for (i = 0; i < 255; i++) { rval = (l_uint32)rand() & 0xff; gval = (l_uint32)rand() & 0xff; bval = (l_uint32)rand() & 0xff; composeRGBPixel(rval, gval, bval, &colors[i]); } } pixad = pixaCreate(n); for (i = 0; i < n; i++) { pix = pixaGetPix(pixas, i, L_COPY); boxa = boxaaGetBoxa(baa, i, L_CLONE); nbox = boxaGetCount(boxa); for (j = 0; j < nbox; j++) { box = boxaGetBox(boxa, j, L_CLONE); extractRGBValues(colors[j % 255], &rval, &gval, &bval); pixRenderBoxArb(pix, box, width, rval, gval, bval); boxDestroy(&box); } boxaDestroy(&boxa); pixaAddPix(pixad, pix, L_INSERT); } return pixad; } /*---------------------------------------------------------------------* * Split mask components into Boxa * *---------------------------------------------------------------------*/ /*! * \brief pixSplitIntoBoxa() * * \param[in] pixs 1 bpp * \param[in] minsum minimum pixels to trigger propagation * \param[in] skipdist distance before computing sum for propagation * \param[in] delta difference required to stop propagation * \param[in] maxbg maximum number of allowed bg pixels in ref scan * \param[in] maxcomps use 0 for unlimited number of subdivided components * \param[in] remainder set to 1 to get b.b. of remaining stuff * \return boxa of rectangles covering the fg of pixs, or NULL on error * *
 * Notes:
 *      (1) This generates a boxa of rectangles that covers
 *          the fg of a mask.  For each 8-connected component in pixs,
 *          it does a greedy partitioning, choosing the largest
 *          rectangle found from each of the four directions at each iter.
 *          See pixSplitComponentIntoBoxa() for details.
 *      (2) The input parameters give some flexibility for boundary
 *          noise.  The resulting set of rectangles may cover some
 *          bg pixels.
 *      (3) This should be used when there are a small number of
 *          mask components, each of which has sides that are close
 *          to horizontal and vertical.  The input parameters %delta
 *          and %maxbg determine whether or not holes in the mask are covered.
 *      (4) The parameter %maxcomps gives the maximum number of allowed
 *          rectangles extracted from any single connected component.
 *          Use 0 if no limit is to be applied.
 *      (5) The flag %remainder specifies whether we take a final bounding
 *          box for anything left after the maximum number of allowed
 *          rectangle is extracted.
 * 
*/ BOXA * pixSplitIntoBoxa(PIX *pixs, l_int32 minsum, l_int32 skipdist, l_int32 delta, l_int32 maxbg, l_int32 maxcomps, l_int32 remainder) { l_int32 i, n; BOX *box; BOXA *boxa, *boxas, *boxad; PIX *pix; PIXA *pixas; PROCNAME("pixSplitIntoBoxa"); if (!pixs || pixGetDepth(pixs) != 1) return (BOXA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); boxas = pixConnComp(pixs, &pixas, 8); n = boxaGetCount(boxas); boxad = boxaCreate(0); for (i = 0; i < n; i++) { pix = pixaGetPix(pixas, i, L_CLONE); box = boxaGetBox(boxas, i, L_CLONE); boxa = pixSplitComponentIntoBoxa(pix, box, minsum, skipdist, delta, maxbg, maxcomps, remainder); boxaJoin(boxad, boxa, 0, -1); pixDestroy(&pix); boxDestroy(&box); boxaDestroy(&boxa); } pixaDestroy(&pixas); boxaDestroy(&boxas); return boxad; } /*! * \brief pixSplitComponentIntoBoxa() * * \param[in] pix 1 bpp * \param[in] box [optional] location of pix w/rt an origin * \param[in] minsum minimum pixels to trigger propagation * \param[in] skipdist distance before computing sum for propagation * \param[in] delta difference required to stop propagation * \param[in] maxbg maximum number of allowed bg pixels in ref scan * \param[in] maxcomps use 0 for unlimited number of subdivided components * \param[in] remainder set to 1 to get b.b. of remaining stuff * \return boxa of rectangles covering the fg of pix, or NULL on error * *
 * Notes:
 *      (1) This generates a boxa of rectangles that covers
 *          the fg of a mask.  It does so by a greedy partitioning of
 *          the mask, choosing the largest rectangle found from
 *          each of the four directions at each step.
 *      (2) The input parameters give some flexibility for boundary
 *          noise.  The resulting set of rectangles must cover all
 *          the fg pixels and, in addition, may cover some bg pixels.
 *          Using small input parameters on a noiseless mask (i.e., one
 *          that has only large vertical and horizontal edges) will
 *          result in a proper covering of only the fg pixels of the mask.
 *      (3) The input is assumed to be a single connected component, that
 *          may have holes.  From each side, sweep inward, counting
 *          the pixels.  If the count becomes greater than %minsum,
 *          and we have moved forward a further amount %skipdist,
 *          record that count ('countref'), but don't accept if the scan
 *          contains more than %maxbg bg pixels.  Continue the scan
 *          until we reach a count that differs from countref by at
 *          least %delta, at which point the propagation stops.  The box
 *          swept out gets a score, which is the sum of fg pixels
 *          minus a penalty.  The penalty is the number of bg pixels
 *          in the box.  This is done from all four sides, and the
 *          side with the largest score is saved as a rectangle.
 *          The process repeats until there is either no rectangle
 *          left, or there is one that can't be captured from any
 *          direction.  For the latter case, we simply accept the
 *          last rectangle.
 *      (4) The input box is only used to specify the location of
 *          the UL corner of pix, with respect to an origin that
 *          typically represents the UL corner of an underlying image,
 *          of which pix is one component.  If %box is null,
 *          the UL corner is taken to be (0, 0).
 *      (5) The parameter %maxcomps gives the maximum number of allowed
 *          rectangles extracted from any single connected component.
 *          Use 0 if no limit is to be applied.
 *      (6) The flag %remainder specifies whether we take a final bounding
 *          box for anything left after the maximum number of allowed
 *          rectangle is extracted.
 *      (7) So if %maxcomps > 0, it specifies that we want no more than
 *          the first %maxcomps rectangles that satisfy the input
 *          criteria.  After this, we can get a final rectangle that
 *          bounds everything left over by setting %remainder == 1.
 *          If %remainder == 0, we only get rectangles that satisfy
 *          the input criteria.
 *      (8) It should be noted that the removal of rectangles can
 *          break the original c.c. into several c.c.
 *      (9) Summing up:
 *            * If %maxcomp == 0, the splitting proceeds as far as possible.
 *            * If %maxcomp > 0, the splitting stops when %maxcomps are
 *                found, or earlier if no more components can be selected.
 *            * If %remainder == 1 and components remain that cannot be
 *                selected, they are returned as a single final rectangle;
 *                otherwise, they are ignored.
 * 
*/ BOXA * pixSplitComponentIntoBoxa(PIX *pix, BOX *box, l_int32 minsum, l_int32 skipdist, l_int32 delta, l_int32 maxbg, l_int32 maxcomps, l_int32 remainder) { l_int32 i, w, h, boxx, boxy, bx, by, bw, bh, maxdir, maxscore; l_int32 iter; BOX *boxs; /* shrinks as rectangular regions are removed */ BOX *boxt1, *boxt2, *boxt3; BOXA *boxat; /* stores rectangle data for each side in an iteration */ BOXA *boxad; NUMA *nascore, *nas; PIX *pixs; PROCNAME("pixSplitComponentIntoBoxa"); if (!pix || pixGetDepth(pix) != 1) return (BOXA *)ERROR_PTR("pix undefined or not 1 bpp", procName, NULL); pixs = pixCopy(NULL, pix); pixGetDimensions(pixs, &w, &h, NULL); if (box) boxGetGeometry(box, &boxx, &boxy, NULL, NULL); else boxx = boxy = 0; boxs = boxCreate(0, 0, w, h); boxad = boxaCreate(0); iter = 0; while (boxs != NULL) { boxGetGeometry(boxs, &bx, &by, &bw, &bh); boxat = boxaCreate(4); /* potential rectangular regions */ nascore = numaCreate(4); for (i = 0; i < 4; i++) { pixSearchForRectangle(pixs, boxs, minsum, skipdist, delta, maxbg, i, boxat, nascore); } nas = numaGetSortIndex(nascore, L_SORT_DECREASING); numaGetIValue(nas, 0, &maxdir); numaGetIValue(nascore, maxdir, &maxscore); #if DEBUG_SPLIT lept_stderr("Iteration: %d\n", iter); boxPrintStreamInfo(stderr, boxs); boxaWriteStderr(boxat); lept_stderr("\nmaxdir = %d, maxscore = %d\n\n", maxdir, maxscore); #endif /* DEBUG_SPLIT */ if (maxscore > 0) { /* accept this */ boxt1 = boxaGetBox(boxat, maxdir, L_CLONE); boxt2 = boxTransform(boxt1, boxx, boxy, 1.0, 1.0); boxaAddBox(boxad, boxt2, L_INSERT); pixClearInRect(pixs, boxt1); boxDestroy(&boxt1); pixClipBoxToForeground(pixs, boxs, NULL, &boxt3); boxDestroy(&boxs); boxs = boxt3; if (boxs) { boxGetGeometry(boxs, NULL, NULL, &bw, &bh); if (bw < 2 || bh < 2) boxDestroy(&boxs); /* we're done */ } } else { /* no more valid rectangles can be found */ if (remainder == 1) { /* save the last box */ boxt1 = boxTransform(boxs, boxx, boxy, 1.0, 1.0); boxaAddBox(boxad, boxt1, L_INSERT); } boxDestroy(&boxs); /* we're done */ } boxaDestroy(&boxat); numaDestroy(&nascore); numaDestroy(&nas); iter++; if ((iter == maxcomps) && boxs) { if (remainder == 1) { /* save the last box */ boxt1 = boxTransform(boxs, boxx, boxy, 1.0, 1.0); boxaAddBox(boxad, boxt1, L_INSERT); } boxDestroy(&boxs); /* we're done */ } } pixDestroy(&pixs); return boxad; } /*! * \brief pixSearchForRectangle() * * \param[in] pixs 1 bpp * \param[in] boxs current region to investigate * \param[in] minsum minimum pixels to trigger propagation * \param[in] skipdist distance before computing sum for propagation * \param[in] delta difference required to stop propagation * \param[in] maxbg maximum number of allowed bg pixels in ref scan * \param[in] sideflag side to search from * \param[in] boxat add result of rectangular region found here * \param[in] nascore add score for this rectangle here * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) See pixSplitComponentIntoBoxa() for an explanation of the algorithm.
 *          This does the sweep from a single side.  For each iteration
 *          in pixSplitComponentIntoBoxa(), this will be called 4 times,
 *          for %sideflag = {0, 1, 2, 3}.
 *      (2) If a valid rectangle is not found, add a score of 0 and
 *          input a minimum box.
 * 
*/ static l_int32 pixSearchForRectangle(PIX *pixs, BOX *boxs, l_int32 minsum, l_int32 skipdist, l_int32 delta, l_int32 maxbg, l_int32 sideflag, BOXA *boxat, NUMA *nascore) { l_int32 bx, by, bw, bh, width, height, setref, atref; l_int32 minincol, maxincol, mininrow, maxinrow, minval, maxval, bgref; l_int32 x, y, x0, y0, xref, yref, colsum, rowsum, score, countref, diff; void **lines1; BOX *boxr; PROCNAME("pixSearchForRectangle"); if (!pixs || pixGetDepth(pixs) != 1) return ERROR_INT("pixs undefined or not 1 bpp", procName, 1); if (!boxs) return ERROR_INT("boxs not defined", procName, 1); if (!boxat) return ERROR_INT("boxat not defined", procName, 1); if (!nascore) return ERROR_INT("nascore not defined", procName, 1); lines1 = pixGetLinePtrs(pixs, NULL); boxGetGeometry(boxs, &bx, &by, &bw, &bh); boxr = NULL; setref = 0; atref = 0; maxval = 0; minval = 100000; score = 0; /* sum of all (fg - bg) pixels seen in the scan */ xref = yref = 100000; /* init to impossibly big number */ if (sideflag == L_FROM_LEFT) { for (x = bx; x < bx + bw; x++) { colsum = 0; maxincol = 0; minincol = 100000; for (y = by; y < by + bh; y++) { if (GET_DATA_BIT(lines1[y], x)) { colsum++; if (y > maxincol) maxincol = y; if (y < minincol) minincol = y; } } score += colsum; /* Enough fg to sweep out a rectangle? */ if (!setref && colsum >= minsum) { setref = 1; xref = x + 10; if (xref >= bx + bw) goto failure; } /* Reached the reference line; save the count; * if there is too much bg, the rectangle is invalid. */ if (setref && x == xref) { atref = 1; countref = colsum; bgref = maxincol - minincol + 1 - countref; if (bgref > maxbg) goto failure; } /* Have we left the rectangle? If so, save it along * with the score. */ if (atref) { diff = L_ABS(colsum - countref); if (diff >= delta || x == bx + bw - 1) { height = maxval - minval + 1; width = x - bx; if (x == bx + bw - 1) width = x - bx + 1; boxr = boxCreate(bx, minval, width, height); score = 2 * score - width * height; goto success; } } maxval = L_MAX(maxval, maxincol); minval = L_MIN(minval, minincol); } goto failure; } else if (sideflag == L_FROM_RIGHT) { for (x = bx + bw - 1; x >= bx; x--) { colsum = 0; maxincol = 0; minincol = 100000; for (y = by; y < by + bh; y++) { if (GET_DATA_BIT(lines1[y], x)) { colsum++; if (y > maxincol) maxincol = y; if (y < minincol) minincol = y; } } score += colsum; if (!setref && colsum >= minsum) { setref = 1; xref = x - 10; if (xref < bx) goto failure; } if (setref && x == xref) { atref = 1; countref = colsum; bgref = maxincol - minincol + 1 - countref; if (bgref > maxbg) goto failure; } if (atref) { diff = L_ABS(colsum - countref); if (diff >= delta || x == bx) { height = maxval - minval + 1; x0 = x + 1; if (x == bx) x0 = x; width = bx + bw - x0; boxr = boxCreate(x0, minval, width, height); score = 2 * score - width * height; goto success; } } maxval = L_MAX(maxval, maxincol); minval = L_MIN(minval, minincol); } goto failure; } else if (sideflag == L_FROM_TOP) { for (y = by; y < by + bh; y++) { rowsum = 0; maxinrow = 0; mininrow = 100000; for (x = bx; x < bx + bw; x++) { if (GET_DATA_BIT(lines1[y], x)) { rowsum++; if (x > maxinrow) maxinrow = x; if (x < mininrow) mininrow = x; } } score += rowsum; if (!setref && rowsum >= minsum) { setref = 1; yref = y + 10; if (yref >= by + bh) goto failure; } if (setref && y == yref) { atref = 1; countref = rowsum; bgref = maxinrow - mininrow + 1 - countref; if (bgref > maxbg) goto failure; } if (atref) { diff = L_ABS(rowsum - countref); if (diff >= delta || y == by + bh - 1) { width = maxval - minval + 1; height = y - by; if (y == by + bh - 1) height = y - by + 1; boxr = boxCreate(minval, by, width, height); score = 2 * score - width * height; goto success; } } maxval = L_MAX(maxval, maxinrow); minval = L_MIN(minval, mininrow); } goto failure; } else if (sideflag == L_FROM_BOT) { for (y = by + bh - 1; y >= by; y--) { rowsum = 0; maxinrow = 0; mininrow = 100000; for (x = bx; x < bx + bw; x++) { if (GET_DATA_BIT(lines1[y], x)) { rowsum++; if (x > maxinrow) maxinrow = x; if (x < mininrow) mininrow = x; } } score += rowsum; if (!setref && rowsum >= minsum) { setref = 1; yref = y - 10; if (yref < by) goto failure; } if (setref && y == yref) { atref = 1; countref = rowsum; bgref = maxinrow - mininrow + 1 - countref; if (bgref > maxbg) goto failure; } if (atref) { diff = L_ABS(rowsum - countref); if (diff >= delta || y == by) { width = maxval - minval + 1; y0 = y + 1; if (y == by) y0 = y; height = by + bh - y0; boxr = boxCreate(minval, y0, width, height); score = 2 * score - width * height; goto success; } } maxval = L_MAX(maxval, maxinrow); minval = L_MIN(minval, mininrow); } goto failure; } failure: numaAddNumber(nascore, 0); boxaAddBox(boxat, boxCreate(0, 0, 1, 1), L_INSERT); /* min box */ LEPT_FREE(lines1); return 0; success: numaAddNumber(nascore, score); boxaAddBox(boxat, boxr, L_INSERT); LEPT_FREE(lines1); return 0; } /*---------------------------------------------------------------------* * Represent horizontal or vertical mosaic strips * *---------------------------------------------------------------------*/ /*! * \brief makeMosaicStrips() * * \param[in] w, h * \param[in] direction L_SCAN_HORIZONTAL or L_SCAN_VERTICAL * \param[in] size of strips in the scan direction * \return boxa, or NULL on error * *
 * Notes:
 *      (1) For example, this can be used to generate a pixa of
 *          vertical strips of width 10 from an image, using:
 *             pixGetDimensions(pix, &w, &h, NULL);
 *             boxa = makeMosaicStrips(w, h, L_SCAN_HORIZONTAL, 10);
 *             pixa = pixClipRectangles(pix, boxa);
 *          All strips except the last will be the same width.  The
 *          last strip will have width w % 10.
 * 
*/ BOXA * makeMosaicStrips(l_int32 w, l_int32 h, l_int32 direction, l_int32 size) { l_int32 i, nstrips, extra; BOX *box; BOXA *boxa; PROCNAME("makeMosaicStrips"); if (w < 1 || h < 1) return (BOXA *)ERROR_PTR("invalid w or h", procName, NULL); if (direction != L_SCAN_HORIZONTAL && direction != L_SCAN_VERTICAL) return (BOXA *)ERROR_PTR("invalid direction", procName, NULL); if (size < 1) return (BOXA *)ERROR_PTR("size < 1", procName, NULL); boxa = boxaCreate(0); if (direction == L_SCAN_HORIZONTAL) { nstrips = w / size; for (i = 0; i < nstrips; i++) { box = boxCreate(i * size, 0, size, h); boxaAddBox(boxa, box, L_INSERT); } if ((extra = w % size) > 0) { box = boxCreate(nstrips * size, 0, extra, h); boxaAddBox(boxa, box, L_INSERT); } } else { nstrips = h / size; for (i = 0; i < nstrips; i++) { box = boxCreate(0, i * size, w, size); boxaAddBox(boxa, box, L_INSERT); } if ((extra = h % size) > 0) { box = boxCreate(0, nstrips * size, w, extra); boxaAddBox(boxa, box, L_INSERT); } } return boxa; } /*---------------------------------------------------------------------* * Comparison between boxa * *---------------------------------------------------------------------*/ /*! * \brief boxaCompareRegions() * * \param[in] boxa1, boxa2 * \param[in] areathresh minimum area of boxes to be considered * \param[out] pnsame true if same number of boxes * \param[out] pdiffarea fractional difference in total area * \param[out] pdiffxor [optional] fractional difference in xor of regions * \param[out] ppixdb [optional] debug pix showing two boxa * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) This takes 2 boxa, removes all boxes smaller than a given area,
 *          and compares the remaining boxes between the boxa.
 *      (2) The area threshold is introduced to help remove noise from
 *          small components.  Any box with a smaller value of w * h
 *          will be removed from consideration.
 *      (3) The xor difference is the most stringent test, requiring alignment
 *          of the corresponding boxes.  It is also more computationally
 *          intensive and is optionally returned.  Alignment is to the
 *          UL corner of each region containing all boxes, as given by
 *          boxaGetExtent().
 *      (4) Both fractional differences are with respect to the total
 *          area in the two boxa.  They range from 0.0 to 1.0.
 *          A perfect match has value 0.0.  If both boxa are empty,
 *          we return 0.0; if one is empty we return 1.0.
 *      (5) An example input might be the rectangular regions of a
 *          segmentation mask for text or images from two pages.
 * 
*/ l_ok boxaCompareRegions(BOXA *boxa1, BOXA *boxa2, l_int32 areathresh, l_int32 *pnsame, l_float32 *pdiffarea, l_float32 *pdiffxor, PIX **ppixdb) { l_int32 w, h, x3, y3, w3, h3, x4, y4, w4, h4, n3, n4, area1, area2; l_int32 count3, count4, countxor; l_int32 *tab; BOX *box3, *box4; BOXA *boxa3, *boxa4, *boxa3t, *boxa4t; PIX *pix1, *pix2, *pix3, *pix4, *pix5; PIXA *pixa; PROCNAME("boxaCompareRegions"); if (pdiffxor) *pdiffxor = 1.0; if (ppixdb) *ppixdb = NULL; if (pnsame) *pnsame = FALSE; if (pdiffarea) *pdiffarea = 1.0; if (!boxa1 || !boxa2) return ERROR_INT("boxa1 and boxa2 not both defined", procName, 1); if (!pnsame) return ERROR_INT("&nsame not defined", procName, 1); if (!pdiffarea) return ERROR_INT("&diffarea not defined", procName, 1); boxa3 = boxaSelectByArea(boxa1, areathresh, L_SELECT_IF_GTE, NULL); boxa4 = boxaSelectByArea(boxa2, areathresh, L_SELECT_IF_GTE, NULL); n3 = boxaGetCount(boxa3); n4 = boxaGetCount(boxa4); if (n3 == n4) *pnsame = TRUE; /* There are no boxes in one or both */ if (n3 == 0 || n4 == 0) { boxaDestroy(&boxa3); boxaDestroy(&boxa4); if (n3 == 0 && n4 == 0) { /* they are both empty: we say they are the * same; otherwise, they differ maximally * and retain the default value. */ *pdiffarea = 0.0; if (pdiffxor) *pdiffxor = 0.0; } return 0; } /* There are boxes in both */ boxaGetArea(boxa3, &area1); boxaGetArea(boxa4, &area2); *pdiffarea = (l_float32)L_ABS(area1 - area2) / (l_float32)(area1 + area2); if (!pdiffxor) { boxaDestroy(&boxa3); boxaDestroy(&boxa4); return 0; } /* The easiest way to get the xor of aligned boxes is to work * with images of each boxa. This is done by translating each * boxa so that the UL corner of the region that includes all * boxes in the boxa is placed at the origin of each pix. */ boxaGetExtent(boxa3, &w, &h, &box3); boxaGetExtent(boxa4, &w, &h, &box4); boxGetGeometry(box3, &x3, &y3, &w3, &h3); boxGetGeometry(box4, &x4, &y4, &w4, &h4); boxa3t = boxaTransform(boxa3, -x3, -y3, 1.0, 1.0); boxa4t = boxaTransform(boxa4, -x4, -y4, 1.0, 1.0); w = L_MAX(x3 + w3, x4 + w4); h = L_MAX(y3 + h3, y4 + h4); pix3 = pixCreate(w, h, 1); /* use the max to keep everything in the xor */ pix4 = pixCreate(w, h, 1); pixMaskBoxa(pix3, pix3, boxa3t, L_SET_PIXELS); pixMaskBoxa(pix4, pix4, boxa4t, L_SET_PIXELS); tab = makePixelSumTab8(); pixCountPixels(pix3, &count3, tab); pixCountPixels(pix4, &count4, tab); pix5 = pixXor(NULL, pix3, pix4); pixCountPixels(pix5, &countxor, tab); LEPT_FREE(tab); *pdiffxor = (l_float32)countxor / (l_float32)(count3 + count4); if (ppixdb) { pixa = pixaCreate(2); pix1 = pixCreate(w, h, 32); pixSetAll(pix1); pixRenderHashBoxaBlend(pix1, boxa3, 5, 1, L_POS_SLOPE_LINE, 2, 255, 0, 0, 0.5); pixRenderHashBoxaBlend(pix1, boxa4, 5, 1, L_NEG_SLOPE_LINE, 2, 0, 255, 0, 0.5); pixaAddPix(pixa, pix1, L_INSERT); pix2 = pixCreate(w, h, 32); pixPaintThroughMask(pix2, pix3, x3, y3, 0xff000000); pixPaintThroughMask(pix2, pix4, x4, y4, 0x00ff0000); pixAnd(pix3, pix3, pix4); pixPaintThroughMask(pix2, pix3, x3, y3, 0x0000ff00); pixaAddPix(pixa, pix2, L_INSERT); *ppixdb = pixaDisplayTiledInRows(pixa, 32, 1000, 1.0, 0, 30, 2); pixaDestroy(&pixa); } boxDestroy(&box3); boxDestroy(&box4); boxaDestroy(&boxa3); boxaDestroy(&boxa3t); boxaDestroy(&boxa4); boxaDestroy(&boxa4t); pixDestroy(&pix3); pixDestroy(&pix4); pixDestroy(&pix5); return 0; } /*---------------------------------------------------------------------* * Reliable selection of a single large box * *---------------------------------------------------------------------*/ /*! * \brief pixSelectLargeULComp() * * \param[in] pixs 1 bpp * \param[in] areaslop fraction near but less than 1.0 * \param[in] yslop number of pixels in y direction * \param[in] connectivity 4 or 8 * \return box, or NULL on error * *
 * Notes:
 *      (1) This selects a box near the top (first) and left (second)
 *          of the image, from the set of all boxes that have
 *                area >= %areaslop * (area of biggest box),
 *          where %areaslop is some fraction; say ~ 0.9.
 *      (2) For all boxes satisfying the above condition, select
 *          the left-most box that is within %yslop (say, 20) pixels
 *          of the box nearest the top.
 *      (3) This can be used to reliably select a specific one of
 *          the largest regions in an image, for applications where
 *          there are expected to be small variations in region size
 *          and location.
 *      (4) See boxSelectLargeULBox() for implementation details.
 * 
*/ BOX * pixSelectLargeULComp(PIX *pixs, l_float32 areaslop, l_int32 yslop, l_int32 connectivity) { BOX *box; BOXA *boxa1; PROCNAME("pixSelectLargeULComp"); if (!pixs) return (BOX *)ERROR_PTR("pixs not defined", procName, NULL); if (areaslop < 0.0 || areaslop > 1.0) return (BOX *)ERROR_PTR("invalid value for areaslop", procName, NULL); yslop = L_MAX(0, yslop); boxa1 = pixConnCompBB(pixs, connectivity); if (boxaGetCount(boxa1) == 0) { boxaDestroy(&boxa1); return NULL; } box = boxaSelectLargeULBox(boxa1, areaslop, yslop); boxaDestroy(&boxa1); return box; } /*! * \brief boxaSelectLargeULBox() * * \param[in] boxas 1 bpp * \param[in] areaslop fraction near but less than 1.0 * \param[in] yslop number of pixels in y direction * \return box, or NULL on error * *
 * Notes:
 *      (1) See usage notes in pixSelectLargeULComp().
 * 
*/ BOX * boxaSelectLargeULBox(BOXA *boxas, l_float32 areaslop, l_int32 yslop) { l_int32 w, h, i, n, x1, y1, x2, y2, select; l_float32 area, max_area; BOX *box; BOXA *boxa1, *boxa2, *boxa3; PROCNAME("boxaSelectLargeULBox"); if (!boxas) return (BOX *)ERROR_PTR("boxas not defined", procName, NULL); if (boxaGetCount(boxas) == 0) return (BOX *)ERROR_PTR("no boxes in boxas", procName, NULL); if (areaslop < 0.0 || areaslop > 1.0) return (BOX *)ERROR_PTR("invalid value for areaslop", procName, NULL); yslop = L_MAX(0, yslop); boxa1 = boxaSort(boxas, L_SORT_BY_AREA, L_SORT_DECREASING, NULL); boxa2 = boxaSort(boxa1, L_SORT_BY_Y, L_SORT_INCREASING, NULL); n = boxaGetCount(boxa2); boxaGetBoxGeometry(boxa1, 0, NULL, NULL, &w, &h); /* biggest box by area */ max_area = (l_float32)(w * h); /* boxa3 collects all boxes eligible by area, sorted top-down */ boxa3 = boxaCreate(4); for (i = 0; i < n; i++) { boxaGetBoxGeometry(boxa2, i, NULL, NULL, &w, &h); area = (l_float32)(w * h); if (area / max_area >= areaslop) { box = boxaGetBox(boxa2, i, L_COPY); boxaAddBox(boxa3, box, L_INSERT); } } /* Take the first (top-most box) unless the second (etc) has * nearly the same y value but a smaller x value. */ n = boxaGetCount(boxa3); boxaGetBoxGeometry(boxa3, 0, &x1, &y1, NULL, NULL); select = 0; for (i = 1; i < n; i++) { boxaGetBoxGeometry(boxa3, i, &x2, &y2, NULL, NULL); if (y2 - y1 < yslop && x2 < x1) { select = i; x1 = x2; /* but always compare against y1 */ } } box = boxaGetBox(boxa3, select, L_COPY); boxaDestroy(&boxa1); boxaDestroy(&boxa2); boxaDestroy(&boxa3); return box; }