Leptonica 1.68
C Image Processing Library

ccbord.c

Go to the documentation of this file.
00001 /*====================================================================*
00002  -  Copyright (C) 2001 Leptonica.  All rights reserved.
00003  -  This software is distributed in the hope that it will be
00004  -  useful, but with NO WARRANTY OF ANY KIND.
00005  -  No author or distributor accepts responsibility to anyone for the
00006  -  consequences of using this software, or for whether it serves any
00007  -  particular purpose or works at all, unless he or she says so in
00008  -  writing.  Everyone is granted permission to copy, modify and
00009  -  redistribute this source code, for commercial or non-commercial
00010  -  purposes, with the following restrictions: (1) the origin of this
00011  -  source code must not be misrepresented; (2) modified versions must
00012  -  be plainly marked as such; and (3) this notice may not be removed
00013  -  or altered from any source or modified source distribution.
00014  *====================================================================*/
00015 
00016 
00017 /*
00018  *  ccbord.c
00019  *
00020  *     CCBORDA and CCBORD creation and destruction
00021  *         CCBORDA     *ccbaCreate()
00022  *         void        *ccbaDestroy()
00023  *         CCBORD      *ccbCreate()
00024  *         void        *ccbDestroy()
00025  *
00026  *     CCBORDA addition
00027  *         l_int32      ccbaAddCcb()
00028  *         l_int32      ccbaExtendArray()
00029  *
00030  *     CCBORDA accessors
00031  *         l_int32      ccbaGetCount()
00032  *         l_int32      ccbaGetCcb()
00033  *
00034  *     Top-level border-finding routines
00035  *         CCBORDA     *pixGetAllCCBorders()
00036  *         CCBORD      *pixGetCCBorders()
00037  *         PTAA        *pixGetOuterBordersPtaa()
00038  *         PTA         *pixGetOuterBorderPta()
00039  *
00040  *     Lower-level border location routines
00041  *         l_int32      pixGetOuterBorder()
00042  *         l_int32      pixGetHoleBorder()
00043  *         l_int32      findNextBorderPixel()
00044  *         void         locateOutsideSeedPixel()
00045  *
00046  *     Border conversions
00047  *         l_int32      ccbaGenerateGlobalLocs()
00048  *         l_int32      ccbaGenerateStepChains()
00049  *         l_int32      ccbaStepChainsToPixCoords()
00050  *         l_int32      ccbaGenerateSPGlobalLocs()
00051  *
00052  *     Conversion to single path
00053  *         l_int32      ccbaGenerateSinglePath()
00054  *         PTA         *getCutPathForHole()
00055  *
00056  *     Border and full image rendering
00057  *         PIX         *ccbaDisplayBorder()
00058  *         PIX         *ccbaDisplaySPBorder()
00059  *         PIX         *ccbaDisplayImage1()
00060  *         PIX         *ccbaDisplayImage2()
00061  *
00062  *     Serialize for I/O
00063  *         l_int32      ccbaWrite()
00064  *         l_int32      ccbaWriteStream()
00065  *         l_int32      ccbaRead()
00066  *         l_int32      ccbaReadStream()
00067  *
00068  *     SVG output
00069  *         l_int32      ccbaWriteSVG()
00070  *         char        *ccbaWriteSVGString()
00071  *
00072  *
00073  *     Border finding is tricky because components can have
00074  *     holes, which also need to be traced out.  The outer
00075  *     border can be connected with all the hole borders,
00076  *     so that there is a single border for each component.
00077  *     [Alternatively, the connecting paths can be eliminated if
00078  *     you're willing to have a set of borders for each
00079  *     component (an exterior border and some number of
00080  *     interior ones), with "line to" operations tracing
00081  *     out each border and "move to" operations going from
00082  *     one border to the next.]
00083  *
00084  *     Here's the plan.  We get the pix for each connected
00085  *     component, and trace its exterior border.  We then
00086  *     find the holes (if any) in the pix, and separately
00087  *     trace out their borders, all using the same
00088  *     border-following rule that has ON pixels on the right
00089  *     side of the path.  
00090  *
00091  *     [For svg, we may want to turn each set of borders for a c.c.
00092  *     into a closed path.  This can be done by tunnelling
00093  *     through the component from the outer border to each of the 
00094  *     holes, going in and coming out along the same path so
00095  *     the connection will be invisible in any rendering 
00096  *     (display or print) from the outline.  The result is a 
00097  *     closed path, where the outside border is traversed
00098  *     cw and each hole is traversed ccw.  The svg renderer
00099  *     is assumed to handle these closed borders properly.]
00100  *
00101  *     Each border is a closed path that is traversed in such
00102  *     a way that the stuff inside the c.c. is on the right
00103  *     side of the traveller.  The border of a singly-connected
00104  *     component is thus traversed cw, and the border of the
00105  *     holes inside a c.c. are traversed ccw.  Suppose we have
00106  *     a list of all the borders of each c.c., both the cw and ccw
00107  *     traversals.  How do we reconstruct the image?
00108  *
00109  *   Reconstruction:
00110  *
00111  *     Method 1.  Topological method using connected components.
00112  *     We have closed borders composed of cw border pixels for the
00113  *     exterior of c.c. and ccw border pixels for the interior (holes)
00114  *     in the c.c. 
00115  *         (a) Initialize the destination to be OFF.  Then,
00116  *             in any order:
00117  *         (b) Fill the components within and including the cw borders,
00118  *             and sequentially XOR them onto the destination.
00119  *         (c) Fill the components within but not including the ccw
00120  *             borders and sequentially XOR them onto the destination.
00121  *     The components that are XOR'd together can be generated as follows:
00122  *         (a) For each closed cw path, use pixFillClosedBorders():
00123  *               (1) Turn on the path pixels in a subimage that
00124  *                   minimally supports the border.
00125  *               (2) Do a 4-connected fill from a seed of 1 pixel width
00126  *                   on the border, using the inverted image in (1) as
00127  *                   a filling mask.
00128  *               (3) Invert the fill result: this gives the component
00129  *                   including the exterior cw path, with all holes
00130  *                   filled.
00131  *         (b) For each closed ccw path (hole):
00132  *               (1) Turn on the path pixels in a subimage that minimally
00133  *                   supports the path.
00134  *               (2) Find a seed pixel on the inside of this path.
00135  *               (3) Do a 4-connected fill from this seed pixel, using
00136  *                   the inverted image of the path in (1) as a filling
00137  *                   mask.
00138  *             
00139  *     ------------------------------------------------------
00140  *
00141  *     Method 2.  A variant of Method 1.  Topological.
00142  *     In Method 1, we treat the exterior border differently from
00143  *     the interior (hole) borders.  Here, all borders in a c.c.
00144  *     are treated equally:
00145  *         (1) Start with a pix with a 1 pixel OFF boundary
00146  *             enclosing all the border pixels of the c.c. 
00147  *             This is the filling mask.
00148  *         (2) Make a seed image of the same size as follows:  for
00149  *             each border, put one seed pixel OUTSIDE the border
00150  *             (where OUTSIDE is determined by the inside/outside
00151  *             convention for borders).
00152  *         (3) Seedfill into the seed image, filling in the regions
00153  *             determined by the filling mask.  The fills are clipped
00154  *             by the border pixels.
00155  *         (4) Inverting this, we get the c.c. properly filled,
00156  *             with the holes empty!
00157  *         (5) Rasterop using XOR the filled c.c. (but not the 1
00158  *             pixel boundary) into the full dest image.
00159  *
00160  *     Method 2 is about 1.2x faster than Method 1 on text images,
00161  *     and about 2x faster on complex images (e.g., with halftones).
00162  *
00163  *     ------------------------------------------------------
00164  *
00165  *     Method 3.  The traditional way to fill components delineated
00166  *     by boundaries is through scan line conversion.  It's a bit
00167  *     tricky, and I have not yet tried to implement it.
00168  *
00169  *     ------------------------------------------------------
00170  *
00171  *     Method 4.  [Nota Bene: this method probably doesn't work, and
00172  *     won't be implemented.  If I get a more traditional scan line
00173  *     conversion algorithm working, I'll erase these notes.]
00174  *     Render all border pixels on a destination image,
00175  *     which will be the final result after scan conversion.  Assign
00176  *     a value 1 to pixels on cw paths, 2 to pixels on ccw paths,
00177  *     and 3 to pixels that are on both paths.  Each of the paths
00178  *     is an 8-connected component.  Now scan across each raster
00179  *     line.  The attempt is to make rules for each scan line
00180  *     that are independent of neighboring scanlines.  Here are
00181  *     a set of rules for writing ON pixels on a destination raster image:
00182  *
00183  *         (a) The rasterizer will be in one of two states: ON and OFF.
00184  *         (b) Start each line in the OFF state.  In the OFF state,
00185  *             skip pixels until you hit a path of any type.  Turn
00186  *             the path pixel ON. 
00187  *         (c) If the state is ON, each pixel you encounter will
00188  *             be turned on, until and including hitting a path pixel.
00189  *         (d) When you hit a path pixel, if the path does NOT cut
00190  *             through the line, so that there is not an 8-cc path
00191  *             pixel (of any type) both above and below, the state
00192  *             is unchanged (it stays either ON or OFF).
00193  *         (e) If the path does cut through, but with a possible change
00194  *             of pixel type, then we decide whether or
00195  *             not to toggle the state based on the values of the
00196  *             path pixel and the path pixels above and below:
00197  *               (1) if a 1 path cuts through, toggle;
00198  *               (1) if a 2 path cuts through, toggle;
00199  *               (3) if a 3 path cuts through, do not toggle;
00200  *               (4) if on one side a 3 touches both a 1 and a 2, use the 2
00201  *               (5) if a 3 has any 1 neighbors, toggle; else if it has
00202  *                   no 1 neighbors, do not toggle;
00203  *               (6) if a 2 has any neighbors that are 1 or 3,
00204  *                   do not toggle
00205  *               (7) if a 1 has neighbors 1 and x (x = 2 or 3),
00206  *                   toggle
00207  *
00208  *
00209  *     To visualize how these rules work, consider the following
00210  *     component with border pixels labeled according to the scheme
00211  *     above.  We also show the values of the interior pixels
00212  *     (w=OFF, b=ON), but these of course must be inferred properly
00213  *     from the rules above:
00214  *
00215  *                     3   
00216  *                  3  w  3             1  1  1
00217  *                  1  2  1          1  b  2  b  1 
00218  *                  1  b  1             3  w  2  1
00219  *                  3  b  1          1  b  2  b  1
00220  *               3  w  3                1  1  1
00221  *               3  w  3                     
00222  *            1  b  2  b  1
00223  *            1  2  w  2  1
00224  *         1  b  2  w  2  b  1
00225  *            1  2  w  2  1
00226  *               1  2  b  1
00227  *               1  b  1
00228  *                  1
00229  *
00230  *
00231  *     Even if this works, which is unlikely, it will certainly be
00232  *     slow because decisions have to be made on a pixel-by-pixel
00233  *     basis when encountering borders.
00234  *
00235  */
00236 
00237 #include <string.h>
00238 #include "allheaders.h"
00239 
00240 #ifdef HAVE_CONFIG_H
00241 #include "config_auto.h"
00242 #endif  /* HAVE_CONFIG_H */
00243 
00244 static const l_int32  INITIAL_PTR_ARRAYSIZE = 20;    /* n'import quoi */
00245 
00246     /* In ccbaGenerateSinglePath(): don't save holes
00247      * in c.c. with ridiculously many small holes   */
00248 static const l_int32  NMAX_HOLES = 150;
00249 
00250     /*  Tables used to trace the border.
00251      *   - The 8 pixel positions of neighbors Q are labelled:
00252      *                  1   2   3
00253      *                  0   P   4
00254      *                  7   6   5
00255      *     where the labels are the index offset [0, ... 7] of Q relative to P.
00256      *   - xpostab[] and ypostab[] give the actual x and y pixel offsets
00257      *     of Q relative to P, indexed by the index offset.
00258      *   - qpostab[pos] gives the new index offset of Q relative to P, at
00259      *     the time that a new P has been chosen to be in index offset
00260      *     position 'pos' relative to the previous P.   The relation
00261      *     between P and Q is always 4-connected.  */
00262 static const l_int32   xpostab[] = {-1, -1, 0, 1, 1, 1, 0, -1};
00263 static const l_int32   ypostab[] = {0, -1, -1, -1, 0, 1, 1, 1};
00264 static const l_int32   qpostab[] = {6, 6, 0, 0, 2, 2, 4, 4};
00265 
00266 
00267 #ifndef  NO_CONSOLE_IO
00268 #define  DEBUG_PRINT   0
00269 #endif   /* NO CONSOLE_IO */
00270 
00271 
00272 
00273 /*---------------------------------------------------------------------*
00274  *                   ccba and ccb creation and destruction             *
00275  *---------------------------------------------------------------------*/
00276 /*!
00277  *   ccbaCreate()
00278  *
00279  *       Input:  pixs  (binary image; can be null)
00280  *               n  (initial number of ptrs)
00281  *       Return: ccba, or null on error
00282  */
00283 CCBORDA *
00284 ccbaCreate(PIX     *pixs,
00285            l_int32  n)
00286 {
00287 CCBORDA  *ccba;
00288 
00289     PROCNAME("ccbaCreate");
00290 
00291     if (n <= 0)
00292         n = INITIAL_PTR_ARRAYSIZE;
00293 
00294     if ((ccba = (CCBORDA *)CALLOC(1, sizeof(CCBORDA))) == NULL)
00295         return (CCBORDA *)ERROR_PTR("ccba not made", procName, NULL);
00296     if (pixs) {
00297         ccba->pix = pixClone(pixs);
00298         ccba->w = pixGetWidth(pixs);
00299         ccba->h = pixGetHeight(pixs);
00300     }
00301     ccba->n = 0;
00302     ccba->nalloc = n;
00303 
00304     if ((ccba->ccb = (CCBORD **)CALLOC(n, sizeof(CCBORD *))) == NULL)
00305         return (CCBORDA *)ERROR_PTR("ccba ptrs not made", procName, NULL);
00306 
00307     return ccba;
00308 }
00309 
00310 
00311 /*!
00312  *  ccbaDestroy()
00313  *
00314  *     Input:  &ccba  (<to be nulled>)
00315  *     Return: void
00316  */
00317 void
00318 ccbaDestroy(CCBORDA  **pccba)
00319 {
00320 l_int32   i;
00321 CCBORDA  *ccba;
00322 
00323     PROCNAME("ccbaDestroy");
00324 
00325     if (pccba == NULL) {
00326         L_WARNING("ptr address is NULL!", procName);
00327         return;
00328     }
00329 
00330     if ((ccba = *pccba) == NULL)
00331         return;
00332 
00333     pixDestroy(&ccba->pix);
00334     for (i = 0; i < ccba->n; i++) 
00335         ccbDestroy(&ccba->ccb[i]);
00336     FREE(ccba->ccb);
00337     FREE(ccba);
00338     *pccba = NULL;
00339     return;
00340 }
00341 
00342 
00343 /*!
00344  *  ccbCreate()
00345  * 
00346  *     Input:  pixs  (<optional>)
00347  *     Return: ccb or null on error
00348  */
00349 CCBORD *
00350 ccbCreate(PIX  *pixs)
00351 {
00352 BOXA    *boxa;
00353 CCBORD  *ccb;
00354 PTA     *start;
00355 PTAA    *local;
00356 
00357     PROCNAME("ccbCreate");
00358 
00359     if (pixs) {
00360         if (pixGetDepth(pixs) != 1)
00361             return (CCBORD *)ERROR_PTR("pixs not binary", procName, NULL);
00362     }
00363 
00364     if ((ccb = (CCBORD *)CALLOC(1, sizeof(CCBORD))) == NULL)
00365         return (CCBORD *)ERROR_PTR("ccb not made", procName, NULL);
00366     ccb->refcount++;
00367     if (pixs)
00368         ccb->pix = pixClone(pixs);
00369     if ((boxa = boxaCreate(1)) == NULL)
00370         return (CCBORD *)ERROR_PTR("boxa not made", procName, NULL);
00371     ccb->boxa = boxa;
00372     if ((start = ptaCreate(1)) == NULL)
00373         return (CCBORD *)ERROR_PTR("start pta not made", procName, NULL);
00374     ccb->start = start;
00375     if ((local = ptaaCreate(1)) == NULL)
00376         return (CCBORD *)ERROR_PTR("local ptaa not made", procName, NULL);
00377     ccb->local = local;
00378 
00379     return ccb;
00380 }
00381 
00382 
00383 /*!
00384  *  ccbDestroy()
00385  *
00386  *     Input:  &ccb (<to be nulled>)
00387  *     Return: void
00388  */
00389 void
00390 ccbDestroy(CCBORD  **pccb)
00391 {
00392 CCBORD  *ccb;
00393 
00394     PROCNAME("ccbDestroy");
00395 
00396     if (pccb == NULL) {
00397         L_WARNING("ptr address is NULL!", procName);
00398         return;
00399     }
00400 
00401     if ((ccb = *pccb) == NULL)
00402         return;
00403 
00404     ccb->refcount--;
00405     if (ccb->refcount == 0) {
00406         if (ccb->pix)
00407             pixDestroy(&ccb->pix);
00408         if (ccb->boxa)
00409             boxaDestroy(&ccb->boxa);
00410         if (ccb->start)
00411             ptaDestroy(&ccb->start);
00412         if (ccb->local)
00413             ptaaDestroy(&ccb->local);
00414         if (ccb->global)
00415             ptaaDestroy(&ccb->global);
00416         if (ccb->step)
00417             numaaDestroy(&ccb->step);
00418         if (ccb->splocal)
00419             ptaDestroy(&ccb->splocal);
00420         if (ccb->spglobal)
00421             ptaDestroy(&ccb->spglobal);
00422         FREE(ccb);
00423         *pccb = NULL;
00424     }
00425     return;
00426 }
00427 
00428 
00429 /*---------------------------------------------------------------------*
00430  *                            ccba addition                            *
00431  *---------------------------------------------------------------------*/
00432 /*!
00433  *  ccbaAddCcb()
00434  *
00435  *      Input:  ccba
00436  *              ccb (to be added by insertion)
00437  *      Return: 0 if OK; 1 on error
00438  */
00439 l_int32
00440 ccbaAddCcb(CCBORDA  *ccba,
00441            CCBORD   *ccb)
00442 {
00443 l_int32  n;
00444 
00445     PROCNAME("ccbaAddCcb");
00446 
00447     if (!ccba)
00448         return ERROR_INT("ccba not defined", procName, 1);
00449     if (!ccb)
00450         return ERROR_INT("ccb not defined", procName, 1);
00451 
00452     n = ccbaGetCount(ccba);
00453     if (n >= ccba->nalloc)
00454         ccbaExtendArray(ccba);
00455     ccba->ccb[n] = ccb;
00456     ccba->n++;
00457     return 0;
00458 }
00459 
00460 
00461 /*!
00462  *  ccbaExtendArray()
00463  *
00464  *      Input:  ccba
00465  *      Return: 0 if OK; 1 on error
00466  */
00467 l_int32
00468 ccbaExtendArray(CCBORDA  *ccba)
00469 {
00470     PROCNAME("ccbaExtendArray");
00471 
00472     if (!ccba)
00473         return ERROR_INT("ccba not defined", procName, 1);
00474 
00475     if ((ccba->ccb = (CCBORD **)reallocNew((void **)&ccba->ccb,
00476                                 sizeof(CCBORD *) * ccba->nalloc,
00477                                 2 * sizeof(CCBORD *) * ccba->nalloc)) == NULL)
00478         return ERROR_INT("new ptr array not returned", procName, 1);
00479 
00480     ccba->nalloc = 2 * ccba->nalloc;
00481     return 0;
00482 }
00483 
00484 
00485 
00486 /*---------------------------------------------------------------------*
00487  *                            ccba accessors                           *
00488  *---------------------------------------------------------------------*/
00489 /*!
00490  *  ccbaGetCount()
00491  *
00492  *     Input:  ccba
00493  *     Return: count, with 0 on error
00494  */
00495 l_int32 
00496 ccbaGetCount(CCBORDA  *ccba)
00497 {
00498 
00499     PROCNAME("ccbaGetCount");
00500 
00501     if (!ccba)
00502         return ERROR_INT("ccba not defined", procName, 0);
00503 
00504     return ccba->n;
00505 }
00506 
00507 
00508 /*!
00509  *  ccbaGetCcb()
00510  *
00511  *     Input:  ccba
00512  *     Return: ccb, or null on error
00513  */
00514 CCBORD *
00515 ccbaGetCcb(CCBORDA  *ccba,
00516            l_int32   index)
00517 {
00518 CCBORD  *ccb;
00519 
00520     PROCNAME("ccbaGetCcb");
00521 
00522     if (!ccba)
00523         return (CCBORD *)ERROR_PTR("ccba not defined", procName, NULL);
00524     if (index < 0 || index >= ccba->n)
00525         return (CCBORD *)ERROR_PTR("index out of bounds", procName, NULL);
00526 
00527     ccb = ccba->ccb[index];
00528     ccb->refcount++;
00529     return ccb;
00530 }
00531 
00532 
00533 
00534 /*---------------------------------------------------------------------*
00535  *                   Top-level border-finding routines                 *
00536  *---------------------------------------------------------------------*/
00537 /*!
00538  *  pixGetAllCCBorders()
00539  *
00540  *      Input:  pixs (1 bpp)
00541  *      Return: ccborda, or null on error
00542  */
00543 CCBORDA *
00544 pixGetAllCCBorders(PIX  *pixs)
00545 {
00546 l_int32   n, i;
00547 BOX      *box;
00548 BOXA     *boxa;
00549 CCBORDA  *ccba;
00550 CCBORD   *ccb;
00551 PIX      *pix;
00552 PIXA     *pixa;
00553 
00554     PROCNAME("pixGetAllCCBorders");
00555 
00556     if (!pixs)
00557         return (CCBORDA *)ERROR_PTR("pixs not defined", procName, NULL);
00558     if (pixGetDepth(pixs) != 1)
00559         return (CCBORDA *)ERROR_PTR("pixs not binary", procName, NULL);
00560 
00561     if ((boxa = pixConnComp(pixs, &pixa, 8)) == NULL)
00562         return (CCBORDA *)ERROR_PTR("boxa not made", procName, NULL);
00563     n = boxaGetCount(boxa);
00564 
00565     if ((ccba = ccbaCreate(pixs, n)) == NULL)
00566         return (CCBORDA *)ERROR_PTR("ccba not made", procName, NULL);
00567 
00568     for (i = 0; i < n; i++) {
00569         if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL)
00570             return (CCBORDA *)ERROR_PTR("pix not found", procName, NULL);
00571         if ((box = pixaGetBox(pixa, i, L_CLONE)) == NULL)
00572             return (CCBORDA *)ERROR_PTR("box not found", procName, NULL);
00573         if ((ccb = pixGetCCBorders(pix, box)) == NULL)
00574             return (CCBORDA *)ERROR_PTR("ccb not made", procName, NULL);
00575 /*        ptaWriteStream(stderr, ccb->local, 1); */
00576         ccbaAddCcb(ccba, ccb);
00577         pixDestroy(&pix);
00578         boxDestroy(&box);
00579     }
00580 
00581     boxaDestroy(&boxa);
00582     pixaDestroy(&pixa);
00583     return ccba;
00584 }
00585 
00586 
00587 /*!
00588  *  pixGetCCBorders()
00589  *
00590  *      Input:  pixs (1 bpp, one 8-connected component)
00591  *              box  (xul, yul, width, height) in global coords
00592  *      Return: ccbord, or null on error
00593  *
00594  *  Notes:
00595  *      (1) We are finding the exterior and interior borders
00596  *          of an 8-connected component.   This should be used
00597  *          on a pix that has exactly one 8-connected component.
00598  *      (2) Typically, pixs is a c.c. in some larger pix.  The 
00599  *          input box gives its location in global coordinates.
00600  *          This box is saved, as well as the boxes for the
00601  *          borders of any holes within the c.c., but the latter
00602  *          are given in relative coords within the c.c.
00603  *      (3) The calculations for the exterior border are done
00604  *          on a pix with a 1-pixel
00605  *          added border, but the saved pixel coordinates
00606  *          are the correct (relative) ones for the input pix
00607  *          (without a 1-pixel border)
00608  *      (4) For the definition of the three tables -- xpostab[], ypostab[]
00609  *          and qpostab[] -- see above where they are defined.
00610  */
00611 CCBORD *
00612 pixGetCCBorders(PIX      *pixs,
00613                 BOX      *box)
00614 {
00615 l_int32   allzero, i, x, xh, w, nh;
00616 l_int32   xs, ys;   /* starting hole border pixel, relative in pixs */
00617 l_uint32  val;
00618 BOX      *boxt, *boxe;
00619 BOXA     *boxa;
00620 CCBORD   *ccb;
00621 PIX      *pixh;  /* for hole components */
00622 PIX      *pixt;
00623 PIXA     *pixa;
00624 
00625     PROCNAME("pixGetCCBorders");
00626 
00627     if (!pixs)
00628         return (CCBORD *)ERROR_PTR("pixs not defined", procName, NULL);
00629     if (!box)
00630         return (CCBORD *)ERROR_PTR("box not defined", procName, NULL);
00631     if (pixGetDepth(pixs) != 1)
00632         return (CCBORD *)ERROR_PTR("pixs not binary", procName, NULL);
00633 
00634     pixZero(pixs, &allzero);
00635     if (allzero)
00636         return (CCBORD *)ERROR_PTR("pixs all 0", procName, NULL);
00637 
00638     if ((ccb = ccbCreate(pixs)) == NULL)
00639         return (CCBORD *)ERROR_PTR("ccb not made", procName, NULL);
00640 
00641         /* Get the exterior border */
00642     pixGetOuterBorder(ccb, pixs, box);
00643 
00644         /* Find the holes, if any */
00645     if ((pixh = pixHolesByFilling(pixs, 4)) == NULL)
00646         return (CCBORD *)ERROR_PTR("pixh not made", procName, NULL);
00647     pixZero(pixh, &allzero);
00648     if (allzero) {  /* no holes */
00649         pixDestroy(&pixh);
00650         return ccb;
00651     }
00652 
00653         /* Get c.c. and locations of the holes */
00654     if ((boxa = pixConnComp(pixh, &pixa, 4)) == NULL)
00655         return (CCBORD *)ERROR_PTR("boxa not made", procName, NULL);
00656     nh = boxaGetCount(boxa);
00657 /*    fprintf(stderr, "%d holes\n", nh); */
00658 
00659         /* For each hole, find an interior pixel within the hole,
00660          * then march to the right and stop at the first border
00661          * pixel.  Save the bounding box of the border, which
00662          * is 1 pixel bigger on each side than the bounding box
00663          * of the hole itself.  Note that we use a pix of the
00664          * c.c. of the hole itself to be sure that we start
00665          * with a pixel in the hole of the proper component.
00666          * If we did everything from the parent component, it is
00667          * possible to start in a different hole that is within
00668          * the b.b. of a larger hole.  */
00669     w = pixGetWidth(pixs);
00670     for (i = 0; i < nh; i++) {
00671         boxt = boxaGetBox(boxa, i, L_CLONE);
00672         pixt = pixaGetPix(pixa, i, L_CLONE);
00673         ys = boxt->y;   /* there must be a hole pixel on this raster line */
00674         for (x = 0; x < boxt->w; x++) {  /* look for (fg) hole pixel */
00675             pixGetPixel(pixt, x, 0, &val);
00676             if (val == 1) {
00677                 xh = x;
00678                 break;
00679             }
00680         }
00681         if (x == boxt->w) {
00682             L_WARNING("no hole pixel found!", procName);
00683             continue;
00684         }
00685         for (x = xh + boxt->x; x < w; x++) {  /* look for (fg) border pixel */
00686             pixGetPixel(pixs, x, ys, &val);
00687             if (val == 1) {
00688                 xs = x;
00689                 break;
00690             }
00691         }
00692         boxe = boxCreate(boxt->x - 1, boxt->y - 1, boxt->w + 2, boxt->h + 2);
00693 #if  DEBUG_PRINT
00694         boxPrintStreamInfo(stderr, box);
00695         boxPrintStreamInfo(stderr, boxe);
00696         fprintf(stderr, "xs = %d, ys = %d\n", xs, ys);
00697 #endif   /* DEBUG_PRINT */
00698         pixGetHoleBorder(ccb, pixs, boxe, xs, ys);
00699         boxDestroy(&boxt);
00700         boxDestroy(&boxe);
00701         pixDestroy(&pixt);
00702     }
00703 
00704     boxaDestroy(&boxa);
00705     pixaDestroy(&pixa);
00706     pixDestroy(&pixh);
00707         
00708     return ccb;
00709 }
00710 
00711 
00712 /*!
00713  *  pixGetOuterBordersPtaa()
00714  *
00715  *      Input:  pixs (1 bpp)
00716  *      Return: ptaa (of outer borders, in global coords), or null on error
00717  */
00718 PTAA *
00719 pixGetOuterBordersPtaa(PIX  *pixs)
00720 {
00721 l_int32  i, n;
00722 BOX     *box;
00723 BOXA    *boxa;
00724 PIX     *pix;
00725 PIXA    *pixa;
00726 PTA     *pta;
00727 PTAA    *ptaa;
00728 
00729     PROCNAME("pixGetOuterBordersPtaa");
00730 
00731     if (!pixs)
00732         return (PTAA *)ERROR_PTR("pixs not defined", procName, NULL);
00733     if (pixGetDepth(pixs) != 1)
00734         return (PTAA *)ERROR_PTR("pixs not binary", procName, NULL);
00735 
00736     boxa = pixConnComp(pixs, &pixa, 8);
00737     n = boxaGetCount(boxa);
00738     if (n == 0) {
00739         boxaDestroy(&boxa);
00740         pixaDestroy(&pixa);
00741         return (PTAA *)ERROR_PTR("pixs empty", procName, NULL);
00742     }
00743 
00744     ptaa = ptaaCreate(n);
00745     for (i = 0; i < n; i++) {
00746         box = boxaGetBox(boxa, i, L_CLONE);
00747         pix = pixaGetPix(pixa, i, L_CLONE);
00748         pta = pixGetOuterBorderPta(pix, box);
00749         if (pta)
00750             ptaaAddPta(ptaa, pta, L_INSERT);
00751         boxDestroy(&box);
00752         pixDestroy(&pix);
00753     }
00754 
00755     pixaDestroy(&pixa);
00756     boxaDestroy(&boxa);
00757     return ptaa;
00758 }
00759 
00760 
00761 /*!
00762  *  pixGetOuterBorderPta()
00763  *
00764  *      Input:  pixs (1 bpp, one 8-connected component)
00765  *              box  (<optional> of pixs, in global coordinates)
00766  *      Return: pta (of outer border, in global coords), or null on error
00767  *
00768  *  Notes:
00769  *      (1) We are finding the exterior border of a single 8-connected
00770  *          component.
00771  *      (2) If box is NULL, the outline returned is in the local coords
00772  *          of the input pix.  Otherwise, box is assumed to give the
00773  *          location of the pix in global coordinates, and the returned
00774  *          pta will be in those global coordinates.
00775  */
00776 PTA *
00777 pixGetOuterBorderPta(PIX  *pixs,
00778                      BOX  *box)
00779 {
00780 l_int32  allzero, x, y;
00781 BOX     *boxt;
00782 CCBORD  *ccb;
00783 PTA     *ptaloc, *ptad;
00784 
00785     PROCNAME("pixGetOuterBorderPta");
00786 
00787     if (!pixs)
00788         return (PTA *)ERROR_PTR("pixs not defined", procName, NULL);
00789     if (pixGetDepth(pixs) != 1)
00790         return (PTA *)ERROR_PTR("pixs not binary", procName, NULL);
00791 
00792     pixZero(pixs, &allzero);
00793     if (allzero)
00794         return (PTA *)ERROR_PTR("pixs all 0", procName, NULL);
00795 
00796     if ((ccb = ccbCreate(pixs)) == NULL)
00797         return (PTA *)ERROR_PTR("ccb not made", procName, NULL);
00798     if (!box)
00799         boxt = boxCreate(0, 0, pixGetWidth(pixs), pixGetHeight(pixs));
00800     else
00801         boxt = boxClone(box);
00802 
00803         /* Get the exterior border in local coords */
00804     pixGetOuterBorder(ccb, pixs, boxt);
00805     if ((ptaloc = ptaaGetPta(ccb->local, 0, L_CLONE)) == NULL) {
00806         ccbDestroy(&ccb);
00807         boxDestroy(&boxt);
00808         return (PTA *)ERROR_PTR("ptaloc not made", procName, NULL);
00809     }
00810 
00811         /* Transform to global coordinates, if they are given */
00812     if (box) {
00813         boxGetGeometry(box, &x, &y, NULL, NULL);
00814         ptad = ptaTransform(ptaloc, x, y, 1.0, 1.0);
00815     }
00816     else {
00817         ptad = ptaClone(ptaloc);
00818     }
00819 
00820     ptaDestroy(&ptaloc);
00821     boxDestroy(&boxt);
00822     ccbDestroy(&ccb);
00823     return ptad;
00824 }
00825 
00826 
00827 /*---------------------------------------------------------------------*
00828  *                   Lower-level border-finding routines               *
00829  *---------------------------------------------------------------------*/
00830 /*!
00831  *  pixGetOuterBorder()
00832  *
00833  *      Input:  ccb  (unfilled)
00834  *              pixs (for the component at hand)
00835  *              box  (for the component, in global coords)
00836  *      Return: 0 if OK, 1 on error
00837  *
00838  *  Notes:
00839  *      (1) the border is saved in relative coordinates within
00840  *          the c.c. (pixs).  Because the calculation is done
00841  *          in pixb with added 1 pixel border, we must subtract
00842  *          1 from each pixel value before storing it.
00843  *      (2) the stopping condition is that after the first pixel is
00844  *          returned to, the next pixel is the second pixel.  Having
00845  *          these 2 pixels recur in sequence proves the path is closed,
00846  *          and we do not store the second pixel again.
00847  */
00848 l_int32
00849 pixGetOuterBorder(CCBORD   *ccb,
00850                   PIX      *pixs,
00851                   BOX      *box)
00852 {
00853 l_int32    fpx, fpy, spx, spy, qpos;
00854 l_int32    px, py, npx, npy;
00855 l_int32    w, h, wpl;
00856 l_uint32  *data;
00857 PTA       *pta;
00858 PIX       *pixb;  /* with 1 pixel border */
00859 
00860     PROCNAME("pixGetOuterBorder");
00861 
00862     if (!ccb)
00863         return ERROR_INT("ccb not defined", procName, 1);
00864     if (!pixs)
00865         return ERROR_INT("pixs not defined", procName, 1);
00866     if (!box)
00867         return ERROR_INT("box not defined", procName, 1);
00868 
00869         /* Add 1-pixel border all around, and find start pixel */
00870     if ((pixb = pixAddBorder(pixs, 1, 0)) == NULL)
00871         return ERROR_INT("pixs not made", procName, 1);
00872     if (!nextOnPixelInRaster(pixb, 1, 1, &px, &py))
00873         return ERROR_INT("no start pixel found", procName, 1);
00874     qpos = 0;   /* relative to p */
00875     fpx = px;  /* save location of first pixel on border */
00876     fpy = py;
00877 
00878         /* Save box and start pixel in relative coords */
00879     boxaAddBox(ccb->boxa, box, L_COPY);
00880     ptaAddPt(ccb->start, px - 1, py - 1);
00881 
00882     if ((pta = ptaCreate(0)) == NULL)
00883         return ERROR_INT("pta not made", procName, 1);
00884     ptaaAddPta(ccb->local, pta, L_INSERT);
00885     ptaAddPt(pta, px - 1, py - 1);   /* initial point */
00886 
00887     w = pixGetWidth(pixb);
00888     h = pixGetHeight(pixb);
00889     data = pixGetData(pixb);
00890     wpl = pixGetWpl(pixb);
00891 
00892         /* Get the second point; if there is none, return */
00893     if (findNextBorderPixel(w, h, data, wpl, px, py, &qpos, &npx, &npy)) {
00894         pixDestroy(&pixb);
00895         return 0;
00896     }
00897 
00898     spx = npx;  /* save location of second pixel on border */
00899     spy = npy;
00900     ptaAddPt(pta, npx - 1, npy - 1);   /* second point */
00901     px = npx;
00902     py = npy;
00903 
00904     while (1) {
00905         findNextBorderPixel(w, h, data, wpl, px, py, &qpos, &npx, &npy);
00906         if (px == fpx && py == fpy && npx == spx && npy == spy)
00907             break;
00908         ptaAddPt(pta, npx - 1, npy - 1);
00909         px = npx;
00910         py = npy;
00911     }
00912     
00913     pixDestroy(&pixb);
00914     return 0;
00915 }
00916 
00917 
00918 /*!
00919  *  pixGetHoleBorder()
00920  *
00921  *      Input:  ccb  (the exterior border is already made)
00922  *              pixs (for the connected component at hand)
00923  *              box  (for the specific hole border, in relative
00924  *                    coordinates to the c.c.)
00925  *              xs, ys   (first pixel on hole border, relative to c.c.)
00926  *      Return: 0 if OK, 1 on error
00927  *
00928  *  Notes:
00929  *      (1) we trace out hole border on pixs without addition
00930  *          of single pixel added border to pixs
00931  *      (2) therefore all coordinates are relative within the c.c. (pixs)
00932  *      (3) same position tables and stopping condition as for
00933  *          exterior borders
00934  */
00935 l_int32
00936 pixGetHoleBorder(CCBORD   *ccb,
00937                  PIX      *pixs,
00938                  BOX      *box,
00939                  l_int32   xs,
00940                  l_int32   ys)
00941 {
00942 l_int32    fpx, fpy, spx, spy, qpos;
00943 l_int32    px, py, npx, npy;
00944 l_int32    w, h, wpl;
00945 l_uint32  *data;
00946 PTA       *pta;
00947 
00948     PROCNAME("pixGetHoleBorder");
00949 
00950     if (!ccb)
00951         return ERROR_INT("ccb not defined", procName, 1);
00952     if (!pixs)
00953         return ERROR_INT("pixs not defined", procName, 1);
00954     if (!box)
00955         return ERROR_INT("box not defined", procName, 1);
00956 
00957         /* Add border and find start pixel */
00958     qpos = 0;   /* orientation of Q relative to P */
00959     fpx = xs;  /* save location of first pixel on border */
00960     fpy = ys;
00961 
00962         /* Save box and start pixel */
00963     boxaAddBox(ccb->boxa, box, L_COPY);
00964     ptaAddPt(ccb->start, xs, ys);
00965 
00966     if ((pta = ptaCreate(0)) == NULL)
00967         return ERROR_INT("pta not made", procName, 1);
00968     ptaaAddPta(ccb->local, pta, L_INSERT);
00969     ptaAddPt(pta, xs, ys);   /* initial pixel */
00970 
00971     w = pixGetWidth(pixs);
00972     h = pixGetHeight(pixs);
00973     data = pixGetData(pixs);
00974     wpl = pixGetWpl(pixs);
00975 
00976         /* Get the second point; there should always be at least 4 pts
00977          * in a minimal hole border!  */
00978     if (findNextBorderPixel(w, h, data, wpl, xs, ys, &qpos, &npx, &npy))
00979         return ERROR_INT("isolated hole border point!", procName, 1);
00980 
00981     spx = npx;  /* save location of second pixel on border */
00982     spy = npy;
00983     ptaAddPt(pta, npx, npy);   /* second pixel */
00984     px = npx;
00985     py = npy;
00986 
00987     while (1) {
00988         findNextBorderPixel(w, h, data, wpl, px, py, &qpos, &npx, &npy);
00989         if (px == fpx && py == fpy && npx == spx && npy == spy)
00990             break;
00991         ptaAddPt(pta, npx, npy);
00992         px = npx;
00993         py = npy;
00994     }
00995     
00996     return 0;
00997 }
00998 
00999 
01000 /*!
01001  *  findNextBorderPixel()
01002  *
01003  *      Input:  w, h, data, wpl
01004  *              (px, py),     (current P)
01005  *              &qpos (input current Q; <return> new Q)
01006  *              (&npx, &npy)    (<return> new P)
01007  *      Return: 0 if next pixel found; 1 otherwise
01008  *
01009  *  Notes:
01010  *      (1) qpos increases clockwise from 0 to 7, with 0 at
01011  *          location with Q to left of P:   Q P
01012  *      (2) this is a low-level function that does not check input
01013  *          parameters.  All calling functions should check them.
01014  */
01015 l_int32
01016 findNextBorderPixel(l_int32    w,
01017                     l_int32    h,
01018                     l_uint32  *data,
01019                     l_int32    wpl,
01020                     l_int32    px,
01021                     l_int32    py,
01022                     l_int32   *pqpos,
01023                     l_int32   *pnpx,
01024                     l_int32   *pnpy)
01025 {
01026 l_int32    qpos, i, pos, npx, npy, val;
01027 l_uint32  *line;
01028 
01029     qpos = *pqpos;
01030     for (i = 1; i < 8; i++) {
01031         pos = (qpos + i) % 8;
01032         npx = px + xpostab[pos];
01033         npy = py + ypostab[pos];
01034         line = data + npy * wpl;
01035         val = GET_DATA_BIT(line, npx);
01036         if (val) {
01037             *pnpx = npx;
01038             *pnpy = npy;
01039             *pqpos = qpostab[pos];
01040             return 0;
01041         }
01042     }
01043 
01044     return 1;
01045 }
01046         
01047 
01048 /*!
01049  *  locateOutsideSeedPixel()
01050  *
01051  *      Input: fpx, fpy    (location of first pixel)
01052  *             spx, spy    (location of second pixel)
01053  *             &xs, &xy    (seed pixel to be returned)
01054  *
01055  *  Notes:
01056  *      (1) the first and second pixels must be 8-adjacent,
01057  *          so |dx| <= 1 and |dy| <= 1 and both dx and dy 
01058  *          cannot be 0.  There are 8 possible cases.
01059  *      (2) the seed pixel is OUTSIDE the foreground of the c.c.
01060  *      (3) these rules are for the situation where the INSIDE
01061  *          of the c.c. is on the right as you follow the border:
01062  *          cw for an exterior border and ccw for a hole border.
01063  */
01064 void
01065 locateOutsideSeedPixel(l_int32   fpx, 
01066                        l_int32   fpy,
01067                        l_int32   spx,
01068                        l_int32   spy,
01069                        l_int32  *pxs,
01070                        l_int32  *pys)
01071 {
01072 l_int32  dx, dy;
01073 
01074     dx = spx - fpx;
01075     dy = spy - fpy;
01076 
01077     if (dx * dy == 1) {
01078         *pxs = fpx + dx;
01079         *pys = fpy;
01080     }
01081     else if (dx * dy == -1) {
01082         *pxs = fpx;
01083         *pys = fpy + dy;
01084     }
01085     else if (dx == 0) {
01086         *pxs = fpx + dy;
01087         *pys = fpy + dy;
01088     }
01089     else  /* dy == 0 */ {
01090         *pxs = fpx + dx;
01091         *pys = fpy - dx;
01092     }
01093 
01094     return;
01095 }
01096 
01097 
01098 
01099 /*---------------------------------------------------------------------*
01100  *                            Border conversions                       *
01101  *---------------------------------------------------------------------*/
01102 /*!
01103  *  ccbaGenerateGlobalLocs()
01104  *
01105  *      Input:  ccba (with local chain ptaa of borders computed)
01106  *      Return: 0 if OK, 1 on error
01107  *
01108  *  Action: this uses the pixel locs in the local ptaa, which are all
01109  *          relative to each c.c., to find the global pixel locations,
01110  *          and stores them in the global ptaa.
01111  */
01112 l_int32
01113 ccbaGenerateGlobalLocs(CCBORDA  *ccba)
01114 {
01115 l_int32  ncc, nb, n, i, j, k, xul, yul, x, y;
01116 CCBORD  *ccb;
01117 PTAA    *ptaal, *ptaag;
01118 PTA     *ptal, *ptag;
01119 
01120     PROCNAME("ccbaGenerateGlobalLocs");
01121 
01122     if (!ccba)
01123         return ERROR_INT("ccba not defined", procName, 1);
01124 
01125     ncc = ccbaGetCount(ccba);  /* number of c.c. */
01126     for (i = 0; i < ncc; i++) {
01127         ccb = ccbaGetCcb(ccba, i);
01128 
01129             /* Get the UL corner in global coords, (xul, yul), of the c.c. */
01130         boxaGetBoxGeometry(ccb->boxa, 0, &xul, &yul, NULL, NULL);
01131 
01132             /* Make a new global ptaa, removing any old one */
01133         ptaal = ccb->local;
01134         nb = ptaaGetCount(ptaal);   /* number of borders */
01135         if (ccb->global)   /* remove old one */
01136             ptaaDestroy(&ccb->global);
01137         if ((ptaag = ptaaCreate(nb)) == NULL)
01138             return ERROR_INT("ptaag not made", procName, 1);
01139         ccb->global = ptaag;  /* save new one */
01140 
01141             /* Iterate through the borders for this c.c. */
01142         for (j = 0; j < nb; j++) {
01143             ptal = ptaaGetPta(ptaal, j, L_CLONE);
01144             n = ptaGetCount(ptal);   /* number of pixels in border */
01145             if ((ptag = ptaCreate(n)) == NULL)
01146                 return ERROR_INT("ptag not made", procName, 1);
01147             ptaaAddPta(ptaag, ptag, L_INSERT);
01148             for (k = 0; k < n; k++) {
01149                 ptaGetIPt(ptal, k, &x, &y);
01150                 ptaAddPt(ptag, x  + xul, y + yul);
01151             }
01152             ptaDestroy(&ptal);
01153         }
01154         ccbDestroy(&ccb);
01155     }
01156 
01157     return 0;
01158 }
01159 
01160 
01161 /*!
01162  *  ccbaGenerateStepChains()
01163  *
01164  *      Input:  ccba (with local chain ptaa of borders computed)
01165  *      Return: 0 if OK, 1 on error
01166  *
01167  *  Notes:
01168  *      (1) This uses the pixel locs in the local ptaa,
01169  *          which are all relative to each c.c., to find
01170  *          the step directions for successive pixels in
01171  *          the chain, and stores them in the step numaa.
01172  *      (2) To get the step direction, use
01173  *              1   2   3
01174  *              0   P   4
01175  *              7   6   5
01176  *          where P is the previous pixel at (px, py).  The step direction
01177  *          is the number (from 0 through 7) for each relative location
01178  *          of the current pixel at (cx, cy).  It is easily found by
01179  *          indexing into a 2-d 3x3 array (dirtab).
01180  */
01181 l_int32
01182 ccbaGenerateStepChains(CCBORDA  *ccba)
01183 {
01184 l_int32  ncc, nb, n, i, j, k;
01185 l_int32  px, py, cx, cy, stepdir;
01186 l_int32  dirtab[][3] = {{1, 2, 3}, {0, -1, 4}, {7, 6, 5}};
01187 CCBORD  *ccb;
01188 NUMA    *na;
01189 NUMAA   *naa;   /* step chain code; to be made */
01190 PTA     *ptal;
01191 PTAA    *ptaal;  /* local chain code */
01192 
01193     PROCNAME("ccbaGenerateStepChains");
01194 
01195     if (!ccba)
01196         return ERROR_INT("ccba not defined", procName, 1);
01197 
01198     ncc = ccbaGetCount(ccba);  /* number of c.c. */
01199     for (i = 0; i < ncc; i++) {
01200         ccb = ccbaGetCcb(ccba, i);
01201 
01202             /* Make a new step numaa, removing any old one */
01203         ptaal = ccb->local;
01204         nb = ptaaGetCount(ptaal);   /* number of borders */
01205         if (ccb->step)   /* remove old one */
01206             numaaDestroy(&ccb->step);
01207         if ((naa = numaaCreate(nb)) == NULL)
01208             return ERROR_INT("naa not made", procName, 1);
01209         ccb->step = naa;  /* save new one */
01210 
01211             /* Iterate through the borders for this c.c. */
01212         for (j = 0; j < nb; j++) {
01213             ptal = ptaaGetPta(ptaal, j, L_CLONE);
01214             n = ptaGetCount(ptal);   /* number of pixels in border */
01215             if (n == 1)  /* isolated pixel */
01216                 na = numaCreate(1);   /* but leave it empty */
01217             else {   /* trace out the boundary */
01218                 if ((na = numaCreate(n)) == NULL)
01219                     return ERROR_INT("na not made", procName, 1);
01220                 ptaGetIPt(ptal, 0, &px, &py);
01221                 for (k = 1; k < n; k++) {
01222                     ptaGetIPt(ptal, k, &cx, &cy);
01223                     stepdir = dirtab[1 + cy - py][1 + cx - px];
01224                     numaAddNumber(na, stepdir);
01225                     px = cx;
01226                     py = cy;
01227                 }
01228             }
01229             numaaAddNuma(naa, na, L_INSERT);
01230             ptaDestroy(&ptal);
01231         }
01232         ccbDestroy(&ccb);  /* just decrement refcount */
01233     }
01234 
01235     return 0;
01236 }
01237 
01238 
01239 /*!
01240  *  ccbaStepChainsToPixCoords()
01241  *
01242  *      Input:  ccba (with step chains numaa of borders)
01243  *              coordtype  (CCB_GLOBAL_COORDS or CCB_LOCAL_COORDS)
01244  *      Return: 0 if OK, 1 on error
01245  *
01246  *  Notes:
01247  *      (1) This uses the step chain data in each ccb to determine
01248  *          the pixel locations, either global or local,
01249  *          and stores them in the appropriate ptaa,
01250  *          either global or local.  For the latter, the
01251  *          pixel locations are relative to the c.c.
01252  */
01253 l_int32
01254 ccbaStepChainsToPixCoords(CCBORDA  *ccba,
01255                           l_int32   coordtype)
01256 {
01257 l_int32  ncc, nb, n, i, j, k;
01258 l_int32  xul, yul, xstart, ystart, x, y, stepdir;
01259 BOXA    *boxa;
01260 CCBORD  *ccb;
01261 NUMA    *na;
01262 NUMAA   *naa;
01263 PTAA    *ptaan;  /* new pix coord ptaa */
01264 PTA     *ptas, *ptan;
01265 
01266     PROCNAME("ccbaStepChainsToPixCoords");
01267 
01268     if (!ccba)
01269         return ERROR_INT("ccba not defined", procName, 1);
01270     if (coordtype != CCB_GLOBAL_COORDS && coordtype != CCB_LOCAL_COORDS)
01271         return ERROR_INT("coordtype not valid", procName, 1);
01272 
01273     ncc = ccbaGetCount(ccba);  /* number of c.c. */
01274     for (i = 0; i < ncc; i++) {
01275         ccb = ccbaGetCcb(ccba, i);
01276         if ((naa = ccb->step) == NULL)
01277             return ERROR_INT("step numaa not found", procName, 1);
01278         if ((boxa = ccb->boxa) == NULL)
01279             return ERROR_INT("boxa not found", procName, 1);
01280         if ((ptas = ccb->start) == NULL)
01281             return ERROR_INT("start pta not found", procName, 1);
01282 
01283             /* For global coords, get the (xul, yul) of the c.c.;
01284              * otherwise, use relative coords. */
01285         if (coordtype == CCB_LOCAL_COORDS) {
01286             xul = 0;
01287             yul = 0;
01288         }
01289         else {  /* coordtype == CCB_GLOBAL_COORDS */
01290                 /* Get UL corner in global coords */
01291             if (boxaGetBoxGeometry(boxa, 0, &xul, &yul, NULL, NULL))
01292                 return ERROR_INT("bounding rectangle not found", procName, 1);
01293         }
01294 
01295             /* Make a new ptaa, removing any old one */
01296         nb = numaaGetCount(naa);   /* number of borders */
01297         if ((ptaan = ptaaCreate(nb)) == NULL)
01298             return ERROR_INT("ptaan not made", procName, 1);
01299         if (coordtype == CCB_LOCAL_COORDS) {
01300             if (ccb->local)   /* remove old one */
01301                 ptaaDestroy(&ccb->local);
01302             ccb->local = ptaan;  /* save new local chain */
01303         }
01304         else {   /* coordtype == CCB_GLOBAL_COORDS */
01305             if (ccb->global)   /* remove old one */
01306                 ptaaDestroy(&ccb->global);
01307             ccb->global = ptaan;  /* save new global chain */
01308         }
01309 
01310             /* Iterate through the borders for this c.c. */
01311         for (j = 0; j < nb; j++) {
01312             na = numaaGetNuma(naa, j, L_CLONE);
01313             n = numaGetCount(na);   /* number of steps in border */
01314             if ((ptan = ptaCreate(n + 1)) == NULL)
01315                 return ERROR_INT("ptan not made", procName, 1);
01316             ptaaAddPta(ptaan, ptan, L_INSERT);
01317             ptaGetIPt(ptas, j, &xstart, &ystart);
01318             x = xul + xstart;
01319             y = yul + ystart;
01320             ptaAddPt(ptan, x, y);
01321             for (k = 0; k < n; k++) {
01322                 numaGetIValue(na, k, &stepdir);
01323                 x += xpostab[stepdir];
01324                 y += ypostab[stepdir];
01325                 ptaAddPt(ptan, x, y);
01326             }
01327             numaDestroy(&na);
01328         }
01329         ccbDestroy(&ccb);
01330     }
01331 
01332     return 0;
01333 }
01334 
01335 
01336 /*!
01337  *  ccbaGenerateSPGlobalLocs()
01338  *
01339  *      Input:  ccba
01340  *              ptsflag  (CCB_SAVE_ALL_PTS or CCB_SAVE_TURNING_PTS)
01341  *      Return: 0 if OK, 1 on error
01342  *
01343  *  Notes:
01344  *      (1) This calculates the splocal rep if not yet made.
01345  *      (2) It uses the local pixel values in splocal, the single
01346  *          path pta, which are all relative to each c.c., to find
01347  *          the corresponding global pixel locations, and stores
01348  *          them in the spglobal pta.
01349  *      (3) This lists only the turning points: it both makes a
01350  *          valid svg file and is typically about half the size
01351  *          when all border points are listed.
01352  */
01353 l_int32
01354 ccbaGenerateSPGlobalLocs(CCBORDA  *ccba,
01355                          l_int32   ptsflag)
01356 {
01357 l_int32  ncc, npt, i, j, xul, yul, x, y, delx, dely;
01358 l_int32  xp, yp, delxp, delyp;   /* prev point and increments */
01359 CCBORD  *ccb;
01360 PTA     *ptal, *ptag;
01361 
01362     PROCNAME("ccbaGenerateSPGlobalLocs");
01363 
01364     if (!ccba)
01365         return ERROR_INT("ccba not defined", procName, 1);
01366 
01367         /* Make sure we have a local single path representation */
01368     if ((ccb = ccbaGetCcb(ccba, 0)) == NULL)
01369         return ERROR_INT("no ccb", procName, 1);
01370     if (!ccb->splocal)
01371         ccbaGenerateSinglePath(ccba);
01372     ccbDestroy(&ccb);  /* clone ref */
01373 
01374     ncc = ccbaGetCount(ccba);  /* number of c.c. */
01375     for (i = 0; i < ncc; i++) {
01376         ccb = ccbaGetCcb(ccba, i);
01377 
01378             /* Get the UL corner in global coords, (xul, yul), of the c.c. */
01379         if (boxaGetBoxGeometry(ccb->boxa, 0, &xul, &yul, NULL, NULL))
01380             return ERROR_INT("bounding rectangle not found", procName, 1);
01381 
01382             /* Make a new spglobal pta, removing any old one */
01383         ptal = ccb->splocal;
01384         npt = ptaGetCount(ptal);   /* number of points */
01385         if (ccb->spglobal)   /* remove old one */
01386             ptaDestroy(&ccb->spglobal);
01387         if ((ptag = ptaCreate(npt)) == NULL)
01388             return ERROR_INT("ptag not made", procName, 1);
01389         ccb->spglobal = ptag;  /* save new one */
01390 
01391             /* Convert local to global */
01392         if (ptsflag == CCB_SAVE_ALL_PTS) {
01393             for (j = 0; j < npt; j++) {
01394                 ptaGetIPt(ptal, j, &x, &y);
01395                 ptaAddPt(ptag, x  + xul, y + yul);
01396             }
01397         }
01398         else {   /* ptsflag = CCB_SAVE_TURNING_PTS */
01399             ptaGetIPt(ptal, 0, &xp, &yp);   /* get the 1st pt */
01400             ptaAddPt(ptag, xp  + xul, yp + yul);   /* save the 1st pt */
01401             if (npt == 2) {  /* get and save the 2nd pt  */
01402                 ptaGetIPt(ptal, 1, &x, &y);
01403                 ptaAddPt(ptag, x  + xul, y + yul);
01404             }
01405             else if (npt > 2)  {
01406                 ptaGetIPt(ptal, 1, &x, &y);
01407                 delxp = x - xp;
01408                 delyp = y - yp;
01409                 xp = x;
01410                 yp = y;
01411                 for (j = 2; j < npt; j++) {
01412                     ptaGetIPt(ptal, j, &x, &y);
01413                     delx = x - xp;
01414                     dely = y - yp;
01415                     if (delx != delxp || dely != delyp)
01416                         ptaAddPt(ptag, xp  + xul, yp + yul);
01417                     xp = x;
01418                     yp = y;
01419                     delxp = delx;
01420                     delyp = dely;
01421                 }
01422                 ptaAddPt(ptag, xp  + xul, yp + yul);
01423             }
01424         }
01425 
01426         ccbDestroy(&ccb);  /* clone ref */
01427     }
01428 
01429     return 0;
01430 }
01431 
01432 
01433 
01434 /*---------------------------------------------------------------------*
01435  *                       Conversion to single path                     *
01436  *---------------------------------------------------------------------*/
01437 /*!
01438  *  ccbaGenerateSinglePath()
01439  *
01440  *      Input:  ccba
01441  *      Return: 0 if OK, 1 on error
01442  *
01443  *  Notes:
01444  *      (1) Generates a single border in local pixel coordinates.
01445  *          For each c.c., if there is just an outer border, copy it.
01446  *          If there are also hole borders, for each hole border,
01447  *          determine the smallest horizontal or vertical
01448  *          distance from the border to the outside of the c.c.,
01449  *          and find a path through the c.c. for this cut.
01450  *          We do this in a way that guarantees a pixel from the
01451  *          hole border is the starting point of the path, and
01452  *          we must verify that the path intersects the outer
01453  *          border (if it intersects it, then it ends on it).
01454  *          One can imagine pathological cases, but they may not
01455  *          occur in images of text characters and un-textured
01456  *          line graphics.
01457  *      (2) Once it is verified that the path through the c.c.
01458  *          intersects both the hole and outer borders, we
01459  *          generate the full single path for all borders in the
01460  *          c.c.  Starting at the start point on the outer
01461  *          border, when we hit a line on a cut, we take
01462  *          the cut, do the hold border, and return on the cut
01463  *          to the outer border.  We compose a pta of the
01464  *          outer border pts that are on cut paths, and for
01465  *          every point on the outer border (as we go around),
01466  *          we check against this pta.  When we find a matching
01467  *          point in the pta, we do its cut path and hole border.
01468  *          The single path is saved in the ccb.
01469  */
01470 l_int32
01471 ccbaGenerateSinglePath(CCBORDA  *ccba)
01472 {
01473 l_int32   i, j, k, ncc, nb, ncut, npt, dir, len, state, lostholes;
01474 l_int32   x, y, xl, yl, xf, yf;
01475 BOX      *boxinner;
01476 BOXA     *boxa;
01477 CCBORD   *ccb;
01478 PTA      *pta, *ptac, *ptah;
01479 PTA      *ptahc;  /* cyclic permutation of hole border, with end pts at cut */
01480 PTA      *ptas;  /* output result: new single path for c.c. */
01481 PTA      *ptaf;  /* points on the hole borders that intersect with cuts */
01482 PTA      *ptal;  /* points on outer border that intersect with cuts */
01483 PTA      *ptap, *ptarp;   /* path and reverse path between borders */
01484 PTAA     *ptaa;
01485 PTAA     *ptaap;  /* ptaa for all paths between borders */
01486 
01487     PROCNAME("ccbaGenerateSinglePath");
01488 
01489     if (!ccba)
01490         return ERROR_INT("ccba not defined", procName, 1);
01491 
01492     ncc = ccbaGetCount(ccba);   /* number of c.c. */
01493     lostholes = 0;
01494     for (i = 0; i < ncc; i++) {
01495         ccb = ccbaGetCcb(ccba, i);
01496         if ((ptaa = ccb->local) == NULL) {
01497             L_WARNING("local pixel loc array not found", procName);
01498             continue;
01499         }
01500         nb = ptaaGetCount(ptaa);   /* number of borders in the c.c.  */
01501 
01502             /* Prepare the output pta */
01503         if (ccb->splocal)
01504             ptaDestroy(&ccb->splocal);
01505         if ((ptas = ptaCreate(0)) == NULL)
01506             return ERROR_INT("ptas not made", procName, 1);
01507         ccb->splocal = ptas;
01508 
01509             /* If no holes, just concat the outer border */
01510         pta = ptaaGetPta(ptaa, 0, L_CLONE);
01511         if (nb == 1 || nb > NMAX_HOLES + 1) {
01512             ptaJoin(ptas, pta, 0, 0);
01513             ptaDestroy(&pta);  /* remove clone */
01514             ccbDestroy(&ccb);  /* remove clone */
01515             continue;
01516         }
01517 
01518             /* Find the (nb - 1) cut paths that connect holes
01519              * with outer border */
01520         boxa = ccb->boxa;
01521         if ((ptaap = ptaaCreate(nb - 1)) == NULL)
01522             return ERROR_INT("ptaap not made", procName, 1);
01523         if ((ptaf = ptaCreate(nb - 1)) == NULL)
01524             return ERROR_INT("ptaf not made", procName, 1);
01525         if ((ptal = ptaCreate(nb - 1)) == NULL)
01526             return ERROR_INT("ptal not made", procName, 1);
01527         for (j = 1; j < nb; j++) {
01528             boxinner = boxaGetBox(boxa, j, L_CLONE);
01529 
01530                 /* Find a short path and store it */
01531             ptac = getCutPathForHole(ccb->pix, pta, boxinner, &dir, &len);
01532             if (len == 0) {  /* bad: we lose the hole! */
01533                 lostholes++;
01534 /*                boxPrintStreamInfo(stderr, boxa->box[0]); */
01535             }
01536             ptaaAddPta(ptaap, ptac, L_INSERT);
01537 /*            fprintf(stderr, "dir = %d, length = %d\n", dir, len); */
01538 /*            ptaWriteStream(stderr, ptac, 1); */
01539 
01540                 /* Store the first and last points in the cut path,
01541                  * which must be on a hole border and the outer
01542                  * border, respectively */
01543             ncut = ptaGetCount(ptac);
01544             if (ncut == 0) {   /* missed hole; neg coords won't match */
01545                 ptaAddPt(ptaf, -1, -1);
01546                 ptaAddPt(ptal, -1, -1);
01547             }
01548             else {
01549                 ptaGetIPt(ptac, 0, &x, &y);
01550                 ptaAddPt(ptaf, x, y);
01551                 ptaGetIPt(ptac, ncut - 1, &x, &y);
01552                 ptaAddPt(ptal, x, y);
01553             }
01554             boxDestroy(&boxinner);
01555         }
01556                 
01557             /* Make a single path for the c.c. using these connections */
01558         npt = ptaGetCount(pta);  /* outer border pts */
01559         for (k = 0; k < npt; k++) {
01560             ptaGetIPt(pta, k, &x, &y);
01561             if (k == 0) {   /* if there is a cut at the first point,
01562                              * we can wait until the end to take it */
01563                 ptaAddPt(ptas, x, y); 
01564                 continue;
01565             }
01566             state = L_NOT_FOUND;
01567             for (j = 0; j < nb - 1; j++) {  /* iterate over cut end pts */
01568                 ptaGetIPt(ptal, j, &xl, &yl);  /* cut point on outer border */
01569                 if (x == xl && y == yl) {  /* take this cut to the hole */
01570                     state = L_FOUND;
01571                     ptap = ptaaGetPta(ptaap, j, L_CLONE);
01572                     if ((ptarp = ptaReverse(ptap, 1)) == NULL)
01573                         return ERROR_INT("ptarp not made", procName, 1);
01574                         /* Cut point on hole border: */
01575                     ptaGetIPt(ptaf, j, &xf, &yf);
01576                         /* Hole border: */
01577                     ptah = ptaaGetPta(ptaa, j + 1, L_CLONE);
01578                     ptahc = ptaCyclicPerm(ptah, xf, yf);
01579 /*                    ptaWriteStream(stderr, ptahc, 1); */
01580                     ptaJoin(ptas, ptarp, 0, 0);
01581                     ptaJoin(ptas, ptahc, 0, 0);
01582                     ptaJoin(ptas, ptap, 0, 0);
01583                     ptaDestroy(&ptap);
01584                     ptaDestroy(&ptarp);
01585                     ptaDestroy(&ptah);
01586                     ptaDestroy(&ptahc);
01587                     break;
01588                 }
01589             }
01590             if (state == L_NOT_FOUND)
01591                 ptaAddPt(ptas, x, y);
01592         }
01593         
01594 /*        ptaWriteStream(stderr, ptas, 1); */
01595         ptaaDestroy(&ptaap);
01596         ptaDestroy(&ptaf);
01597         ptaDestroy(&ptal);
01598         ptaDestroy(&pta);  /* remove clone */
01599         ccbDestroy(&ccb);  /* remove clone */
01600     }
01601 
01602     if (lostholes > 0)
01603         L_WARNING_INT("***** %d lost holes *****", procName, lostholes);
01604 
01605     return 0;
01606 }
01607 
01608 
01609 /*!
01610  *  getCutPathForHole()
01611  *
01612  *      Input:  pix  (of c.c.)
01613  *              pta  (of outer border)
01614  *              boxinner (b.b. of hole path)
01615  *              &dir  (direction (0-3), returned; only needed for debug)
01616  *              &len  (length of path, returned)
01617  *      Return: pta of pts on cut path from the hole border
01618  *              to the outer border, including end points on
01619  *              both borders; or null on error
01620  *
01621  *  Notes:
01622  *      (1) If we don't find a path, we return a pta with no pts
01623  *          in it and len = 0.
01624  *      (2) The goal is to get a reasonably short path between the
01625  *          inner and outer borders, that goes entirely within the fg of
01626  *          the pix.  This function is cheap-and-dirty, may fail for some
01627  *          holes in complex topologies such as those you might find in a
01628  *          moderately dark scanned halftone.  If it fails to find a
01629  *          path to any particular hole, it gives a warning, and because
01630  *          that hole path is not included, the hole will not be rendered.
01631  */
01632 PTA *
01633 getCutPathForHole(PIX      *pix,
01634                   PTA      *pta,
01635                   BOX      *boxinner,
01636                   l_int32  *pdir,
01637                   l_int32  *plen)
01638 {
01639 l_int32   w, h, nc, x, y, xl, yl, xmid, ymid;
01640 l_uint32  val;
01641 PTA      *ptac;
01642 
01643     PROCNAME("getCutPathForHole");
01644     
01645     if (!pix)
01646         return (PTA *)ERROR_PTR("pix not defined", procName, NULL);
01647     if (!pta)
01648         return (PTA *)ERROR_PTR("pta not defined", procName, NULL);
01649     if (!boxinner)
01650         return (PTA *)ERROR_PTR("boxinner not defined", procName, NULL);
01651 
01652     w = pixGetWidth(pix);
01653     h = pixGetHeight(pix);
01654 
01655     if ((ptac = ptaCreate(4)) == NULL)
01656         return (PTA *)ERROR_PTR("ptac not made", procName, NULL);
01657     xmid = boxinner->x + boxinner->w / 2;
01658     ymid = boxinner->y + boxinner->h / 2;
01659 
01660         /* try top first */
01661     for (y = ymid; y >= 0; y--) { 
01662         pixGetPixel(pix, xmid, y, &val);
01663         if (val == 1) {
01664             ptaAddPt(ptac, xmid, y);
01665             break;
01666         }
01667     }
01668     for (y = y - 1; y >= 0; y--) {
01669         pixGetPixel(pix, xmid, y, &val);
01670         if (val == 1)
01671             ptaAddPt(ptac, xmid, y);
01672         else
01673             break;
01674     }
01675     nc = ptaGetCount(ptac);
01676     ptaGetIPt(ptac, nc - 1, &xl, &yl);
01677     if (ptaContainsPt(pta, xl, yl)) {
01678         *pdir = 1;
01679         *plen = nc;
01680         return ptac;
01681     }
01682 
01683         /* Next try bottom */
01684     ptaEmpty(ptac);
01685     for (y = ymid; y < h; y++) { 
01686         pixGetPixel(pix, xmid, y, &val);
01687         if (val == 1) {
01688             ptaAddPt(ptac, xmid, y);
01689             break;
01690         }
01691     }
01692     for (y = y + 1; y < h; y++) {
01693         pixGetPixel(pix, xmid, y, &val);
01694         if (val == 1)
01695             ptaAddPt(ptac, xmid, y);
01696         else
01697             break;
01698     }
01699     nc = ptaGetCount(ptac);
01700     ptaGetIPt(ptac, nc - 1, &xl, &yl);
01701     if (ptaContainsPt(pta, xl, yl)) {
01702         *pdir = 3;
01703         *plen = nc;
01704         return ptac;
01705     }
01706 
01707         /* Next try left */
01708     ptaEmpty(ptac);
01709     for (x = xmid; x >= 0; x--) { 
01710         pixGetPixel(pix, x, ymid, &val);
01711         if (val == 1) {
01712             ptaAddPt(ptac, x, ymid);
01713             break;
01714         }
01715     }
01716     for (x = x - 1; x >= 0; x--) {
01717         pixGetPixel(pix, x, ymid, &val);
01718         if (val == 1)
01719             ptaAddPt(ptac, x, ymid);
01720         else
01721             break;
01722     }
01723     nc = ptaGetCount(ptac);
01724     ptaGetIPt(ptac, nc - 1, &xl, &yl);
01725     if (ptaContainsPt(pta, xl, yl)) {
01726         *pdir = 0;
01727         *plen = nc;
01728         return ptac;
01729     }
01730 
01731         /* Finally try right */
01732     ptaEmpty(ptac);
01733     for (x = xmid; x < w; x++) { 
01734         pixGetPixel(pix, x, ymid, &val);
01735         if (val == 1) {
01736             ptaAddPt(ptac, x, ymid);
01737             break;
01738         }
01739     }
01740     for (x = x + 1; x < w; x++) {
01741         pixGetPixel(pix, x, ymid, &val);
01742         if (val == 1)
01743             ptaAddPt(ptac, x, ymid);
01744         else
01745             break;
01746     }
01747     nc = ptaGetCount(ptac);
01748     ptaGetIPt(ptac, nc - 1, &xl, &yl);
01749     if (ptaContainsPt(pta, xl, yl)) {
01750         *pdir = 2;
01751         *plen = nc;
01752         return ptac;
01753     }
01754 
01755         /* If we get here, we've failed! */
01756     ptaEmpty(ptac);
01757 /*    L_WARNING("no path found", procName); */
01758     *plen = 0;
01759     return ptac;
01760 }
01761 
01762 
01763 
01764 /*---------------------------------------------------------------------*
01765  *                            Border rendering                         *
01766  *---------------------------------------------------------------------*/
01767 /*!
01768  *  ccbaDisplayBorder()
01769  *
01770  *      Input:  ccba
01771  *      Return: pix of border pixels, or null on error
01772  *
01773  *  Notes:
01774  *      (1) Uses global ptaa, which gives each border pixel in
01775  *          global coordinates, and must be computed in advance
01776  *          by calling ccbaGenerateGlobalLocs().
01777  */
01778 PIX *
01779 ccbaDisplayBorder(CCBORDA  *ccba)
01780 {
01781 l_int32  ncc, nb, n, i, j, k, x, y;
01782 CCBORD  *ccb;
01783 PIX     *pixd;
01784 PTAA    *ptaa;
01785 PTA     *pta;
01786 
01787     PROCNAME("ccbaDisplayBorder");
01788 
01789     if (!ccba)
01790         return (PIX *)ERROR_PTR("ccba not defined", procName, NULL);
01791 
01792     if ((pixd = pixCreate(ccba->w, ccba->h, 1)) == NULL)
01793         return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
01794     ncc = ccbaGetCount(ccba);   /* number of c.c. */
01795     for (i = 0; i < ncc; i++) {
01796         ccb = ccbaGetCcb(ccba, i);
01797         if ((ptaa = ccb->global) == NULL) {
01798             L_WARNING("global pixel loc array not found", procName);
01799             continue;
01800         }
01801         nb = ptaaGetCount(ptaa);   /* number of borders in the c.c.  */
01802         for (j = 0; j < nb; j++) {
01803             pta = ptaaGetPta(ptaa, j, L_CLONE);
01804             n = ptaGetCount(pta);   /* number of pixels in the border */
01805             for (k = 0; k < n; k++) {
01806                 ptaGetIPt(pta, k, &x, &y);
01807                 pixSetPixel(pixd, x, y, 1);
01808             }
01809             ptaDestroy(&pta);
01810         }
01811         ccbDestroy(&ccb);
01812     }
01813     
01814     return pixd;
01815 }
01816 
01817 
01818 /*!
01819  *  ccbaDisplaySPBorder()
01820  *
01821  *      Input:  ccba
01822  *      Return: pix of border pixels, or null on error
01823  *
01824  *  Notes:
01825  *      (1) Uses spglobal pta, which gives each border pixel in
01826  *          global coordinates, one path per c.c., and must
01827  *          be computed in advance by calling ccbaGenerateSPGlobalLocs().
01828  */
01829 PIX *
01830 ccbaDisplaySPBorder(CCBORDA  *ccba)
01831 {
01832 l_int32  ncc, npt, i, j, x, y;
01833 CCBORD  *ccb;
01834 PIX     *pixd;
01835 PTA     *ptag;
01836 
01837     PROCNAME("ccbaDisplaySPBorder");
01838 
01839     if (!ccba)
01840         return (PIX *)ERROR_PTR("ccba not defined", procName, NULL);
01841 
01842     if ((pixd = pixCreate(ccba->w, ccba->h, 1)) == NULL)
01843         return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
01844     ncc = ccbaGetCount(ccba);   /* number of c.c. */
01845     for (i = 0; i < ncc; i++) {
01846         ccb = ccbaGetCcb(ccba, i);
01847         if ((ptag = ccb->spglobal) == NULL) {
01848             L_WARNING("spglobal pixel loc array not found", procName);
01849             continue;
01850         }
01851         npt = ptaGetCount(ptag);   /* number of pixels on path */
01852         for (j = 0; j < npt; j++) {
01853             ptaGetIPt(ptag, j, &x, &y);
01854             pixSetPixel(pixd, x, y, 1);
01855         }
01856         ccbDestroy(&ccb);  /* clone ref */
01857     }
01858     
01859     return pixd;
01860 }
01861 
01862 
01863 /*!
01864  *  ccbaDisplayImage1()
01865  *
01866  *      Input:  ccborda
01867  *      Return: pix of image, or null on error
01868  *
01869  *  Notes:
01870  *      (1) Uses local ptaa, which gives each border pixel in
01871  *          local coordinates, so the actual pixel positions must
01872  *          be computed using all offsets.
01873  *      (2) For the holes, use coordinates relative to the c.c.
01874  *      (3) This is slower than Method 2.
01875  *      (4) This uses topological properties (Method 1) to do scan
01876  *          conversion to raster
01877  *
01878  *  This algorithm deserves some commentary. 
01879  *
01880  *  I first tried the following:
01881  *    - outer borders: 4-fill from outside, stopping at the
01882  *         border, using pixFillClosedBorders()
01883  *    - inner borders: 4-fill from outside, stopping again
01884  *         at the border, XOR with the border, and invert
01885  *         to get the hole.  This did not work, because if
01886  *         you have a hole border that looks like:
01887  *
01888  *                x x x x x x 
01889  *                x          x
01890  *                x   x x x   x
01891  *                  x x o x   x
01892  *                      x     x
01893  *                      x     x
01894  *                        x x x
01895  *
01896  *         if you 4-fill from the outside, the pixel 'o' will
01897  *         not be filled!  XORing with the border leaves it OFF.
01898  *         Inverting then gives a single bad ON pixel that is not
01899  *         actually part of the hole.
01900  *
01901  *  So what you must do instead is 4-fill the holes from inside.
01902  *  You can do this from a seedfill, using a pix with the hole
01903  *  border as the filling mask.  But you need to start with a
01904  *  pixel inside the hole.  How is this determined?  The best
01905  *  way is from the contour.  We have a right-hand shoulder
01906  *  rule for inside (i.e., the filled region).   Take the
01907  *  first 2 pixels of the hole border, and compute dx and dy
01908  *  (second coord minus first coord:  dx = sx - fx, dy = sy - fy).
01909  *  There are 8 possibilities, depending on the values of dx and
01910  *  dy (which can each be -1, 0, and +1, but not both 0).
01911  *  These 8 cases can be broken into 4; see the simple algorithm below. 
01912  *  Once you have an interior seed pixel, you fill from the seed,
01913  *  clipping with the hole border pix by filling into its invert.
01914  *
01915  *  You then successively XOR these interior filled components, in any order.
01916  */
01917 PIX *
01918 ccbaDisplayImage1(CCBORDA  *ccba)
01919 {
01920 l_int32  ncc, i, nb, n, j, k, x, y, xul, yul, xoff, yoff, w, h;
01921 l_int32  fpx, fpy, spx, spy, xs, ys;
01922 BOX     *box;
01923 BOXA    *boxa;
01924 CCBORD  *ccb;
01925 PIX     *pixd, *pixt, *pixh;
01926 PTAA    *ptaa;
01927 PTA     *pta;
01928 
01929     PROCNAME("ccbaDisplayImage1");
01930 
01931     if (!ccba)
01932         return (PIX *)ERROR_PTR("ccba not defined", procName, NULL);
01933 
01934     if ((pixd = pixCreate(ccba->w, ccba->h, 1)) == NULL)
01935         return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
01936     ncc = ccbaGetCount(ccba);
01937     for (i = 0; i < ncc; i++) {
01938         ccb = ccbaGetCcb(ccba, i);
01939         if ((boxa = ccb->boxa) == NULL)
01940             return (PIX *)ERROR_PTR("boxa not found", procName, NULL);
01941 
01942             /* Render border in pixt */
01943         if ((ptaa = ccb->local) == NULL) {
01944             L_WARNING("local chain array not found", procName);
01945             continue;
01946         }
01947 
01948         nb = ptaaGetCount(ptaa);   /* number of borders in the c.c.  */
01949         for (j = 0; j < nb; j++) {
01950             if ((box = boxaGetBox(boxa, j, L_CLONE)) == NULL)
01951                 return (PIX *)ERROR_PTR("b. box not found", procName, NULL);
01952             if (j == 0) {
01953                 boxGetGeometry(box, &xul, &yul, &w, &h);
01954                 xoff = yoff = 0;
01955             } else
01956                 boxGetGeometry(box, &xoff, &yoff, &w, &h);
01957             boxDestroy(&box);
01958 
01959                 /* Render the border in a minimum-sized pix;
01960                  * subtract xoff and yoff because the pixel
01961                  * location is stored relative to the c.c., but
01962                  * we need it relative to just the hole border. */
01963             if ((pixt = pixCreate(w, h, 1)) == NULL)
01964                 return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
01965             pta = ptaaGetPta(ptaa, j, L_CLONE);
01966             n = ptaGetCount(pta);   /* number of pixels in the border */
01967             for (k = 0; k < n; k++) {
01968                 ptaGetIPt(pta, k, &x, &y);
01969                 pixSetPixel(pixt, x - xoff, y - yoff, 1);
01970                 if (j > 0) {   /* need this for finding hole border pixel */
01971                     if (k == 0) {
01972                         fpx = x - xoff;
01973                         fpy = y - yoff;
01974                     }
01975                     if (k == 1) {
01976                         spx = x - xoff;
01977                         spy = y - yoff;
01978                     }
01979                 }
01980             }
01981             ptaDestroy(&pta);
01982 
01983                 /* Get the filled component */
01984             if (j == 0) {  /* if outer border, fill from outer boundary */
01985                 if ((pixh = pixFillClosedBorders(pixt, 4)) == NULL)
01986                     return (PIX *)ERROR_PTR("pixh not made", procName, NULL);
01987             }
01988             else {   /* fill the hole from inside */
01989                     /* get the location of a seed pixel in the hole */
01990                 locateOutsideSeedPixel(fpx, fpy, spx, spy, &xs, &ys);
01991 
01992                     /* Put seed in hole and fill interior of hole,
01993                      * using pixt as clipping mask */
01994                 if ((pixh = pixCreateTemplate(pixt)) == NULL)
01995                     return (PIX *)ERROR_PTR("pixh not made", procName, NULL);
01996                 pixSetPixel(pixh, xs, ys, 1);  /* put seed pixel in hole */
01997                 pixInvert(pixt, pixt);  /* to make filling mask */
01998                 pixSeedfillBinary(pixh, pixh, pixt, 4);  /* 4-fill hole */
01999             }
02000 
02001                 /* XOR into the dest */
02002             pixRasterop(pixd, xul + xoff, yul + yoff, w, h, PIX_XOR,
02003                         pixh, 0, 0);
02004             pixDestroy(&pixt);
02005             pixDestroy(&pixh);
02006         }
02007 
02008         ccbDestroy(&ccb);
02009     }
02010     
02011     return pixd;
02012 }
02013 
02014 
02015 
02016 /*!
02017  *  ccbaDisplayImage2()
02018  *
02019  *      Input: ccborda
02020  *      Return: pix of image, or null on error
02021  *
02022  *  Notes:
02023  *      (1) Uses local chain ptaa, which gives each border pixel in
02024  *          local coordinates, so the actual pixel positions must
02025  *          be computed using all offsets.
02026  *      (2) Treats exterior and hole borders on equivalent
02027  *          footing, and does all calculations on a pix
02028  *          that spans the c.c. with a 1 pixel added boundary.
02029  *      (3) This uses topological properties (Method 2) to do scan
02030  *          conversion to raster
02031  *      (4) The algorithm is described at the top of this file (Method 2).
02032  *          It is preferred to Method 1 because it is between 1.2x and 2x
02033  *          faster than Method 1.
02034  */
02035 PIX *
02036 ccbaDisplayImage2(CCBORDA  *ccba)
02037 {
02038 l_int32  ncc, nb, n, i, j, k, x, y, xul, yul, w, h;
02039 l_int32  fpx, fpy, spx, spy, xs, ys;
02040 BOXA    *boxa;
02041 CCBORD  *ccb;
02042 PIX     *pixd, *pixc, *pixs;
02043 PTAA    *ptaa;
02044 PTA     *pta;
02045 
02046     PROCNAME("ccbaDisplayImage2");
02047 
02048     if (!ccba)
02049         return (PIX *)ERROR_PTR("ccba not defined", procName, NULL);
02050 
02051     if ((pixd = pixCreate(ccba->w, ccba->h, 1)) == NULL)
02052         return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
02053 
02054     ncc = ccbaGetCount(ccba);
02055     for (i = 0; i < ncc; i++) {
02056 
02057             /* Generate clipping mask from border pixels and seed image
02058              * from one seed for each closed border. */
02059         ccb = ccbaGetCcb(ccba, i);
02060         if ((boxa = ccb->boxa) == NULL)
02061             return (PIX *)ERROR_PTR("boxa not found", procName, NULL);
02062         if (boxaGetBoxGeometry(boxa, 0, &xul, &yul, &w, &h))
02063             return (PIX *)ERROR_PTR("b. box not found", procName, NULL);
02064         if ((pixc = pixCreate(w + 2, h + 2, 1)) == NULL)
02065             return (PIX *)ERROR_PTR("pixc not made", procName, NULL);
02066         if ((pixs = pixCreateTemplate(pixc)) == NULL)
02067             return (PIX *)ERROR_PTR("pixs not made", procName, NULL);
02068 
02069         if ((ptaa = ccb->local) == NULL) {
02070             L_WARNING("local chain array not found", procName);
02071             continue;
02072         }
02073         nb = ptaaGetCount(ptaa);   /* number of borders in the c.c.  */
02074         for (j = 0; j < nb; j++) {
02075             pta = ptaaGetPta(ptaa, j, L_CLONE);
02076             n = ptaGetCount(pta);   /* number of pixels in the border */
02077 
02078                 /* Render border pixels in pixc */
02079             for (k = 0; k < n; k++) {
02080                 ptaGetIPt(pta, k, &x, &y);
02081                 pixSetPixel(pixc, x + 1, y + 1, 1);
02082                 if (k == 0) {
02083                     fpx = x + 1;
02084                     fpy = y + 1;
02085                 }
02086                 else if (k == 1) {
02087                     spx = x + 1;
02088                     spy = y + 1;
02089                 }
02090             }
02091 
02092                 /* Get and set seed pixel for this border in pixs */
02093             if (n > 1)
02094                 locateOutsideSeedPixel(fpx, fpy, spx, spy, &xs, &ys);
02095             else  /* isolated c.c. */
02096                 xs = ys = 0;
02097             pixSetPixel(pixs, xs, ys, 1);
02098             ptaDestroy(&pta);
02099         }
02100 
02101             /* Fill from seeds in pixs, using pixc as the clipping mask,
02102              * to reconstruct the c.c. */
02103         pixInvert(pixc, pixc);  /* to convert clipping -> filling mask */
02104         pixSeedfillBinary(pixs, pixs, pixc, 4);  /* 4-fill */
02105         pixInvert(pixs, pixs);  /* to make the c.c. */
02106 
02107             /* XOR into the dest */
02108         pixRasterop(pixd, xul, yul, w, h, PIX_XOR, pixs, 1, 1);
02109 
02110         pixDestroy(&pixc);
02111         pixDestroy(&pixs);
02112         ccbDestroy(&ccb);  /* ref-counted */
02113     }
02114 
02115     return pixd;
02116 }
02117         
02118 
02119 
02120 /*---------------------------------------------------------------------*
02121  *                            Serialize for I/O                        *
02122  *---------------------------------------------------------------------*/
02123 /*!
02124  *  ccbaWrite()
02125  *
02126  *      Input:  filename
02127  *              ccba
02128  *      Return: 0 if OK, 1 on error
02129  */
02130 l_int32
02131 ccbaWrite(const char  *filename,
02132           CCBORDA     *ccba)
02133 {
02134 FILE  *fp;
02135 
02136     PROCNAME("ccbaWrite");
02137 
02138     if (!filename)
02139         return ERROR_INT("filename not defined", procName, 1);
02140     if (!ccba)
02141         return ERROR_INT("ccba not defined", procName, 1);
02142 
02143     if ((fp = fopenWriteStream(filename, "wb+")) == NULL)
02144         return ERROR_INT("stream not opened", procName, 1);
02145     if (ccbaWriteStream(fp, ccba)) {
02146         fclose(fp);
02147         return ERROR_INT("ccba not written to stream", procName, 1);
02148     }
02149 
02150     fclose(fp);
02151     return 0;
02152 }
02153 
02154 
02155 
02156 /*!
02157  *  ccbaWriteStream()
02158  *
02159  *      Input:  stream
02160  *              ccba
02161  *      Return: 0 if OK; 1 on error
02162  *
02163  *  Format:  ccba: %7d cc\n (num. c.c.) (ascii)   (18B)
02164  *           pix width (4B)
02165  *           pix height (4B)
02166  *           [for i = 1, ncc]
02167  *               ulx  (4B)
02168  *               uly  (4B)
02169  *               w    (4B)       -- not req'd for reconstruction
02170  *               h    (4B)       -- not req'd for reconstruction
02171  *               number of borders (4B)
02172  *               [for j = 1, nb]
02173  *                   startx  (4B)
02174  *                   starty  (4B)
02175  *                   [for k = 1, nb]
02176  *                        2 steps (1B)
02177  *                   end in z8 or 88  (1B)
02178  */
02179 l_int32
02180 ccbaWriteStream(FILE     *fp,
02181                 CCBORDA  *ccba)
02182 {
02183 char      strbuf[256];
02184 l_uint8   bval;
02185 l_uint8  *datain, *dataout;
02186 l_int32   i, j, k, bx, by, bw, bh, val, startx, starty; 
02187 l_int32   ncc, nb, n;
02188 l_uint32  w, h;
02189 size_t    inbytes, outbytes;
02190 BBUFFER  *bbuf;
02191 CCBORD   *ccb;
02192 NUMA     *na;
02193 NUMAA    *naa;
02194 PTA      *pta;
02195 
02196     PROCNAME("ccbaWriteStream");
02197 
02198 #if  !HAVE_LIBZ  /* defined in environ.h */
02199     return ERROR_INT("no libz: can't write data", procName, 1);
02200 #else
02201 
02202     if (!fp)
02203         return ERROR_INT("stream not open", procName, 1);
02204     if (!ccba)
02205         return ERROR_INT("ccba not defined", procName, 1);
02206 
02207     if ((bbuf = bbufferCreate(NULL, 1000)) == NULL)
02208         return ERROR_INT("bbuf not made", procName, 1);
02209 
02210     ncc = ccbaGetCount(ccba);
02211     sprintf(strbuf, "ccba: %7d cc\n", ncc);
02212     bbufferRead(bbuf, (l_uint8 *)strbuf, 18);
02213     w = pixGetWidth(ccba->pix);
02214     h = pixGetHeight(ccba->pix);
02215     bbufferRead(bbuf, (l_uint8 *)&w, 4);  /* width */
02216     bbufferRead(bbuf, (l_uint8 *)&h, 4);  /* height */
02217     for (i = 0; i < ncc; i++) {
02218         ccb = ccbaGetCcb(ccba, i);
02219         if (boxaGetBoxGeometry(ccb->boxa, 0, &bx, &by, &bw, &bh))
02220             return ERROR_INT("bounding box not found", procName, 1);
02221         bbufferRead(bbuf, (l_uint8 *)&bx, 4);  /* ulx of c.c. */
02222         bbufferRead(bbuf, (l_uint8 *)&by, 4);  /* uly of c.c. */
02223         bbufferRead(bbuf, (l_uint8 *)&bw, 4);  /* w of c.c. */
02224         bbufferRead(bbuf, (l_uint8 *)&bh, 4);  /* h of c.c. */
02225         if ((naa = ccb->step) == NULL) {
02226             ccbaGenerateStepChains(ccba);
02227             naa = ccb->step;
02228         }
02229         nb = numaaGetCount(naa);
02230         bbufferRead(bbuf, (l_uint8 *)&nb, 4);  /* number of borders in c.c. */
02231         pta = ccb->start;
02232         for (j = 0; j < nb; j++) {
02233             ptaGetIPt(pta, j, &startx, &starty);
02234             bbufferRead(bbuf, (l_uint8 *)&startx, 4);  /* starting x in border */
02235             bbufferRead(bbuf, (l_uint8 *)&starty, 4);  /* starting y in border */
02236             na = numaaGetNuma(naa, j, L_CLONE);
02237             n = numaGetCount(na);
02238             for (k = 0; k < n; k++) {
02239                 numaGetIValue(na, k, &val);
02240                 if (k % 2 == 0)
02241                     bval = (l_uint8)val << 4;
02242                 else
02243                     bval |= (l_uint8)val;
02244                 if (k % 2 == 1)
02245                     bbufferRead(bbuf, (l_uint8 *)&bval, 1);  /* 2 border steps */
02246             }
02247             if (n % 2 == 1) {
02248                 bval |= 0x8;
02249                 bbufferRead(bbuf, (l_uint8 *)&bval, 1); /* end with 0xz8,   */
02250                                              /* where z = {0..7} */
02251             }
02252             else {  /* n % 2 == 0 */
02253                 bval = 0x88;
02254                 bbufferRead(bbuf, (l_uint8 *)&bval, 1);   /* end with 0x88 */
02255             }
02256             numaDestroy(&na);
02257         }
02258         ccbDestroy(&ccb);
02259     }
02260 
02261     datain = bbufferDestroyAndSaveData(&bbuf, &inbytes);
02262     dataout = zlibCompress(datain, inbytes, &outbytes);
02263     fwrite(dataout, 1, outbytes, fp);
02264 
02265     FREE(datain);
02266     FREE(dataout);
02267     return 0;
02268 
02269 #endif  /* !HAVE_LIBZ */
02270 }
02271 
02272 
02273 /*!
02274  *  ccbaRead()
02275  *
02276  *      Input:  filename
02277  *      Return: ccba, or null on error
02278  */
02279 CCBORDA *
02280 ccbaRead(const char  *filename)
02281 {
02282 FILE     *fp;
02283 CCBORDA  *ccba;
02284 
02285     PROCNAME("ccbaRead");
02286 
02287     if (!filename)
02288         return (CCBORDA *)ERROR_PTR("filename not defined", procName, NULL);
02289 
02290     if ((fp = fopenReadStream(filename)) == NULL)
02291         return (CCBORDA *)ERROR_PTR("stream not opened", procName, NULL);
02292     ccba = ccbaReadStream(fp);
02293     fclose(fp);
02294 
02295     if (!ccba)
02296         return (CCBORDA *)ERROR_PTR("ccba not returned", procName, NULL);
02297     return ccba;
02298 }
02299 
02300 
02301 /*!
02302  *  ccbaReadStream()
02303  *
02304  *      Input:   stream
02305  *      Return:  ccba, or null on error
02306  *
02307  *  Format:  ccba: %7d cc\n (num. c.c.) (ascii)   (17B)
02308  *           pix width (4B)
02309  *           pix height (4B)
02310  *           [for i = 1, ncc]
02311  *               ulx  (4B)
02312  *               uly  (4B)
02313  *               w    (4B)       -- not req'd for reconstruction
02314  *               h    (4B)       -- not req'd for reconstruction
02315  *               number of borders (4B)
02316  *               [for j = 1, nb]
02317  *                   startx  (4B)
02318  *                   starty  (4B)
02319  *                   [for k = 1, nb]
02320  *                        2 steps (1B)
02321  *                   end in z8 or 88  (1B)
02322  */
02323 CCBORDA *
02324 ccbaReadStream(FILE  *fp)
02325 {
02326 char      strbuf[256];
02327 l_uint8   bval;
02328 l_uint8  *datain, *dataout;
02329 l_int32   i, j, startx, starty; 
02330 l_int32   offset, nib1, nib2;
02331 l_int32   ncc, nb;
02332 l_uint32  width, height, w, h, xoff, yoff;
02333 size_t    inbytes, outbytes;
02334 BOX      *box;
02335 CCBORD   *ccb;
02336 CCBORDA  *ccba;
02337 NUMA     *na;
02338 NUMAA    *step;
02339 
02340     PROCNAME("ccbaReadStream");
02341 
02342 #if  !HAVE_LIBZ  /* defined in environ.h */
02343     return (CCBORDA *)ERROR_PTR("no libz: can't read data", procName, NULL);
02344 #else
02345 
02346     if (!fp)
02347         return (CCBORDA *)ERROR_PTR("stream not open", procName, NULL);
02348 
02349     if ((datain = l_binaryReadStream(fp, &inbytes)) == NULL)
02350         return (CCBORDA *)ERROR_PTR("data not read from file", procName, NULL);
02351 
02352     if ((dataout = zlibUncompress(datain, inbytes, &outbytes)) == NULL)
02353         return (CCBORDA *)ERROR_PTR("dataout not made", procName, NULL);
02354 
02355     offset = 18;
02356     memcpy((void *)strbuf, (void *)dataout, offset);
02357     strbuf[17] = '\0';
02358     if (strncmp(strbuf, "ccba:", 5))
02359         return (CCBORDA *)ERROR_PTR("file not type ccba", procName, NULL);
02360     sscanf(strbuf, "ccba: %7d cc\n", &ncc);
02361 /*    fprintf(stderr, "ncc = %d\n", ncc); */
02362     if ((ccba = ccbaCreate(NULL, ncc)) == NULL)
02363         return (CCBORDA *)ERROR_PTR("ccba not made", procName, NULL);
02364 
02365     memcpy((void *)&width, (void *)(dataout + offset), 4);
02366     offset += 4;
02367     memcpy((void *)&height, (void *)(dataout + offset), 4);
02368     offset += 4;
02369     ccba->w = width;
02370     ccba->h = height;
02371 /*    fprintf(stderr, "width = %d, height = %d\n", width, height); */
02372 
02373     for (i = 0; i < ncc; i++) {  /* should be ncc */
02374         if ((ccb = ccbCreate(NULL)) == NULL)
02375             return (CCBORDA *)ERROR_PTR("ccb not made", procName, NULL);
02376         ccbaAddCcb(ccba, ccb);
02377 
02378         memcpy((void *)&xoff, (void *)(dataout + offset), 4);
02379         offset += 4;
02380         memcpy((void *)&yoff, (void *)(dataout + offset), 4);
02381         offset += 4;
02382         memcpy((void *)&w, (void *)(dataout + offset), 4);
02383         offset += 4;
02384         memcpy((void *)&h, (void *)(dataout + offset), 4);
02385         offset += 4;
02386         if ((box = boxCreate(xoff, yoff, w, h)) == NULL)
02387             return (CCBORDA *)ERROR_PTR("box not made", procName, NULL);
02388         boxaAddBox(ccb->boxa, box, L_INSERT);
02389 /*        fprintf(stderr, "xoff = %d, yoff = %d, w = %d, h = %d\n",
02390                 xoff, yoff, w, h); */
02391    
02392         memcpy((void *)&nb, (void *)(dataout + offset), 4);
02393         offset += 4;
02394 /*        fprintf(stderr, "num borders = %d\n", nb); */
02395         if ((step = numaaCreate(nb)) == NULL)
02396             return (CCBORDA *)ERROR_PTR("step numaa not made", procName, NULL);
02397         ccb->step = step;
02398 
02399         for (j = 0; j < nb; j++) {  /* should be nb */
02400             memcpy((void *)&startx, (void *)(dataout + offset), 4);
02401             offset += 4;
02402             memcpy((void *)&starty, (void *)(dataout + offset), 4);
02403             offset += 4;
02404             ptaAddPt(ccb->start, startx, starty);
02405 /*            fprintf(stderr, "startx = %d, starty = %d\n", startx, starty); */
02406             if ((na = numaCreate(0)) == NULL)
02407                 return (CCBORDA *)ERROR_PTR("na not made", procName, NULL);
02408             numaaAddNuma(step, na, L_INSERT);
02409 
02410             while(1) {
02411                 bval = *(dataout + offset);
02412                 offset++;
02413                 nib1 = (bval >> 4);
02414                 nib2 = bval & 0xf;
02415                 if (nib1 != 8)
02416                     numaAddNumber(na, nib1);
02417                 else
02418                     break;
02419                 if (nib2 != 8)
02420                     numaAddNumber(na, nib2);
02421                 else
02422                     break;
02423             }
02424         }
02425     }
02426     FREE(datain);
02427     FREE(dataout);
02428 
02429     return ccba;
02430 
02431 #endif  /* !HAVE_LIBZ */
02432 }
02433 
02434 
02435 /*---------------------------------------------------------------------*
02436  *                                SVG Output                           *
02437  *---------------------------------------------------------------------*/
02438 /*!
02439  *  ccbaWriteSVG()
02440  *
02441  *      Input:  filename
02442  *              ccba
02443  *      Return: 0 if OK, 1 on error
02444  */
02445 l_int32
02446 ccbaWriteSVG(const char  *filename,
02447              CCBORDA     *ccba)
02448 {
02449 char  *svgstr;
02450 
02451     PROCNAME("ccbaWriteSVG");
02452 
02453     if (!filename)
02454         return ERROR_INT("filename not defined", procName, 1);
02455     if (!ccba)
02456         return ERROR_INT("ccba not defined", procName, 1);
02457 
02458     if ((svgstr = ccbaWriteSVGString(filename, ccba)) == NULL)
02459         return ERROR_INT("svgstr not made", procName, 1);
02460 
02461     l_binaryWrite(filename, "w", svgstr, strlen(svgstr));
02462     FREE(svgstr);
02463 
02464     return 0;
02465 }
02466 
02467 
02468 /*!
02469  *  ccbaWriteSVGString()
02470  *
02471  *      Input:  filename
02472  *              ccba
02473  *      Return: string in svg-formatted, that can be written to file,
02474  *              or null on error.
02475  */
02476 char  *
02477 ccbaWriteSVGString(const char  *filename,
02478                    CCBORDA     *ccba)
02479 {
02480 char    *svgstr;
02481 char     smallbuf[256];
02482 char     line0[] = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>";
02483 char     line1[] = "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 20000303 Stylable//EN\" \"http://www.w3.org/TR/2000/03/WD-SVG-20000303/DTD/svg-20000303-stylable.dtd\">";
02484 char     line2[] = "<svg>";
02485 char     line3[] = "<polygon style=\"stroke-width:1;stroke:black;\" points=\"";
02486 char     line4[] = "\" />";
02487 char     line5[] = "</svg>";
02488 char     space[] = " ";
02489 l_int32  i, j, ncc, npt, x, y;
02490 CCBORD  *ccb;
02491 PTA     *pta;
02492 SARRAY  *sa;
02493 
02494     PROCNAME("ccbaWriteSVGString");
02495 
02496     if (!filename)
02497         return (char *)ERROR_PTR("filename not defined", procName, NULL);
02498     if (!ccba)
02499         return (char *)ERROR_PTR("ccba not defined", procName, NULL);
02500 
02501     if ((sa = sarrayCreate(0)) == NULL)
02502         return (char *)ERROR_PTR("sa not made", procName, NULL);
02503     sarrayAddString(sa, line0, 1);
02504     sarrayAddString(sa, line1, 1);
02505     sarrayAddString(sa, line2, 1);
02506 
02507     ncc = ccbaGetCount(ccba);
02508     for (i = 0; i < ncc; i++) {
02509         if ((ccb = ccbaGetCcb(ccba, i)) == NULL)
02510             return (char *)ERROR_PTR("ccb not found", procName, NULL);
02511         if ((pta = ccb->spglobal) == NULL)
02512             return (char *)ERROR_PTR("spglobal not made", procName, NULL);
02513         sarrayAddString(sa, line3, 1);
02514         npt = ptaGetCount(pta);
02515         for (j = 0; j < npt; j++) {
02516             ptaGetIPt(pta, j, &x, &y);
02517             sprintf(smallbuf, "%0d,%0d", x, y);
02518             sarrayAddString(sa, smallbuf, 1);
02519         }
02520         sarrayAddString(sa, line4, 1);
02521         ccbDestroy(&ccb);
02522     }
02523     sarrayAddString(sa, line5, 1);
02524     sarrayAddString(sa, space, 1);
02525 
02526     svgstr = sarrayToString(sa, 1);
02527 /*    fprintf(stderr, "%s", svgstr); */
02528 
02529     sarrayDestroy(&sa);
02530     return svgstr;
02531 }
02532 
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Defines