/*====================================================================* - 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 ccbord.c *
 *
 *     CCBORDA and CCBORD creation and destruction
 *         CCBORDA         *ccbaCreate()
 *         void            *ccbaDestroy()
 *         CCBORD          *ccbCreate()
 *         void            *ccbDestroy()
 *
 *     CCBORDA addition
 *         l_int32          ccbaAddCcb()
 *         static l_int32   ccbaExtendArray()
 *
 *     CCBORDA accessors
 *         l_int32          ccbaGetCount()
 *         l_int32          ccbaGetCcb()
 *
 *     Top-level border-finding routines
 *         CCBORDA         *pixGetAllCCBorders()
 *         static CCBORD   *pixGetCCBorders()
 *         PTAA            *pixGetOuterBordersPtaa()
 *         static PTA      *pixGetOuterBorderPta()
 *
 *     Lower-level border location routines
 *         PTAA            *pixGetOuterBorder()
 *         static l_int32   pixGetHoleBorder()
 *         static l_int32   findNextBorderPixel()
 *         static void      locateOutsideSeedPixel()
 *
 *     Border conversions
 *         l_int32          ccbaGenerateGlobalLocs()
 *         l_int32          ccbaGenerateStepChains()
 *         l_int32          ccbaStepChainsToPixCoords()
 *         l_int32          ccbaGenerateSPGlobalLocs()
 *
 *     Conversion to single path
 *         l_int32          ccbaGenerateSinglePath()
 *         PTA             *getCutPathForHole()
 *
 *     Border and full image rendering
 *         PIX             *ccbaDisplayBorder()
 *         PIX             *ccbaDisplaySPBorder()
 *         PIX             *ccbaDisplayImage1()
 *         PIX             *ccbaDisplayImage2()
 *
 *     Serialize for I/O
 *         l_int32          ccbaWrite()
 *         l_int32          ccbaWriteStream()
 *         l_int32          ccbaRead()
 *         l_int32          ccbaReadStream()
 *
 *     SVG output
 *         l_int32          ccbaWriteSVG()
 *         char            *ccbaWriteSVGString()
 *
 *
 *     Border finding is tricky because components can have
 *     holes, which also need to be traced out.  The outer
 *     border can be connected with all the hole borders,
 *     so that there is a single border for each component.
 *     [Alternatively, the connecting paths can be eliminated if
 *     you're willing to have a set of borders for each
 *     component (an exterior border and some number of
 *     interior ones), with "line to" operations tracing
 *     out each border and "move to" operations going from
 *     one border to the next.]
 *
 *     Here's the plan.  We get the pix for each connected
 *     component, and trace its exterior border.  We then
 *     find the holes (if any) in the pix, and separately
 *     trace out their borders, all using the same
 *     border-following rule that has ON pixels on the right
 *     side of the path.
 *
 *     [For svg, we may want to turn each set of borders for a c.c.
 *     into a closed path.  This can be done by tunnelling
 *     through the component from the outer border to each of the
 *     holes, going in and coming out along the same path so
 *     the connection will be invisible in any rendering
 *     (display or print) from the outline.  The result is a
 *     closed path, where the outside border is traversed
 *     cw and each hole is traversed ccw.  The svg renderer
 *     is assumed to handle these closed borders properly.]
 *
 *     Each border is a closed path that is traversed in such
 *     a way that the stuff inside the c.c. is on the right
 *     side of the traveller.  The border of a singly-connected
 *     component is thus traversed cw, and the border of the
 *     holes inside a c.c. are traversed ccw.  Suppose we have
 *     a list of all the borders of each c.c., both the cw and ccw
 *     traversals.  How do we reconstruct the image?
 *
 *   Reconstruction:
 *
 *     Method 1.  Topological method using connected components.
 *     We have closed borders composed of cw border pixels for the
 *     exterior of c.c. and ccw border pixels for the interior (holes)
 *     in the c.c.
 *         (a) Initialize the destination to be OFF.  Then,
 *             in any order:
 *         (b) Fill the components within and including the cw borders,
 *             and sequentially XOR them onto the destination.
 *         (c) Fill the components within but not including the ccw
 *             borders and sequentially XOR them onto the destination.
 *     The components that are XOR'd together can be generated as follows:
 *         (a) For each closed cw path, use pixFillClosedBorders():
 *               (1) Turn on the path pixels in a subimage that
 *                   minimally supports the border.
 *               (2) Do a 4-connected fill from a seed of 1 pixel width
 *                   on the border, using the inverted image in (1) as
 *                   a filling mask.
 *               (3) Invert the fill result: this gives the component
 *                   including the exterior cw path, with all holes
 *                   filled.
 *         (b) For each closed ccw path (hole):
 *               (1) Turn on the path pixels in a subimage that minimally
 *                   supports the path.
 *               (2) Find a seed pixel on the inside of this path.
 *               (3) Do a 4-connected fill from this seed pixel, using
 *                   the inverted image of the path in (1) as a filling
 *                   mask.
 *
 *     ------------------------------------------------------
 *
 *     Method 2.  A variant of Method 1.  Topological.
 *     In Method 1, we treat the exterior border differently from
 *     the interior (hole) borders.  Here, all borders in a c.c.
 *     are treated equally:
 *         (1) Start with a pix with a 1 pixel OFF boundary
 *             enclosing all the border pixels of the c.c.
 *             This is the filling mask.
 *         (2) Make a seed image of the same size as follows:  for
 *             each border, put one seed pixel OUTSIDE the border
 *             (where OUTSIDE is determined by the inside/outside
 *             convention for borders).
 *         (3) Seedfill into the seed image, filling in the regions
 *             determined by the filling mask.  The fills are clipped
 *             by the border pixels.
 *         (4) Inverting this, we get the c.c. properly filled,
 *             with the holes empty!
 *         (5) Rasterop using XOR the filled c.c. (but not the 1
 *             pixel boundary) into the full dest image.
 *
 *     Method 2 is about 1.2x faster than Method 1 on text images,
 *     and about 2x faster on complex images (e.g., with halftones).
 *
 *     ------------------------------------------------------
 *
 *     Method 3.  The traditional way to fill components delineated
 *     by boundaries is through scan line conversion.  It's a bit
 *     tricky, and I have not yet tried to implement it.
 *
 *     ------------------------------------------------------
 *
 *     Method 4.  [Nota Bene: this method probably doesn't work, and
 *     won't be implemented.  If I get a more traditional scan line
 *     conversion algorithm working, I'll erase these notes.]
 *     Render all border pixels on a destination image,
 *     which will be the final result after scan conversion.  Assign
 *     a value 1 to pixels on cw paths, 2 to pixels on ccw paths,
 *     and 3 to pixels that are on both paths.  Each of the paths
 *     is an 8-connected component.  Now scan across each raster
 *     line.  The attempt is to make rules for each scan line
 *     that are independent of neighboring scanlines.  Here are
 *     a set of rules for writing ON pixels on a destination raster image:
 *
 *         (a) The rasterizer will be in one of two states: ON and OFF.
 *         (b) Start each line in the OFF state.  In the OFF state,
 *             skip pixels until you hit a path of any type.  Turn
 *             the path pixel ON.
 *         (c) If the state is ON, each pixel you encounter will
 *             be turned on, until and including hitting a path pixel.
 *         (d) When you hit a path pixel, if the path does NOT cut
 *             through the line, so that there is not an 8-cc path
 *             pixel (of any type) both above and below, the state
 *             is unchanged (it stays either ON or OFF).
 *         (e) If the path does cut through, but with a possible change
 *             of pixel type, then we decide whether or
 *             not to toggle the state based on the values of the
 *             path pixel and the path pixels above and below:
 *               (1) if a 1 path cuts through, toggle;
 *               (1) if a 2 path cuts through, toggle;
 *               (3) if a 3 path cuts through, do not toggle;
 *               (4) if on one side a 3 touches both a 1 and a 2, use the 2
 *               (5) if a 3 has any 1 neighbors, toggle; else if it has
 *                   no 1 neighbors, do not toggle;
 *               (6) if a 2 has any neighbors that are 1 or 3,
 *                   do not toggle
 *               (7) if a 1 has neighbors 1 and x (x = 2 or 3),
 *                   toggle
 *
 *
 *     To visualize how these rules work, consider the following
 *     component with border pixels labeled according to the scheme
 *     above.  We also show the values of the interior pixels
 *     (w=OFF, b=ON), but these of course must be inferred properly
 *     from the rules above:
 *
 *                     3
 *                  3  w  3             1  1  1
 *                  1  2  1          1  b  2  b  1
 *                  1  b  1             3  w  2  1
 *                  3  b  1          1  b  2  b  1
 *               3  w  3                1  1  1
 *               3  w  3
 *            1  b  2  b  1
 *            1  2  w  2  1
 *         1  b  2  w  2  b  1
 *            1  2  w  2  1
 *               1  2  b  1
 *               1  b  1
 *                  1
 *
 *
 *     Even if this works, which is unlikely, it will certainly be
 *     slow because decisions have to be made on a pixel-by-pixel
 *     basis when encountering borders.
 *
 * 
*/ #ifdef HAVE_CONFIG_H #include #endif /* HAVE_CONFIG_H */ #include #include "allheaders.h" static const l_int32 INITIAL_PTR_ARRAYSIZE = 20; /* n'import quoi */ /* In ccbaGenerateSinglePath(): don't save holes * in c.c. with ridiculously many small holes */ static const l_int32 NMAX_HOLES = 150; /* Tables used to trace the border. * - The 8 pixel positions of neighbors Q are labeled clockwise * starting from the west: * 1 2 3 * 0 P 4 * 7 6 5 * where the labels are the index offset [0, ... 7] of Q relative to P. * - xpostab[] and ypostab[] give the actual x and y pixel offsets * of Q relative to P, indexed by the index offset. * - qpostab[pos] gives the new index offset of Q relative to P, at * the time that a new P has been chosen to be in index offset * position 'pos' relative to the previous P. The relation * between P and Q is always 4-connected. */ static const l_int32 xpostab[] = {-1, -1, 0, 1, 1, 1, 0, -1}; static const l_int32 ypostab[] = {0, -1, -1, -1, 0, 1, 1, 1}; static const l_int32 qpostab[] = {6, 6, 0, 0, 2, 2, 4, 4}; /* Static functions */ static l_int32 ccbaExtendArray(CCBORDA *ccba); static CCBORD *pixGetCCBorders(PIX *pixs, BOX *box); static PTA *pixGetOuterBorderPta(PIX *pixs, BOX *box); static l_ok pixGetHoleBorder(CCBORD *ccb, PIX *pixs, BOX *box, l_int32 xs, l_int32 ys); static l_int32 findNextBorderPixel(l_int32 w, l_int32 h, l_uint32 *data, l_int32 wpl, l_int32 px, l_int32 py, l_int32 *pqpos, l_int32 *pnpx, l_int32 *pnpy); static void locateOutsideSeedPixel(l_int32 fpx, l_int32 fpy, l_int32 spx, l_int32 spy, l_int32 *pxs, l_int32 *pys); #ifndef NO_CONSOLE_IO #define DEBUG_PRINT 0 #endif /* NO CONSOLE_IO */ /*---------------------------------------------------------------------* * ccba and ccb creation and destruction * *---------------------------------------------------------------------*/ /*! * \brief ccbaCreate() * * \param[in] pixs 1 bpp; can be null * \param[in] n initial number of ptrs * \return ccba, or NULL on error */ CCBORDA * ccbaCreate(PIX *pixs, l_int32 n) { CCBORDA *ccba; PROCNAME("ccbaCreate"); if (n <= 0) n = INITIAL_PTR_ARRAYSIZE; ccba = (CCBORDA *)LEPT_CALLOC(1, sizeof(CCBORDA)); if (pixs) { ccba->pix = pixClone(pixs); ccba->w = pixGetWidth(pixs); ccba->h = pixGetHeight(pixs); } ccba->n = 0; ccba->nalloc = n; if ((ccba->ccb = (CCBORD **)LEPT_CALLOC(n, sizeof(CCBORD *))) == NULL) { ccbaDestroy(&ccba); return (CCBORDA *)ERROR_PTR("ccba ptrs not made", procName, NULL); } return ccba; } /*! * \brief ccbaDestroy() * * \param[in,out] pccba will be set to null befoe returning * \return void */ void ccbaDestroy(CCBORDA **pccba) { l_int32 i; CCBORDA *ccba; PROCNAME("ccbaDestroy"); if (pccba == NULL) { L_WARNING("ptr address is NULL!\n", procName); return; } if ((ccba = *pccba) == NULL) return; pixDestroy(&ccba->pix); for (i = 0; i < ccba->n; i++) ccbDestroy(&ccba->ccb[i]); LEPT_FREE(ccba->ccb); LEPT_FREE(ccba); *pccba = NULL; } /*! * \brief ccbCreate() * * \param[in] pixs [optional]; can be null * \return ccb or NULL on error */ CCBORD * ccbCreate(PIX *pixs) { BOXA *boxa; CCBORD *ccb; PTA *start; PTAA *local; PROCNAME("ccbCreate"); if (pixs && pixGetDepth(pixs) != 1) /* pixs can be null */ return (CCBORD *)ERROR_PTR("pixs defined and not 1bpp", procName, NULL); ccb = (CCBORD *)LEPT_CALLOC(1, sizeof(CCBORD)); ccb->refcount++; if (pixs) ccb->pix = pixClone(pixs); boxa = boxaCreate(1); ccb->boxa = boxa; start = ptaCreate(1); ccb->start = start; local = ptaaCreate(1); ccb->local = local; return ccb; } /*! * \brief ccbDestroy() * * \param[in,out] pccb will be set to null before returning * \return void */ void ccbDestroy(CCBORD **pccb) { CCBORD *ccb; PROCNAME("ccbDestroy"); if (pccb == NULL) { L_WARNING("ptr address is NULL!\n", procName); return; } if ((ccb = *pccb) == NULL) return; ccb->refcount--; if (ccb->refcount == 0) { if (ccb->pix) pixDestroy(&ccb->pix); if (ccb->boxa) boxaDestroy(&ccb->boxa); if (ccb->start) ptaDestroy(&ccb->start); if (ccb->local) ptaaDestroy(&ccb->local); if (ccb->global) ptaaDestroy(&ccb->global); if (ccb->step) numaaDestroy(&ccb->step); if (ccb->splocal) ptaDestroy(&ccb->splocal); if (ccb->spglobal) ptaDestroy(&ccb->spglobal); LEPT_FREE(ccb); *pccb = NULL; } } /*---------------------------------------------------------------------* * ccba addition * *---------------------------------------------------------------------*/ /*! * \brief ccbaAddCcb() * * \param[in] ccba * \param[in] ccb to be added by insertion * \return 0 if OK; 1 on error */ l_ok ccbaAddCcb(CCBORDA *ccba, CCBORD *ccb) { l_int32 n; PROCNAME("ccbaAddCcb"); if (!ccba) return ERROR_INT("ccba not defined", procName, 1); if (!ccb) return ERROR_INT("ccb not defined", procName, 1); n = ccbaGetCount(ccba); if (n >= ccba->nalloc) { if (ccbaExtendArray(ccba)) return ERROR_INT("extension failed", procName, 1); } ccba->ccb[n] = ccb; ccba->n++; return 0; } /*! * \brief ccbaExtendArray() * * \param[in] ccba * \return 0 if OK; 1 on error */ static l_int32 ccbaExtendArray(CCBORDA *ccba) { PROCNAME("ccbaExtendArray"); if (!ccba) return ERROR_INT("ccba not defined", procName, 1); if ((ccba->ccb = (CCBORD **)reallocNew((void **)&ccba->ccb, sizeof(CCBORD *) * ccba->nalloc, 2 * sizeof(CCBORD *) * ccba->nalloc)) == NULL) return ERROR_INT("new ptr array not returned", procName, 1); ccba->nalloc = 2 * ccba->nalloc; return 0; } /*---------------------------------------------------------------------* * ccba accessors * *---------------------------------------------------------------------*/ /*! * \brief ccbaGetCount() * * \param[in] ccba * \return count, with 0 on error */ l_int32 ccbaGetCount(CCBORDA *ccba) { PROCNAME("ccbaGetCount"); if (!ccba) return ERROR_INT("ccba not defined", procName, 0); return ccba->n; } /*! * \brief ccbaGetCcb() * * \param[in] ccba * \param[in] index * \return ccb, or NULL on error * *
 * Notes:
 *      (1) This returns a clone of the ccb; it must be destroyed
 * 
*/ CCBORD * ccbaGetCcb(CCBORDA *ccba, l_int32 index) { CCBORD *ccb; PROCNAME("ccbaGetCcb"); if (!ccba) return (CCBORD *)ERROR_PTR("ccba not defined", procName, NULL); if (index < 0 || index >= ccba->n) return (CCBORD *)ERROR_PTR("index out of bounds", procName, NULL); ccb = ccba->ccb[index]; ccb->refcount++; return ccb; } /*---------------------------------------------------------------------* * Top-level border-finding routines * *---------------------------------------------------------------------*/ /*! * \brief pixGetAllCCBorders() * * \param[in] pixs 1 bpp * \return ccborda, or NULL on error */ CCBORDA * pixGetAllCCBorders(PIX *pixs) { l_int32 n, i; BOX *box; BOXA *boxa; CCBORDA *ccba; CCBORD *ccb; PIX *pix; PIXA *pixa; PROCNAME("pixGetAllCCBorders"); if (!pixs) return (CCBORDA *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 1) return (CCBORDA *)ERROR_PTR("pixs not binary", procName, NULL); if ((boxa = pixConnComp(pixs, &pixa, 8)) == NULL) return (CCBORDA *)ERROR_PTR("boxa not made", procName, NULL); n = boxaGetCount(boxa); if ((ccba = ccbaCreate(pixs, n)) == NULL) { boxaDestroy(&boxa); pixaDestroy(&pixa); return (CCBORDA *)ERROR_PTR("ccba not made", procName, NULL); } for (i = 0; i < n; i++) { if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL) { ccbaDestroy(&ccba); pixaDestroy(&pixa); boxaDestroy(&boxa); return (CCBORDA *)ERROR_PTR("pix not found", procName, NULL); } if ((box = pixaGetBox(pixa, i, L_CLONE)) == NULL) { ccbaDestroy(&ccba); pixaDestroy(&pixa); boxaDestroy(&boxa); pixDestroy(&pix); return (CCBORDA *)ERROR_PTR("box not found", procName, NULL); } ccb = pixGetCCBorders(pix, box); pixDestroy(&pix); boxDestroy(&box); if (!ccb) { ccbaDestroy(&ccba); pixaDestroy(&pixa); boxaDestroy(&boxa); return (CCBORDA *)ERROR_PTR("ccb not made", procName, NULL); } /* ptaWriteStream(stderr, ccb->local, 1); */ ccbaAddCcb(ccba, ccb); } boxaDestroy(&boxa); pixaDestroy(&pixa); return ccba; } /*! * \brief pixGetCCBorders() * * \param[in] pixs 1 bpp, one 8-connected component * \param[in] box of %pixs, in global coords * \return ccbord, or NULL on error * *
 * Notes:
 *      (1) We are finding the exterior and interior borders
 *          of an 8-connected component.   This should be used
 *          on a pix that has exactly one 8-connected component.
 *      (2) Typically, pixs is a c.c. in some larger pix.  The
 *          input box gives its location in global coordinates.
 *          This box is saved, as well as the boxes for the
 *          borders of any holes within the c.c., but the latter
 *          are given in relative coords within the c.c.
 *      (3) The calculations for the exterior border are done
 *          on a pix with a 1-pixel
 *          added border, but the saved pixel coordinates
 *          are the correct (relative) ones for the input pix
 *          (without a 1-pixel border)
 *      (4) For the definition of the three tables -- xpostab[], ypostab[]
 *          and qpostab[] -- see above where they are defined.
 * 
*/ static CCBORD * pixGetCCBorders(PIX *pixs, BOX *box) { l_int32 allzero, i, x, xh, w, nh; l_int32 xs, ys; /* starting hole border pixel, relative in pixs */ l_uint32 val; BOX *boxt, *boxe; BOXA *boxa; CCBORD *ccb; PIX *pixh; /* for hole components */ PIX *pixt; PIXA *pixa; PROCNAME("pixGetCCBorders"); if (!pixs) return (CCBORD *)ERROR_PTR("pixs not defined", procName, NULL); if (!box) return (CCBORD *)ERROR_PTR("box not defined", procName, NULL); if (pixGetDepth(pixs) != 1) return (CCBORD *)ERROR_PTR("pixs not binary", procName, NULL); pixZero(pixs, &allzero); if (allzero) return (CCBORD *)ERROR_PTR("pixs all 0", procName, NULL); if ((ccb = ccbCreate(pixs)) == NULL) return (CCBORD *)ERROR_PTR("ccb not made", procName, NULL); /* Get the exterior border */ pixGetOuterBorder(ccb, pixs, box); /* Find the holes, if any */ if ((pixh = pixHolesByFilling(pixs, 4)) == NULL) { ccbDestroy(&ccb); return (CCBORD *)ERROR_PTR("pixh not made", procName, NULL); } pixZero(pixh, &allzero); if (allzero) { /* no holes */ pixDestroy(&pixh); return ccb; } /* Get c.c. and locations of the holes */ if ((boxa = pixConnComp(pixh, &pixa, 4)) == NULL) { ccbDestroy(&ccb); pixDestroy(&pixh); return (CCBORD *)ERROR_PTR("boxa not made", procName, NULL); } nh = boxaGetCount(boxa); /* lept_stderr("%d holes\n", nh); */ /* For each hole, find an interior pixel within the hole, * then march to the right and stop at the first border * pixel. Save the bounding box of the border, which * is 1 pixel bigger on each side than the bounding box * of the hole itself. Note that we use a pix of the * c.c. of the hole itself to be sure that we start * with a pixel in the hole of the proper component. * If we did everything from the parent component, it is * possible to start in a different hole that is within * the b.b. of a larger hole. */ w = pixGetWidth(pixs); for (i = 0; i < nh; i++) { boxt = boxaGetBox(boxa, i, L_CLONE); pixt = pixaGetPix(pixa, i, L_CLONE); ys = boxt->y; /* there must be a hole pixel on this raster line */ for (x = 0; x < boxt->w; x++) { /* look for (fg) hole pixel */ pixGetPixel(pixt, x, 0, &val); if (val == 1) { xh = x; break; } } if (x == boxt->w) { L_WARNING("no hole pixel found!\n", procName); continue; } for (x = xh + boxt->x; x < w; x++) { /* look for (fg) border pixel */ pixGetPixel(pixs, x, ys, &val); if (val == 1) { xs = x; break; } } boxe = boxCreate(boxt->x - 1, boxt->y - 1, boxt->w + 2, boxt->h + 2); #if DEBUG_PRINT boxPrintStreamInfo(stderr, box); boxPrintStreamInfo(stderr, boxe); lept_stderr("xs = %d, ys = %d\n", xs, ys); #endif /* DEBUG_PRINT */ pixGetHoleBorder(ccb, pixs, boxe, xs, ys); boxDestroy(&boxt); boxDestroy(&boxe); pixDestroy(&pixt); } boxaDestroy(&boxa); pixaDestroy(&pixa); pixDestroy(&pixh); return ccb; } /*! * \brief pixGetOuterBordersPtaa() * * \param[in] pixs 1 bpp * \return ptaa of outer borders, in global coords, or NULL on error */ PTAA * pixGetOuterBordersPtaa(PIX *pixs) { l_int32 i, n; BOX *box; BOXA *boxa; PIX *pix; PIXA *pixa; PTA *pta; PTAA *ptaa; PROCNAME("pixGetOuterBordersPtaa"); if (!pixs) return (PTAA *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 1) return (PTAA *)ERROR_PTR("pixs not binary", procName, NULL); boxa = pixConnComp(pixs, &pixa, 8); n = boxaGetCount(boxa); if (n == 0) { boxaDestroy(&boxa); pixaDestroy(&pixa); return (PTAA *)ERROR_PTR("pixs empty", procName, NULL); } ptaa = ptaaCreate(n); for (i = 0; i < n; i++) { box = boxaGetBox(boxa, i, L_CLONE); pix = pixaGetPix(pixa, i, L_CLONE); pta = pixGetOuterBorderPta(pix, box); if (pta) ptaaAddPta(ptaa, pta, L_INSERT); boxDestroy(&box); pixDestroy(&pix); } pixaDestroy(&pixa); boxaDestroy(&boxa); return ptaa; } /*! * \brief pixGetOuterBorderPta() * * \param[in] pixs 1 bpp, one 8-connected component * \param[in] box [optional] of %pixs, in global coordinates * \return pta of outer border, in global coords, or NULL on error * *
 * Notes:
 *      (1) We are finding the exterior border of a single 8-connected
 *          component.
 *      (2) If box is NULL, the outline returned is in the local coords
 *          of the input pix.  Otherwise, box is assumed to give the
 *          location of the pix in global coordinates, and the returned
 *          pta will be in those global coordinates.
 * 
*/ static PTA * pixGetOuterBorderPta(PIX *pixs, BOX *box) { l_int32 allzero, x, y; BOX *boxt; CCBORD *ccb; PTA *ptaloc, *ptad; PROCNAME("pixGetOuterBorderPta"); if (!pixs) return (PTA *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 1) return (PTA *)ERROR_PTR("pixs not binary", procName, NULL); pixZero(pixs, &allzero); if (allzero) return (PTA *)ERROR_PTR("pixs all 0", procName, NULL); if ((ccb = ccbCreate(pixs)) == NULL) return (PTA *)ERROR_PTR("ccb not made", procName, NULL); if (!box) boxt = boxCreate(0, 0, pixGetWidth(pixs), pixGetHeight(pixs)); else boxt = boxClone(box); /* Get the exterior border in local coords */ pixGetOuterBorder(ccb, pixs, boxt); if ((ptaloc = ptaaGetPta(ccb->local, 0, L_CLONE)) == NULL) { ccbDestroy(&ccb); boxDestroy(&boxt); return (PTA *)ERROR_PTR("ptaloc not made", procName, NULL); } /* Transform to global coordinates, if they are given */ if (box) { boxGetGeometry(box, &x, &y, NULL, NULL); ptad = ptaTransform(ptaloc, x, y, 1.0, 1.0); } else { ptad = ptaClone(ptaloc); } ptaDestroy(&ptaloc); boxDestroy(&boxt); ccbDestroy(&ccb); return ptad; } /*---------------------------------------------------------------------* * Lower-level border-finding routines * *---------------------------------------------------------------------*/ /*! * \brief pixGetOuterBorder() * * \param[in] ccb unfilled * \param[in] pixs for the component at hand * \param[in] box for the component, in global coords * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) the border is saved in relative coordinates within
 *          the c.c. (pixs).  Because the calculation is done
 *          in pixb with added 1 pixel border, we must subtract
 *          1 from each pixel value before storing it.
 *      (2) the stopping condition is that after the first pixel is
 *          returned to, the next pixel is the second pixel.  Having
 *          these 2 pixels recur in sequence proves the path is closed,
 *          and we do not store the second pixel again.
 * 
*/ l_ok pixGetOuterBorder(CCBORD *ccb, PIX *pixs, BOX *box) { l_int32 fpx, fpy, spx, spy, qpos; l_int32 px, py, npx, npy; l_int32 w, h, wpl; l_uint32 *data; PTA *pta; PIX *pixb; /* with 1 pixel border */ PROCNAME("pixGetOuterBorder"); if (!ccb) return ERROR_INT("ccb not defined", procName, 1); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (!box) return ERROR_INT("box not defined", procName, 1); /* Add 1-pixel border all around, and find start pixel */ if ((pixb = pixAddBorder(pixs, 1, 0)) == NULL) return ERROR_INT("pixs not made", procName, 1); if (!nextOnPixelInRaster(pixb, 1, 1, &px, &py)) { pixDestroy(&pixb); return ERROR_INT("no start pixel found", procName, 1); } qpos = 0; /* relative to p */ fpx = px; /* save location of first pixel on border */ fpy = py; /* Save box and start pixel in relative coords */ boxaAddBox(ccb->boxa, box, L_COPY); ptaAddPt(ccb->start, px - 1, py - 1); pta = ptaCreate(0); ptaaAddPta(ccb->local, pta, L_INSERT); ptaAddPt(pta, px - 1, py - 1); /* initial point */ pixGetDimensions(pixb, &w, &h, NULL); data = pixGetData(pixb); wpl = pixGetWpl(pixb); /* Get the second point; if there is none, return */ if (findNextBorderPixel(w, h, data, wpl, px, py, &qpos, &npx, &npy)) { pixDestroy(&pixb); return 0; } spx = npx; /* save location of second pixel on border */ spy = npy; ptaAddPt(pta, npx - 1, npy - 1); /* second point */ px = npx; py = npy; while (1) { findNextBorderPixel(w, h, data, wpl, px, py, &qpos, &npx, &npy); if (px == fpx && py == fpy && npx == spx && npy == spy) break; ptaAddPt(pta, npx - 1, npy - 1); px = npx; py = npy; } pixDestroy(&pixb); return 0; } /*! * \brief pixGetHoleBorder() * * \param[in] ccb the exterior border is already made * \param[in] pixs for the connected component at hand * \param[in] box for the specific hole border, in relative * coordinates to the c.c. * \param[in] xs, ys first pixel on hole border, relative to c.c. * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) we trace out hole border on pixs without addition
 *          of single pixel added border to pixs
 *      (2) therefore all coordinates are relative within the c.c. (pixs)
 *      (3) same position tables and stopping condition as for
 *          exterior borders
 * 
*/ static l_ok pixGetHoleBorder(CCBORD *ccb, PIX *pixs, BOX *box, l_int32 xs, l_int32 ys) { l_int32 fpx, fpy, spx, spy, qpos; l_int32 px, py, npx, npy; l_int32 w, h, wpl; l_uint32 *data; PTA *pta; PROCNAME("pixGetHoleBorder"); if (!ccb) return ERROR_INT("ccb not defined", procName, 1); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (!box) return ERROR_INT("box not defined", procName, 1); /* Add border and find start pixel */ qpos = 0; /* orientation of Q relative to P */ fpx = xs; /* save location of first pixel on border */ fpy = ys; /* Save box and start pixel */ boxaAddBox(ccb->boxa, box, L_COPY); ptaAddPt(ccb->start, xs, ys); pta = ptaCreate(0); ptaaAddPta(ccb->local, pta, L_INSERT); ptaAddPt(pta, xs, ys); /* initial pixel */ w = pixGetWidth(pixs); h = pixGetHeight(pixs); data = pixGetData(pixs); wpl = pixGetWpl(pixs); /* Get the second point; there should always be at least 4 pts * in a minimal hole border! */ if (findNextBorderPixel(w, h, data, wpl, xs, ys, &qpos, &npx, &npy)) return ERROR_INT("isolated hole border point!", procName, 1); spx = npx; /* save location of second pixel on border */ spy = npy; ptaAddPt(pta, npx, npy); /* second pixel */ px = npx; py = npy; while (1) { findNextBorderPixel(w, h, data, wpl, px, py, &qpos, &npx, &npy); if (px == fpx && py == fpy && npx == spx && npy == spy) break; ptaAddPt(pta, npx, npy); px = npx; py = npy; } return 0; } /*! * \brief findNextBorderPixel() * * \param[in] w, h * \param[in] data, wpl * \param[in] px, py current P * \param[in,out] pqpos input current Q; new Q * \param[out] pnpx, pnpy new P * \return 0 if next pixel found; 1 otherwise * *
 * Notes:
 *      (1) qpos increases clockwise from 0 to 7, with 0 at
 *          location with Q to left of P:   Q P
 *      (2) this is a low-level function that does not check input
 *          parameters.  All calling functions should check them.
 * 
*/ static l_int32 findNextBorderPixel(l_int32 w, l_int32 h, l_uint32 *data, l_int32 wpl, l_int32 px, l_int32 py, l_int32 *pqpos, l_int32 *pnpx, l_int32 *pnpy) { l_int32 qpos, i, pos, npx, npy, val; l_uint32 *line; qpos = *pqpos; for (i = 1; i < 8; i++) { pos = (qpos + i) % 8; npx = px + xpostab[pos]; npy = py + ypostab[pos]; if (npx < 0 || npx >= w || npy < 0 || npy >= h) continue; line = data + npy * wpl; val = GET_DATA_BIT(line, npx); if (val) { *pnpx = npx; *pnpy = npy; *pqpos = qpostab[pos]; return 0; } } return 1; } /*! * \brief locateOutsideSeedPixel() * * \param[in] fpx, fpy location of first pixel * \param[in] spx, spy location of second pixel * \param[out] pxs, pys seed pixel to be returned * *
 * Notes:
 *      (1) The first and second pixels must be 8-adjacent,
 *          so |dx| <= 1 and |dy| <= 1 and both dx and dy
 *          cannot be 0.  There are 8 possible cases.
 *      (2) The seed pixel is OUTSIDE the foreground of the c.c.
 *      (3) These rules are for the situation where the INSIDE
 *          of the c.c. is on the right as you follow the border:
 *          cw for an exterior border and ccw for a hole border.
 * 
*/ static void locateOutsideSeedPixel(l_int32 fpx, l_int32 fpy, l_int32 spx, l_int32 spy, l_int32 *pxs, l_int32 *pys) { l_int32 dx, dy; dx = spx - fpx; dy = spy - fpy; if (dx * dy == 1) { *pxs = fpx + dx; *pys = fpy; } else if (dx * dy == -1) { *pxs = fpx; *pys = fpy + dy; } else if (dx == 0) { *pxs = fpx + dy; *pys = fpy + dy; } else /* dy == 0 */ { *pxs = fpx + dx; *pys = fpy - dx; } return; } /*---------------------------------------------------------------------* * Border conversions * *---------------------------------------------------------------------*/ /*! * \brief ccbaGenerateGlobalLocs() * * \param[in] ccba with local chain ptaa of borders computed * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) This uses the pixel locs in the local ptaa, which are all
 *          relative to each c.c., to find the global pixel locations,
 *          and stores them in the global ptaa.
 * 
*/ l_ok ccbaGenerateGlobalLocs(CCBORDA *ccba) { l_int32 ncc, nb, n, i, j, k, xul, yul, x, y; CCBORD *ccb; PTAA *ptaal, *ptaag; PTA *ptal, *ptag; PROCNAME("ccbaGenerateGlobalLocs"); if (!ccba) return ERROR_INT("ccba not defined", procName, 1); ncc = ccbaGetCount(ccba); /* number of c.c. */ for (i = 0; i < ncc; i++) { ccb = ccbaGetCcb(ccba, i); /* Get the UL corner in global coords, (xul, yul), of the c.c. */ boxaGetBoxGeometry(ccb->boxa, 0, &xul, &yul, NULL, NULL); /* Make a new global ptaa, removing any old one */ ptaal = ccb->local; nb = ptaaGetCount(ptaal); /* number of borders */ if (ccb->global) /* remove old one */ ptaaDestroy(&ccb->global); if ((ptaag = ptaaCreate(nb)) == NULL) { ccbDestroy(&ccb); return ERROR_INT("ptaag not made", procName, 1); } ccb->global = ptaag; /* save new one */ /* Iterate through the borders for this c.c. */ for (j = 0; j < nb; j++) { ptal = ptaaGetPta(ptaal, j, L_CLONE); n = ptaGetCount(ptal); /* number of pixels in border */ ptag = ptaCreate(n); ptaaAddPta(ptaag, ptag, L_INSERT); for (k = 0; k < n; k++) { ptaGetIPt(ptal, k, &x, &y); ptaAddPt(ptag, x + xul, y + yul); } ptaDestroy(&ptal); } ccbDestroy(&ccb); } return 0; } /*! * \brief ccbaGenerateStepChains() * * \param[in] ccba with local chain ptaa of borders computed * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) This uses the pixel locs in the local ptaa,
 *          which are all relative to each c.c., to find
 *          the step directions for successive pixels in
 *          the chain, and stores them in the step numaa.
 *      (2) To get the step direction, use
 *              1   2   3
 *              0   P   4
 *              7   6   5
 *          where P is the previous pixel at (px, py).  The step direction
 *          is the number (from 0 through 7) for each relative location
 *          of the current pixel at (cx, cy).  It is easily found by
 *          indexing into a 2-d 3x3 array (dirtab).
 * 
*/ l_ok ccbaGenerateStepChains(CCBORDA *ccba) { l_int32 ncc, nb, n, i, j, k; l_int32 px, py, cx, cy, stepdir; l_int32 dirtab[][3] = {{1, 2, 3}, {0, -1, 4}, {7, 6, 5}}; CCBORD *ccb; NUMA *na; NUMAA *naa; /* step chain code; to be made */ PTA *ptal; PTAA *ptaal; /* local chain code */ PROCNAME("ccbaGenerateStepChains"); if (!ccba) return ERROR_INT("ccba not defined", procName, 1); ncc = ccbaGetCount(ccba); /* number of c.c. */ for (i = 0; i < ncc; i++) { ccb = ccbaGetCcb(ccba, i); /* Make a new step numaa, removing any old one */ ptaal = ccb->local; nb = ptaaGetCount(ptaal); /* number of borders */ if (ccb->step) /* remove old one */ numaaDestroy(&ccb->step); if ((naa = numaaCreate(nb)) == NULL) { ccbDestroy(&ccb); return ERROR_INT("naa not made", procName, 1); } ccb->step = naa; /* save new one */ /* Iterate through the borders for this c.c. */ for (j = 0; j < nb; j++) { ptal = ptaaGetPta(ptaal, j, L_CLONE); n = ptaGetCount(ptal); /* number of pixels in border */ if (n == 1) { /* isolated pixel */ na = numaCreate(1); /* but leave it empty */ } else { /* trace out the boundary */ na = numaCreate(n); ptaGetIPt(ptal, 0, &px, &py); for (k = 1; k < n; k++) { ptaGetIPt(ptal, k, &cx, &cy); stepdir = dirtab[1 + cy - py][1 + cx - px]; numaAddNumber(na, stepdir); px = cx; py = cy; } } numaaAddNuma(naa, na, L_INSERT); ptaDestroy(&ptal); } ccbDestroy(&ccb); /* just decrement refcount */ } return 0; } /*! * \brief ccbaStepChainsToPixCoords() * * \param[in] ccba with step chains numaa of borders * \param[in] coordtype CCB_GLOBAL_COORDS or CCB_LOCAL_COORDS * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) This uses the step chain data in each ccb to determine
 *          the pixel locations, either global or local,
 *          and stores them in the appropriate ptaa,
 *          either global or local.  For the latter, the
 *          pixel locations are relative to the c.c.
 * 
*/ l_ok ccbaStepChainsToPixCoords(CCBORDA *ccba, l_int32 coordtype) { l_int32 ncc, nb, n, i, j, k; l_int32 xul, yul, xstart, ystart, x, y, stepdir; BOXA *boxa; CCBORD *ccb; NUMA *na; NUMAA *naa; PTAA *ptaan; /* new pix coord ptaa */ PTA *ptas, *ptan; PROCNAME("ccbaStepChainsToPixCoords"); if (!ccba) return ERROR_INT("ccba not defined", procName, 1); if (coordtype != CCB_GLOBAL_COORDS && coordtype != CCB_LOCAL_COORDS) return ERROR_INT("coordtype not valid", procName, 1); ncc = ccbaGetCount(ccba); /* number of c.c. */ for (i = 0; i < ncc; i++) { ccb = ccbaGetCcb(ccba, i); if ((naa = ccb->step) == NULL) { ccbDestroy(&ccb); return ERROR_INT("step numaa not found", procName, 1); } if ((boxa = ccb->boxa) == NULL) { ccbDestroy(&ccb); return ERROR_INT("boxa not found", procName, 1); } if ((ptas = ccb->start) == NULL) { ccbDestroy(&ccb); return ERROR_INT("start pta not found", procName, 1); } /* For global coords, get the (xul, yul) of the c.c.; * otherwise, use relative coords. */ if (coordtype == CCB_LOCAL_COORDS) { xul = 0; yul = 0; } else { /* coordtype == CCB_GLOBAL_COORDS */ /* Get UL corner in global coords */ if (boxaGetBoxGeometry(boxa, 0, &xul, &yul, NULL, NULL)) { ccbDestroy(&ccb); return ERROR_INT("bounding rectangle not found", procName, 1); } } /* Make a new ptaa, removing any old one */ nb = numaaGetCount(naa); /* number of borders */ if ((ptaan = ptaaCreate(nb)) == NULL) { ccbDestroy(&ccb); return ERROR_INT("ptaan not made", procName, 1); } if (coordtype == CCB_LOCAL_COORDS) { if (ccb->local) /* remove old one */ ptaaDestroy(&ccb->local); ccb->local = ptaan; /* save new local chain */ } else { /* coordtype == CCB_GLOBAL_COORDS */ if (ccb->global) /* remove old one */ ptaaDestroy(&ccb->global); ccb->global = ptaan; /* save new global chain */ } /* Iterate through the borders for this c.c. */ for (j = 0; j < nb; j++) { na = numaaGetNuma(naa, j, L_CLONE); n = numaGetCount(na); /* number of steps in border */ if ((ptan = ptaCreate(n + 1)) == NULL) { ccbDestroy(&ccb); numaDestroy(&na); return ERROR_INT("ptan not made", procName, 1); } ptaaAddPta(ptaan, ptan, L_INSERT); ptaGetIPt(ptas, j, &xstart, &ystart); x = xul + xstart; y = yul + ystart; ptaAddPt(ptan, x, y); for (k = 0; k < n; k++) { numaGetIValue(na, k, &stepdir); x += xpostab[stepdir]; y += ypostab[stepdir]; ptaAddPt(ptan, x, y); } numaDestroy(&na); } ccbDestroy(&ccb); } return 0; } /*! * \brief ccbaGenerateSPGlobalLocs() * * \param[in] ccba * \param[in] ptsflag CCB_SAVE_ALL_PTS or CCB_SAVE_TURNING_PTS * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) This calculates the splocal rep if not yet made.
 *      (2) It uses the local pixel values in splocal, the single
 *          path pta, which are all relative to each c.c., to find
 *          the corresponding global pixel locations, and stores
 *          them in the spglobal pta.
 *      (3) This lists only the turning points: it both makes a
 *          valid svg file and is typically about half the size
 *          when all border points are listed.
 * 
*/ l_ok ccbaGenerateSPGlobalLocs(CCBORDA *ccba, l_int32 ptsflag) { l_int32 ncc, npt, i, j, xul, yul, x, y, delx, dely; l_int32 xp, yp, delxp, delyp; /* prev point and increments */ CCBORD *ccb; PTA *ptal, *ptag; PROCNAME("ccbaGenerateSPGlobalLocs"); if (!ccba) return ERROR_INT("ccba not defined", procName, 1); /* Make sure we have a local single path representation */ if ((ccb = ccbaGetCcb(ccba, 0)) == NULL) return ERROR_INT("no ccb", procName, 1); if (!ccb->splocal) ccbaGenerateSinglePath(ccba); ccbDestroy(&ccb); /* clone ref */ ncc = ccbaGetCount(ccba); /* number of c.c. */ for (i = 0; i < ncc; i++) { ccb = ccbaGetCcb(ccba, i); /* Get the UL corner in global coords, (xul, yul), of the c.c. */ if (boxaGetBoxGeometry(ccb->boxa, 0, &xul, &yul, NULL, NULL)) { ccbDestroy(&ccb); return ERROR_INT("bounding rectangle not found", procName, 1); } /* Make a new spglobal pta, removing any old one */ ptal = ccb->splocal; npt = ptaGetCount(ptal); /* number of points */ if (ccb->spglobal) /* remove old one */ ptaDestroy(&ccb->spglobal); if ((ptag = ptaCreate(npt)) == NULL) { ccbDestroy(&ccb); return ERROR_INT("ptag not made", procName, 1); } ccb->spglobal = ptag; /* save new one */ /* Convert local to global */ if (ptsflag == CCB_SAVE_ALL_PTS) { for (j = 0; j < npt; j++) { ptaGetIPt(ptal, j, &x, &y); ptaAddPt(ptag, x + xul, y + yul); } } else { /* ptsflag = CCB_SAVE_TURNING_PTS */ ptaGetIPt(ptal, 0, &xp, &yp); /* get the 1st pt */ ptaAddPt(ptag, xp + xul, yp + yul); /* save the 1st pt */ if (npt == 2) { /* get and save the 2nd pt */ ptaGetIPt(ptal, 1, &x, &y); ptaAddPt(ptag, x + xul, y + yul); } else if (npt > 2) { ptaGetIPt(ptal, 1, &x, &y); delxp = x - xp; delyp = y - yp; xp = x; yp = y; for (j = 2; j < npt; j++) { ptaGetIPt(ptal, j, &x, &y); delx = x - xp; dely = y - yp; if (delx != delxp || dely != delyp) ptaAddPt(ptag, xp + xul, yp + yul); xp = x; yp = y; delxp = delx; delyp = dely; } ptaAddPt(ptag, xp + xul, yp + yul); } } ccbDestroy(&ccb); /* clone ref */ } return 0; } /*---------------------------------------------------------------------* * Conversion to single path * *---------------------------------------------------------------------*/ /*! * \brief ccbaGenerateSinglePath() * * \param[in] ccba * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) Generates a single border in local pixel coordinates.
 *          For each c.c., if there is just an outer border, copy it.
 *          If there are also hole borders, for each hole border,
 *          determine the smallest horizontal or vertical
 *          distance from the border to the outside of the c.c.,
 *          and find a path through the c.c. for this cut.
 *          We do this in a way that guarantees a pixel from the
 *          hole border is the starting point of the path, and
 *          we must verify that the path intersects the outer
 *          border (if it intersects it, then it ends on it).
 *          One can imagine pathological cases, but they may not
 *          occur in images of text characters and un-textured
 *          line graphics.
 *      (2) Once it is verified that the path through the c.c.
 *          intersects both the hole and outer borders, we
 *          generate the full single path for all borders in the
 *          c.c.  Starting at the start point on the outer
 *          border, when we hit a line on a cut, we take
 *          the cut, do the hole border, and return on the cut
 *          to the outer border.  We compose a pta of the
 *          outer border pts that are on cut paths, and for
 *          every point on the outer border (as we go around),
 *          we check against this pta.  When we find a matching
 *          point in the pta, we do its cut path and hole border.
 *          The single path is saved in the ccb.
 * 
*/ l_ok ccbaGenerateSinglePath(CCBORDA *ccba) { l_int32 i, j, k, ncc, nb, ncut, npt, dir, len, state, lostholes; l_int32 x, y, xl, yl, xf, yf; BOX *boxinner; BOXA *boxa; CCBORD *ccb; PTA *pta, *ptac, *ptah; PTA *ptahc; /* cyclic permutation of hole border, with end pts at cut */ PTA *ptas; /* output result: new single path for c.c. */ PTA *ptaf; /* points on the hole borders that intersect with cuts */ PTA *ptal; /* points on outer border that intersect with cuts */ PTA *ptap, *ptarp; /* path and reverse path between borders */ PTAA *ptaa; PTAA *ptaap; /* ptaa for all paths between borders */ PROCNAME("ccbaGenerateSinglePath"); if (!ccba) return ERROR_INT("ccba not defined", procName, 1); ncc = ccbaGetCount(ccba); /* number of c.c. */ lostholes = 0; for (i = 0; i < ncc; i++) { ccb = ccbaGetCcb(ccba, i); if ((ptaa = ccb->local) == NULL) { L_WARNING("local pixel loc array not found\n", procName); continue; } nb = ptaaGetCount(ptaa); /* number of borders in the c.c. */ /* Prepare the output pta */ if (ccb->splocal) ptaDestroy(&ccb->splocal); ptas = ptaCreate(0); ccb->splocal = ptas; /* If no holes, just concat the outer border */ pta = ptaaGetPta(ptaa, 0, L_CLONE); if (nb == 1 || nb > NMAX_HOLES + 1) { ptaJoin(ptas, pta, 0, -1); ptaDestroy(&pta); /* remove clone */ ccbDestroy(&ccb); /* remove clone */ continue; } /* Find the (nb - 1) cut paths that connect holes * with outer border */ boxa = ccb->boxa; ptaap = ptaaCreate(nb - 1); ptaf = ptaCreate(nb - 1); ptal = ptaCreate(nb - 1); for (j = 1; j < nb; j++) { boxinner = boxaGetBox(boxa, j, L_CLONE); /* Find a short path and store it */ ptac = getCutPathForHole(ccb->pix, pta, boxinner, &dir, &len); if (len == 0) { /* lost the hole */ lostholes++; /* boxPrintStreamInfo(stderr, boxa->box[0]); */ } ptaaAddPta(ptaap, ptac, L_INSERT); /* lept_stderr("dir = %d, length = %d\n", dir, len); */ /* ptaWriteStream(stderr, ptac, 1); */ /* Store the first and last points in the cut path, * which must be on a hole border and the outer * border, respectively */ ncut = ptaGetCount(ptac); if (ncut == 0) { /* missed hole; neg coords won't match */ ptaAddPt(ptaf, -1, -1); ptaAddPt(ptal, -1, -1); } else { ptaGetIPt(ptac, 0, &x, &y); ptaAddPt(ptaf, x, y); ptaGetIPt(ptac, ncut - 1, &x, &y); ptaAddPt(ptal, x, y); } boxDestroy(&boxinner); } /* Make a single path for the c.c. using these connections */ npt = ptaGetCount(pta); /* outer border pts */ for (k = 0; k < npt; k++) { ptaGetIPt(pta, k, &x, &y); if (k == 0) { /* if there is a cut at the first point, * we can wait until the end to take it */ ptaAddPt(ptas, x, y); continue; } state = L_NOT_FOUND; for (j = 0; j < nb - 1; j++) { /* iterate over cut end pts */ ptaGetIPt(ptal, j, &xl, &yl); /* cut point on outer border */ if (x == xl && y == yl) { /* take this cut to the hole */ state = L_FOUND; ptap = ptaaGetPta(ptaap, j, L_CLONE); ptarp = ptaReverse(ptap, 1); /* Cut point on hole border: */ ptaGetIPt(ptaf, j, &xf, &yf); /* Hole border: */ ptah = ptaaGetPta(ptaa, j + 1, L_CLONE); ptahc = ptaCyclicPerm(ptah, xf, yf); /* ptaWriteStream(stderr, ptahc, 1); */ ptaJoin(ptas, ptarp, 0, -1); ptaJoin(ptas, ptahc, 0, -1); ptaJoin(ptas, ptap, 0, -1); ptaDestroy(&ptap); ptaDestroy(&ptarp); ptaDestroy(&ptah); ptaDestroy(&ptahc); break; } } if (state == L_NOT_FOUND) ptaAddPt(ptas, x, y); } /* ptaWriteStream(stderr, ptas, 1); */ ptaaDestroy(&ptaap); ptaDestroy(&ptaf); ptaDestroy(&ptal); ptaDestroy(&pta); /* remove clone */ ccbDestroy(&ccb); /* remove clone */ } if (lostholes > 0) L_INFO("***** %d lost holes *****\n", procName, lostholes); return 0; } /*! * \brief getCutPathForHole() * * \param[in] pix 1 bpp, of c.c. * \param[in] pta of outer border * \param[in] boxinner bounding box of hole path * \param[out] pdir direction (0-3), returned; only needed for debug * \param[out] plen length of path, returned * \return pta of pts on cut path from the hole border * to the outer border, including end points on * both borders; or NULL on error * *
 * Notes:
 *      (1) If we don't find a path, we return a pta with no pts
 *          in it and len = 0.
 *      (2) The goal is to get a reasonably short path between the
 *          inner and outer borders, that goes entirely within the fg of
 *          the pix.  This function is cheap-and-dirty, may fail for some
 *          holes in complex topologies such as those you might find in a
 *          moderately dark scanned halftone.  If it fails to find a
 *          path to any particular hole, the hole will not be rendered.
 *          Nevertheless, the image can be perfectly reconstructed
 *          from the boundary representation.
 * 
*/ PTA * getCutPathForHole(PIX *pix, PTA *pta, BOX *boxinner, l_int32 *pdir, l_int32 *plen) { l_int32 w, h, nc, x, y, xl, yl, xmid, ymid; l_uint32 val; PTA *ptac; PROCNAME("getCutPathForHole"); if (!pix) return (PTA *)ERROR_PTR("pix not defined", procName, NULL); if (!pta) return (PTA *)ERROR_PTR("pta not defined", procName, NULL); if (!boxinner) return (PTA *)ERROR_PTR("boxinner not defined", procName, NULL); pixGetDimensions(pix, &w, &h, NULL); ptac = ptaCreate(4); xmid = boxinner->x + boxinner->w / 2; ymid = boxinner->y + boxinner->h / 2; /* try top first */ for (y = ymid; y >= 0; y--) { pixGetPixel(pix, xmid, y, &val); if (val == 1) { ptaAddPt(ptac, xmid, y); break; } } for (y = y - 1; y >= 0; y--) { pixGetPixel(pix, xmid, y, &val); if (val == 1) ptaAddPt(ptac, xmid, y); else break; } nc = ptaGetCount(ptac); ptaGetIPt(ptac, nc - 1, &xl, &yl); if (ptaContainsPt(pta, xl, yl)) { *pdir = 1; *plen = nc; return ptac; } /* Next try bottom */ ptaEmpty(ptac); for (y = ymid; y < h; y++) { pixGetPixel(pix, xmid, y, &val); if (val == 1) { ptaAddPt(ptac, xmid, y); break; } } for (y = y + 1; y < h; y++) { pixGetPixel(pix, xmid, y, &val); if (val == 1) ptaAddPt(ptac, xmid, y); else break; } nc = ptaGetCount(ptac); ptaGetIPt(ptac, nc - 1, &xl, &yl); if (ptaContainsPt(pta, xl, yl)) { *pdir = 3; *plen = nc; return ptac; } /* Next try left */ ptaEmpty(ptac); for (x = xmid; x >= 0; x--) { pixGetPixel(pix, x, ymid, &val); if (val == 1) { ptaAddPt(ptac, x, ymid); break; } } for (x = x - 1; x >= 0; x--) { pixGetPixel(pix, x, ymid, &val); if (val == 1) ptaAddPt(ptac, x, ymid); else break; } nc = ptaGetCount(ptac); ptaGetIPt(ptac, nc - 1, &xl, &yl); if (ptaContainsPt(pta, xl, yl)) { *pdir = 0; *plen = nc; return ptac; } /* Finally try right */ ptaEmpty(ptac); for (x = xmid; x < w; x++) { pixGetPixel(pix, x, ymid, &val); if (val == 1) { ptaAddPt(ptac, x, ymid); break; } } for (x = x + 1; x < w; x++) { pixGetPixel(pix, x, ymid, &val); if (val == 1) ptaAddPt(ptac, x, ymid); else break; } nc = ptaGetCount(ptac); ptaGetIPt(ptac, nc - 1, &xl, &yl); if (ptaContainsPt(pta, xl, yl)) { *pdir = 2; *plen = nc; return ptac; } /* Sometimes, there is nothing. */ ptaEmpty(ptac); *plen = 0; return ptac; } /*---------------------------------------------------------------------* * Border rendering * *---------------------------------------------------------------------*/ /*! * \brief ccbaDisplayBorder() * * \param[in] ccba * \return pix of border pixels, or NULL on error * *
 * Notes:
 *      (1) Uses global ptaa, which gives each border pixel in
 *          global coordinates, and must be computed in advance
 *          by calling ccbaGenerateGlobalLocs().
 * 
*/ PIX * ccbaDisplayBorder(CCBORDA *ccba) { l_int32 ncc, nb, n, i, j, k, x, y; CCBORD *ccb; PIX *pixd; PTAA *ptaa; PTA *pta; PROCNAME("ccbaDisplayBorder"); if (!ccba) return (PIX *)ERROR_PTR("ccba not defined", procName, NULL); if ((pixd = pixCreate(ccba->w, ccba->h, 1)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); ncc = ccbaGetCount(ccba); /* number of c.c. */ for (i = 0; i < ncc; i++) { ccb = ccbaGetCcb(ccba, i); if ((ptaa = ccb->global) == NULL) { L_WARNING("global pixel loc array not found", procName); ccbDestroy(&ccb); continue; } nb = ptaaGetCount(ptaa); /* number of borders in the c.c. */ for (j = 0; j < nb; j++) { pta = ptaaGetPta(ptaa, j, L_CLONE); n = ptaGetCount(pta); /* number of pixels in the border */ for (k = 0; k < n; k++) { ptaGetIPt(pta, k, &x, &y); pixSetPixel(pixd, x, y, 1); } ptaDestroy(&pta); } ccbDestroy(&ccb); } return pixd; } /*! * \brief ccbaDisplaySPBorder() * * \param[in] ccba * \return pix of border pixels, or NULL on error * *
 * Notes:
 *      (1) Uses spglobal pta, which gives each border pixel in
 *          global coordinates, one path per c.c., and must
 *          be computed in advance by calling ccbaGenerateSPGlobalLocs().
 * 
*/ PIX * ccbaDisplaySPBorder(CCBORDA *ccba) { l_int32 ncc, npt, i, j, x, y; CCBORD *ccb; PIX *pixd; PTA *ptag; PROCNAME("ccbaDisplaySPBorder"); if (!ccba) return (PIX *)ERROR_PTR("ccba not defined", procName, NULL); if ((pixd = pixCreate(ccba->w, ccba->h, 1)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); ncc = ccbaGetCount(ccba); /* number of c.c. */ for (i = 0; i < ncc; i++) { ccb = ccbaGetCcb(ccba, i); if ((ptag = ccb->spglobal) == NULL) { L_WARNING("spglobal pixel loc array not found\n", procName); ccbDestroy(&ccb); continue; } npt = ptaGetCount(ptag); /* number of pixels on path */ for (j = 0; j < npt; j++) { ptaGetIPt(ptag, j, &x, &y); pixSetPixel(pixd, x, y, 1); } ccbDestroy(&ccb); /* clone ref */ } return pixd; } /*! * \brief ccbaDisplayImage1() * * \param[in] ccba * \return pix of image, or NULL on error * *
 * Notes:
 *      (1) Uses local ptaa, which gives each border pixel in
 *          local coordinates, so the actual pixel positions must
 *          be computed using all offsets.
 *      (2) For the holes, use coordinates relative to the c.c.
 *      (3) This is slower than Method 2.
 *      (4) This uses topological properties (Method 1) to do scan
 *          conversion to raster
 *
 *  This algorithm deserves some commentary.
 *
 *  I first tried the following:
 *    ~ outer borders: 4-fill from outside, stopping at the
 *         border, using pixFillClosedBorders()
 *    ~ inner borders: 4-fill from outside, stopping again
 *         at the border, XOR with the border, and invert
 *         to get the hole.  This did not work, because if
 *         you have a hole border that looks like:
 *
 *                x x x x x x
 *                x          x
 *                x   x x x   x
 *                  x x o x   x
 *                      x     x
 *                      x     x
 *                        x x x
 *
 *         if you 4-fill from the outside, the pixel 'o' will
 *         not be filled!  XORing with the border leaves it OFF.
 *         Inverting then gives a single bad ON pixel that is not
 *         actually part of the hole.
 *
 *  So what you must do instead is 4-fill the holes from inside.
 *  You can do this from a seedfill, using a pix with the hole
 *  border as the filling mask.  But you need to start with a
 *  pixel inside the hole.  How is this determined?  The best
 *  way is from the contour.  We have a right-hand shoulder
 *  rule for inside (i.e., the filled region).   Take the
 *  first 2 pixels of the hole border, and compute dx and dy
 *  (second coord minus first coord:  dx = sx - fx, dy = sy - fy).
 *  There are 8 possibilities, depending on the values of dx and
 *  dy (which can each be -1, 0, and +1, but not both 0).
 *  These 8 cases can be broken into 4; see the simple algorithm below.
 *  Once you have an interior seed pixel, you fill from the seed,
 *  clipping with the hole border pix by filling into its invert.
 *
 *  You then successively XOR these interior filled components, in any order.
 * 
*/ PIX * ccbaDisplayImage1(CCBORDA *ccba) { l_int32 ncc, i, nb, n, j, k, x, y, xul, yul, xoff, yoff, w, h; l_int32 fpx, fpy, spx, spy, xs, ys; BOX *box; BOXA *boxa; CCBORD *ccb; PIX *pixd, *pixt, *pixh; PTAA *ptaa; PTA *pta; PROCNAME("ccbaDisplayImage1"); if (!ccba) return (PIX *)ERROR_PTR("ccba not defined", procName, NULL); if ((pixd = pixCreate(ccba->w, ccba->h, 1)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); ncc = ccbaGetCount(ccba); for (i = 0; i < ncc; i++) { ccb = ccbaGetCcb(ccba, i); if ((boxa = ccb->boxa) == NULL) { pixDestroy(&pixd); ccbDestroy(&ccb); return (PIX *)ERROR_PTR("boxa not found", procName, NULL); } /* Render border in pixt */ if ((ptaa = ccb->local) == NULL) { L_WARNING("local chain array not found\n", procName); ccbDestroy(&ccb); continue; } nb = ptaaGetCount(ptaa); /* number of borders in the c.c. */ for (j = 0; j < nb; j++) { if ((box = boxaGetBox(boxa, j, L_CLONE)) == NULL) { pixDestroy(&pixd); ccbDestroy(&ccb); return (PIX *)ERROR_PTR("b. box not found", procName, NULL); } if (j == 0) { boxGetGeometry(box, &xul, &yul, &w, &h); xoff = yoff = 0; } else { boxGetGeometry(box, &xoff, &yoff, &w, &h); } boxDestroy(&box); /* Render the border in a minimum-sized pix; * subtract xoff and yoff because the pixel * location is stored relative to the c.c., but * we need it relative to just the hole border. */ if ((pixt = pixCreate(w, h, 1)) == NULL) { pixDestroy(&pixd); ccbDestroy(&ccb); return (PIX *)ERROR_PTR("pixt not made", procName, NULL); } pta = ptaaGetPta(ptaa, j, L_CLONE); n = ptaGetCount(pta); /* number of pixels in the border */ for (k = 0; k < n; k++) { ptaGetIPt(pta, k, &x, &y); pixSetPixel(pixt, x - xoff, y - yoff, 1); if (j > 0) { /* need this for finding hole border pixel */ if (k == 0) { fpx = x - xoff; fpy = y - yoff; } if (k == 1) { spx = x - xoff; spy = y - yoff; } } } ptaDestroy(&pta); /* Get the filled component */ if (j == 0) { /* if outer border, fill from outer boundary */ if ((pixh = pixFillClosedBorders(pixt, 4)) == NULL) { pixDestroy(&pixd); pixDestroy(&pixt); ccbDestroy(&ccb); return (PIX *)ERROR_PTR("pixh not made", procName, NULL); } } else { /* fill the hole from inside */ /* get the location of a seed pixel in the hole */ locateOutsideSeedPixel(fpx, fpy, spx, spy, &xs, &ys); /* Put seed in hole and fill interior of hole, * using pixt as clipping mask */ pixh = pixCreateTemplate(pixt); pixSetPixel(pixh, xs, ys, 1); /* put seed pixel in hole */ pixInvert(pixt, pixt); /* to make filling mask */ pixSeedfillBinary(pixh, pixh, pixt, 4); /* 4-fill hole */ } /* XOR into the dest */ pixRasterop(pixd, xul + xoff, yul + yoff, w, h, PIX_XOR, pixh, 0, 0); pixDestroy(&pixt); pixDestroy(&pixh); } ccbDestroy(&ccb); } return pixd; } /*! * \brief ccbaDisplayImage2() * * \param[in] ccba * \return pix of image, or NULL on error * *
 * Notes:
 *      (1) Uses local chain ptaa, which gives each border pixel in
 *          local coordinates, so the actual pixel positions must
 *          be computed using all offsets.
 *      (2) Treats exterior and hole borders on equivalent
 *          footing, and does all calculations on a pix
 *          that spans the c.c. with a 1 pixel added boundary.
 *      (3) This uses topological properties (Method 2) to do scan
 *          conversion to raster
 *      (4) The algorithm is described at the top of this file (Method 2).
 *          It is preferred to Method 1 because it is between 1.2x and 2x
 *          faster than Method 1.
 * 
*/ PIX * ccbaDisplayImage2(CCBORDA *ccba) { l_int32 ncc, nb, n, i, j, k, x, y, xul, yul, w, h; l_int32 fpx, fpy, spx, spy, xs, ys; BOXA *boxa; CCBORD *ccb; PIX *pixd, *pixc, *pixs; PTAA *ptaa; PTA *pta; PROCNAME("ccbaDisplayImage2"); if (!ccba) return (PIX *)ERROR_PTR("ccba not defined", procName, NULL); if ((pixd = pixCreate(ccba->w, ccba->h, 1)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); ncc = ccbaGetCount(ccba); for (i = 0; i < ncc; i++) { /* Generate clipping mask from border pixels and seed image * from one seed for each closed border. */ ccb = ccbaGetCcb(ccba, i); if ((boxa = ccb->boxa) == NULL) { pixDestroy(&pixd); ccbDestroy(&ccb); return (PIX *)ERROR_PTR("boxa not found", procName, NULL); } if (boxaGetBoxGeometry(boxa, 0, &xul, &yul, &w, &h)) { pixDestroy(&pixd); ccbDestroy(&ccb); return (PIX *)ERROR_PTR("b. box not found", procName, NULL); } pixc = pixCreate(w + 2, h + 2, 1); pixs = pixCreateTemplate(pixc); if ((ptaa = ccb->local) == NULL) { pixDestroy(&pixc); pixDestroy(&pixs); ccbDestroy(&ccb); L_WARNING("local chain array not found\n", procName); continue; } nb = ptaaGetCount(ptaa); /* number of borders in the c.c. */ for (j = 0; j < nb; j++) { pta = ptaaGetPta(ptaa, j, L_CLONE); n = ptaGetCount(pta); /* number of pixels in the border */ /* Render border pixels in pixc */ for (k = 0; k < n; k++) { ptaGetIPt(pta, k, &x, &y); pixSetPixel(pixc, x + 1, y + 1, 1); if (k == 0) { fpx = x + 1; fpy = y + 1; } else if (k == 1) { spx = x + 1; spy = y + 1; } } /* Get and set seed pixel for this border in pixs */ if (n > 1) locateOutsideSeedPixel(fpx, fpy, spx, spy, &xs, &ys); else /* isolated c.c. */ xs = ys = 0; pixSetPixel(pixs, xs, ys, 1); ptaDestroy(&pta); } /* Fill from seeds in pixs, using pixc as the clipping mask, * to reconstruct the c.c. */ pixInvert(pixc, pixc); /* to convert clipping -> filling mask */ pixSeedfillBinary(pixs, pixs, pixc, 4); /* 4-fill */ pixInvert(pixs, pixs); /* to make the c.c. */ /* XOR into the dest */ pixRasterop(pixd, xul, yul, w, h, PIX_XOR, pixs, 1, 1); pixDestroy(&pixc); pixDestroy(&pixs); ccbDestroy(&ccb); /* ref-counted */ } return pixd; } /*---------------------------------------------------------------------* * Serialize for I/O * *---------------------------------------------------------------------*/ /*! * \brief ccbaWrite() * * \param[in] filename * \param[in] ccba * \return 0 if OK, 1 on error */ l_ok ccbaWrite(const char *filename, CCBORDA *ccba) { FILE *fp; PROCNAME("ccbaWrite"); if (!filename) return ERROR_INT("filename not defined", procName, 1); if (!ccba) return ERROR_INT("ccba not defined", procName, 1); if ((fp = fopenWriteStream(filename, "wb+")) == NULL) return ERROR_INT("stream not opened", procName, 1); if (ccbaWriteStream(fp, ccba)) { fclose(fp); return ERROR_INT("ccba not written to stream", procName, 1); } fclose(fp); return 0; } /*! * \brief ccbaWriteStream() * * \param[in] fp file stream * \param[in] ccba * \return 0 if OK; 1 on error * * Format: * \code * ccba: %7d cc\n num. c.c.) (ascii) (18B * pix width 4B * pix height 4B * [for i = 1, ncc] * ulx 4B * uly 4B * w 4B -- not req'd for reconstruction * h 4B -- not req'd for reconstruction * number of borders 4B * [for j = 1, nb] * startx 4B * starty 4B * [for k = 1, nb] * 2 steps 1B * end in z8 or 88 1B * \endcode */ l_ok ccbaWriteStream(FILE *fp, CCBORDA *ccba) { #if HAVE_LIBZ /* defined in environ.h */ char strbuf[256]; l_uint8 bval; l_uint8 *datain, *dataout; l_int32 i, j, k, bx, by, bw, bh, val, startx, starty; l_int32 ncc, nb, n; l_uint32 w, h; size_t inbytes, outbytes; L_BBUFFER *bbuf; CCBORD *ccb; NUMA *na; NUMAA *naa; PTA *pta; #endif PROCNAME("ccbaWriteStream"); #if !HAVE_LIBZ /* defined in environ.h */ return ERROR_INT("no libz: can't write data", procName, 1); #else if (!fp) return ERROR_INT("stream not open", procName, 1); if (!ccba) return ERROR_INT("ccba not defined", procName, 1); if ((bbuf = bbufferCreate(NULL, 1000)) == NULL) return ERROR_INT("bbuf not made", procName, 1); ncc = ccbaGetCount(ccba); snprintf(strbuf, sizeof(strbuf), "ccba: %7d cc\n", ncc); bbufferRead(bbuf, (l_uint8 *)strbuf, 18); w = pixGetWidth(ccba->pix); h = pixGetHeight(ccba->pix); bbufferRead(bbuf, (l_uint8 *)&w, 4); /* width */ bbufferRead(bbuf, (l_uint8 *)&h, 4); /* height */ for (i = 0; i < ncc; i++) { ccb = ccbaGetCcb(ccba, i); if (boxaGetBoxGeometry(ccb->boxa, 0, &bx, &by, &bw, &bh)) { bbufferDestroy(&bbuf); ccbDestroy(&ccb); return ERROR_INT("bounding box not found", procName, 1); } bbufferRead(bbuf, (l_uint8 *)&bx, 4); /* ulx of c.c. */ bbufferRead(bbuf, (l_uint8 *)&by, 4); /* uly of c.c. */ bbufferRead(bbuf, (l_uint8 *)&bw, 4); /* w of c.c. */ bbufferRead(bbuf, (l_uint8 *)&bh, 4); /* h of c.c. */ if ((naa = ccb->step) == NULL) { ccbaGenerateStepChains(ccba); naa = ccb->step; } nb = numaaGetCount(naa); bbufferRead(bbuf, (l_uint8 *)&nb, 4); /* number of borders in c.c. */ pta = ccb->start; for (j = 0; j < nb; j++) { ptaGetIPt(pta, j, &startx, &starty); bbufferRead(bbuf, (l_uint8 *)&startx, 4); /* starting x in border */ bbufferRead(bbuf, (l_uint8 *)&starty, 4); /* starting y in border */ na = numaaGetNuma(naa, j, L_CLONE); n = numaGetCount(na); for (k = 0; k < n; k++) { numaGetIValue(na, k, &val); if (k % 2 == 0) bval = (l_uint8)val << 4; else bval |= (l_uint8)val; if (k % 2 == 1) bbufferRead(bbuf, (l_uint8 *)&bval, 1); /* 2 border steps */ } if (n % 2 == 1) { bval |= 0x8; bbufferRead(bbuf, (l_uint8 *)&bval, 1); /* end with 0xz8, */ /* where z = {0..7} */ } else { /* n % 2 == 0 */ bval = 0x88; bbufferRead(bbuf, (l_uint8 *)&bval, 1); /* end with 0x88 */ } numaDestroy(&na); } ccbDestroy(&ccb); } datain = bbufferDestroyAndSaveData(&bbuf, &inbytes); dataout = zlibCompress(datain, inbytes, &outbytes); fwrite(dataout, 1, outbytes, fp); LEPT_FREE(datain); LEPT_FREE(dataout); return 0; #endif /* !HAVE_LIBZ */ } /*! * \brief ccbaRead() * * \param[in] filename * \return ccba, or NULL on error */ CCBORDA * ccbaRead(const char *filename) { FILE *fp; CCBORDA *ccba; PROCNAME("ccbaRead"); if (!filename) return (CCBORDA *)ERROR_PTR("filename not defined", procName, NULL); if ((fp = fopenReadStream(filename)) == NULL) return (CCBORDA *)ERROR_PTR("stream not opened", procName, NULL); ccba = ccbaReadStream(fp); fclose(fp); if (!ccba) return (CCBORDA *)ERROR_PTR("ccba not returned", procName, NULL); return ccba; } /*! * \brief ccbaReadStream() * * \param[in] fp file stream * \return ccba, or NULL on error * * \code * Format: ccba: %7d cc\n num. c.c.) (ascii) (17B * pix width 4B * pix height 4B * [for i = 1, ncc] * ulx 4B * uly 4B * w 4B -- not req'd for reconstruction * h 4B -- not req'd for reconstruction * number of borders 4B * [for j = 1, nb] * startx 4B * starty 4B * [for k = 1, nb] * 2 steps 1B * end in z8 or 88 1B * \endcode */ CCBORDA * ccbaReadStream(FILE *fp) { #if HAVE_LIBZ /* defined in environ.h */ char strbuf[256]; l_uint8 bval; l_uint8 *datain, *dataout; l_int32 i, j, startx, starty; l_int32 offset, nib1, nib2; l_int32 ncc, nb; l_uint32 width, height, w, h, xoff, yoff; size_t inbytes, outbytes; BOX *box; CCBORD *ccb; CCBORDA *ccba; NUMA *na; NUMAA *step; #endif PROCNAME("ccbaReadStream"); #if !HAVE_LIBZ /* defined in environ.h */ return (CCBORDA *)ERROR_PTR("no libz: can't read data", procName, NULL); #else if (!fp) return (CCBORDA *)ERROR_PTR("stream not open", procName, NULL); if ((datain = l_binaryReadStream(fp, &inbytes)) == NULL) return (CCBORDA *)ERROR_PTR("data not read from file", procName, NULL); dataout = zlibUncompress(datain, inbytes, &outbytes); LEPT_FREE(datain); if (!dataout) return (CCBORDA *)ERROR_PTR("dataout not made", procName, NULL); offset = 18; memcpy(strbuf, dataout, offset); strbuf[17] = '\0'; if (memcmp(strbuf, "ccba:", 5) != 0) { LEPT_FREE(dataout); return (CCBORDA *)ERROR_PTR("file not type ccba", procName, NULL); } sscanf(strbuf, "ccba: %7d cc\n", &ncc); /* lept_stderr("ncc = %d\n", ncc); */ if ((ccba = ccbaCreate(NULL, ncc)) == NULL) { LEPT_FREE(dataout); return (CCBORDA *)ERROR_PTR("ccba not made", procName, NULL); } memcpy(&width, dataout + offset, 4); offset += 4; memcpy(&height, dataout + offset, 4); offset += 4; ccba->w = width; ccba->h = height; /* lept_stderr("width = %d, height = %d\n", width, height); */ for (i = 0; i < ncc; i++) { /* should be ncc */ ccb = ccbCreate(NULL); ccbaAddCcb(ccba, ccb); memcpy(&xoff, dataout + offset, 4); offset += 4; memcpy(&yoff, dataout + offset, 4); offset += 4; memcpy(&w, dataout + offset, 4); offset += 4; memcpy(&h, dataout + offset, 4); offset += 4; box = boxCreate(xoff, yoff, w, h); boxaAddBox(ccb->boxa, box, L_INSERT); /* lept_stderr("xoff = %d, yoff = %d, w = %d, h = %d\n", xoff, yoff, w, h); */ memcpy(&nb, dataout + offset, 4); offset += 4; /* lept_stderr("num borders = %d\n", nb); */ step = numaaCreate(nb); ccb->step = step; for (j = 0; j < nb; j++) { /* should be nb */ memcpy(&startx, dataout + offset, 4); offset += 4; memcpy(&starty, dataout + offset, 4); offset += 4; ptaAddPt(ccb->start, startx, starty); /* lept_stderr("startx = %d, starty = %d\n", startx, starty); */ na = numaCreate(0); numaaAddNuma(step, na, L_INSERT); while(1) { bval = *(dataout + offset); offset++; nib1 = (bval >> 4); nib2 = bval & 0xf; if (nib1 != 8) numaAddNumber(na, nib1); else break; if (nib2 != 8) numaAddNumber(na, nib2); else break; } } } LEPT_FREE(dataout); return ccba; #endif /* !HAVE_LIBZ */ } /*---------------------------------------------------------------------* * SVG Output * *---------------------------------------------------------------------*/ /*! * \brief ccbaWriteSVG() * * \param[in] filename * \param[in] ccba * \return 0 if OK, 1 on error */ l_ok ccbaWriteSVG(const char *filename, CCBORDA *ccba) { char *svgstr; PROCNAME("ccbaWriteSVG"); if (!filename) return ERROR_INT("filename not defined", procName, 1); if (!ccba) return ERROR_INT("ccba not defined", procName, 1); if ((svgstr = ccbaWriteSVGString(ccba)) == NULL) return ERROR_INT("svgstr not made", procName, 1); l_binaryWrite(filename, "w", svgstr, strlen(svgstr)); LEPT_FREE(svgstr); return 0; } /*! * \brief ccbaWriteSVGString() * * \param[in] ccba * \return string in svg-formatted, that can be written to file, * or NULL on error. */ char * ccbaWriteSVGString(CCBORDA *ccba) { char *svgstr; char smallbuf[256]; char line0[] = ""; char line1[] = ""; char line2[] = ""; char line3[] = ""; char line5[] = ""; char space[] = " "; l_int32 i, j, ncc, npt, x, y; CCBORD *ccb; PTA *pta; SARRAY *sa; PROCNAME("ccbaWriteSVGString"); if (!ccba) return (char *)ERROR_PTR("ccba not defined", procName, NULL); sa = sarrayCreate(0); sarrayAddString(sa, line0, L_COPY); sarrayAddString(sa, line1, L_COPY); sarrayAddString(sa, line2, L_COPY); ncc = ccbaGetCount(ccba); for (i = 0; i < ncc; i++) { if ((ccb = ccbaGetCcb(ccba, i)) == NULL) { sarrayDestroy(&sa); return (char *)ERROR_PTR("ccb not found", procName, NULL); } if ((pta = ccb->spglobal) == NULL) { sarrayDestroy(&sa); ccbDestroy(&ccb); return (char *)ERROR_PTR("spglobal not made", procName, NULL); } sarrayAddString(sa, line3, L_COPY); npt = ptaGetCount(pta); for (j = 0; j < npt; j++) { ptaGetIPt(pta, j, &x, &y); snprintf(smallbuf, sizeof(smallbuf), "%0d,%0d", x, y); sarrayAddString(sa, smallbuf, L_COPY); } sarrayAddString(sa, line4, L_COPY); ccbDestroy(&ccb); } sarrayAddString(sa, line5, L_COPY); sarrayAddString(sa, space, L_COPY); svgstr = sarrayToString(sa, 1); /* lept_stderr("%s", svgstr); */ sarrayDestroy(&sa); return svgstr; }