1 /*====================================================================*
2  -  Copyright (C) 2001 Leptonica.  All rights reserved.
3  -
4  -  Redistribution and use in source and binary forms, with or without
5  -  modification, are permitted provided that the following conditions
6  -  are met:
7  -  1. Redistributions of source code must retain the above copyright
8  -     notice, this list of conditions and the following disclaimer.
9  -  2. Redistributions in binary form must reproduce the above
10  -     copyright notice, this list of conditions and the following
11  -     disclaimer in the documentation and/or other materials
12  -     provided with the distribution.
13  -
14  -  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15  -  ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16  -  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17  -  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL ANY
18  -  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  -  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  -  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  -  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  -  OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23  -  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24  -  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  *====================================================================*/
26 
27 
28 /*!
29  * \file ccbord.c
30  * <pre>
31  *
32  *     CCBORDA and CCBORD creation and destruction
33  *         CCBORDA     *ccbaCreate()
34  *         void        *ccbaDestroy()
35  *         CCBORD      *ccbCreate()
36  *         void        *ccbDestroy()
37  *
38  *     CCBORDA addition
39  *         l_int32      ccbaAddCcb()
40  *         static l_int32  ccbaExtendArray()
41  *
42  *     CCBORDA accessors
43  *         l_int32      ccbaGetCount()
44  *         l_int32      ccbaGetCcb()
45  *
46  *     Top-level border-finding routines
47  *         CCBORDA     *pixGetAllCCBorders()
48  *         CCBORD      *pixGetCCBorders()
49  *         PTAA        *pixGetOuterBordersPtaa()
50  *         PTA         *pixGetOuterBorderPta()
51  *
52  *     Lower-level border location routines
53  *         l_int32      pixGetOuterBorder()
54  *         l_int32      pixGetHoleBorder()
55  *         l_int32      findNextBorderPixel()
56  *         void         locateOutsideSeedPixel()
57  *
58  *     Border conversions
59  *         l_int32      ccbaGenerateGlobalLocs()
60  *         l_int32      ccbaGenerateStepChains()
61  *         l_int32      ccbaStepChainsToPixCoords()
62  *         l_int32      ccbaGenerateSPGlobalLocs()
63  *
64  *     Conversion to single path
65  *         l_int32      ccbaGenerateSinglePath()
66  *         PTA         *getCutPathForHole()
67  *
68  *     Border and full image rendering
69  *         PIX         *ccbaDisplayBorder()
70  *         PIX         *ccbaDisplaySPBorder()
71  *         PIX         *ccbaDisplayImage1()
72  *         PIX         *ccbaDisplayImage2()
73  *
74  *     Serialize for I/O
75  *         l_int32      ccbaWrite()
76  *         l_int32      ccbaWriteStream()
77  *         l_int32      ccbaRead()
78  *         l_int32      ccbaReadStream()
79  *
80  *     SVG output
81  *         l_int32      ccbaWriteSVG()
82  *         char        *ccbaWriteSVGString()
83  *
84  *
85  *     Border finding is tricky because components can have
86  *     holes, which also need to be traced out.  The outer
87  *     border can be connected with all the hole borders,
88  *     so that there is a single border for each component.
89  *     [Alternatively, the connecting paths can be eliminated if
90  *     you're willing to have a set of borders for each
91  *     component (an exterior border and some number of
92  *     interior ones), with "line to" operations tracing
93  *     out each border and "move to" operations going from
94  *     one border to the next.]
95  *
96  *     Here's the plan.  We get the pix for each connected
97  *     component, and trace its exterior border.  We then
98  *     find the holes (if any) in the pix, and separately
99  *     trace out their borders, all using the same
100  *     border-following rule that has ON pixels on the right
101  *     side of the path.
102  *
103  *     [For svg, we may want to turn each set of borders for a c.c.
104  *     into a closed path.  This can be done by tunnelling
105  *     through the component from the outer border to each of the
106  *     holes, going in and coming out along the same path so
107  *     the connection will be invisible in any rendering
108  *     (display or print) from the outline.  The result is a
109  *     closed path, where the outside border is traversed
110  *     cw and each hole is traversed ccw.  The svg renderer
111  *     is assumed to handle these closed borders properly.]
112  *
113  *     Each border is a closed path that is traversed in such
114  *     a way that the stuff inside the c.c. is on the right
115  *     side of the traveller.  The border of a singly-connected
116  *     component is thus traversed cw, and the border of the
117  *     holes inside a c.c. are traversed ccw.  Suppose we have
118  *     a list of all the borders of each c.c., both the cw and ccw
119  *     traversals.  How do we reconstruct the image?
120  *
121  *   Reconstruction:
122  *
123  *     Method 1.  Topological method using connected components.
124  *     We have closed borders composed of cw border pixels for the
125  *     exterior of c.c. and ccw border pixels for the interior (holes)
126  *     in the c.c.
127  *         (a) Initialize the destination to be OFF.  Then,
128  *             in any order:
129  *         (b) Fill the components within and including the cw borders,
130  *             and sequentially XOR them onto the destination.
131  *         (c) Fill the components within but not including the ccw
132  *             borders and sequentially XOR them onto the destination.
133  *     The components that are XOR'd together can be generated as follows:
134  *         (a) For each closed cw path, use pixFillClosedBorders():
135  *               (1) Turn on the path pixels in a subimage that
136  *                   minimally supports the border.
137  *               (2) Do a 4-connected fill from a seed of 1 pixel width
138  *                   on the border, using the inverted image in (1) as
139  *                   a filling mask.
140  *               (3) Invert the fill result: this gives the component
141  *                   including the exterior cw path, with all holes
142  *                   filled.
143  *         (b) For each closed ccw path (hole):
144  *               (1) Turn on the path pixels in a subimage that minimally
145  *                   supports the path.
146  *               (2) Find a seed pixel on the inside of this path.
147  *               (3) Do a 4-connected fill from this seed pixel, using
148  *                   the inverted image of the path in (1) as a filling
149  *                   mask.
150  *
151  *     ------------------------------------------------------
152  *
153  *     Method 2.  A variant of Method 1.  Topological.
154  *     In Method 1, we treat the exterior border differently from
155  *     the interior (hole) borders.  Here, all borders in a c.c.
156  *     are treated equally:
157  *         (1) Start with a pix with a 1 pixel OFF boundary
158  *             enclosing all the border pixels of the c.c.
159  *             This is the filling mask.
160  *         (2) Make a seed image of the same size as follows:  for
161  *             each border, put one seed pixel OUTSIDE the border
162  *             (where OUTSIDE is determined by the inside/outside
163  *             convention for borders).
164  *         (3) Seedfill into the seed image, filling in the regions
165  *             determined by the filling mask.  The fills are clipped
166  *             by the border pixels.
167  *         (4) Inverting this, we get the c.c. properly filled,
168  *             with the holes empty!
169  *         (5) Rasterop using XOR the filled c.c. (but not the 1
170  *             pixel boundary) into the full dest image.
171  *
172  *     Method 2 is about 1.2x faster than Method 1 on text images,
173  *     and about 2x faster on complex images (e.g., with halftones).
174  *
175  *     ------------------------------------------------------
176  *
177  *     Method 3.  The traditional way to fill components delineated
178  *     by boundaries is through scan line conversion.  It's a bit
179  *     tricky, and I have not yet tried to implement it.
180  *
181  *     ------------------------------------------------------
182  *
183  *     Method 4.  [Nota Bene: this method probably doesn't work, and
184  *     won't be implemented.  If I get a more traditional scan line
185  *     conversion algorithm working, I'll erase these notes.]
186  *     Render all border pixels on a destination image,
187  *     which will be the final result after scan conversion.  Assign
188  *     a value 1 to pixels on cw paths, 2 to pixels on ccw paths,
189  *     and 3 to pixels that are on both paths.  Each of the paths
190  *     is an 8-connected component.  Now scan across each raster
191  *     line.  The attempt is to make rules for each scan line
192  *     that are independent of neighboring scanlines.  Here are
193  *     a set of rules for writing ON pixels on a destination raster image:
194  *
195  *         (a) The rasterizer will be in one of two states: ON and OFF.
196  *         (b) Start each line in the OFF state.  In the OFF state,
197  *             skip pixels until you hit a path of any type.  Turn
198  *             the path pixel ON.
199  *         (c) If the state is ON, each pixel you encounter will
200  *             be turned on, until and including hitting a path pixel.
201  *         (d) When you hit a path pixel, if the path does NOT cut
202  *             through the line, so that there is not an 8-cc path
203  *             pixel (of any type) both above and below, the state
204  *             is unchanged (it stays either ON or OFF).
205  *         (e) If the path does cut through, but with a possible change
206  *             of pixel type, then we decide whether or
207  *             not to toggle the state based on the values of the
208  *             path pixel and the path pixels above and below:
209  *               (1) if a 1 path cuts through, toggle;
210  *               (1) if a 2 path cuts through, toggle;
211  *               (3) if a 3 path cuts through, do not toggle;
212  *               (4) if on one side a 3 touches both a 1 and a 2, use the 2
213  *               (5) if a 3 has any 1 neighbors, toggle; else if it has
214  *                   no 1 neighbors, do not toggle;
215  *               (6) if a 2 has any neighbors that are 1 or 3,
216  *                   do not toggle
217  *               (7) if a 1 has neighbors 1 and x (x = 2 or 3),
218  *                   toggle
219  *
220  *
221  *     To visualize how these rules work, consider the following
222  *     component with border pixels labeled according to the scheme
223  *     above.  We also show the values of the interior pixels
224  *     (w=OFF, b=ON), but these of course must be inferred properly
225  *     from the rules above:
226  *
227  *                     3
228  *                  3  w  3             1  1  1
229  *                  1  2  1          1  b  2  b  1
230  *                  1  b  1             3  w  2  1
231  *                  3  b  1          1  b  2  b  1
232  *               3  w  3                1  1  1
233  *               3  w  3
234  *            1  b  2  b  1
235  *            1  2  w  2  1
236  *         1  b  2  w  2  b  1
237  *            1  2  w  2  1
238  *               1  2  b  1
239  *               1  b  1
240  *                  1
241  *
242  *
243  *     Even if this works, which is unlikely, it will certainly be
244  *     slow because decisions have to be made on a pixel-by-pixel
245  *     basis when encountering borders.
246  *
247  * </pre>
248  */
249 
250 #ifdef HAVE_CONFIG_H
251 #include "config_auto.h"
252 #endif  /* HAVE_CONFIG_H */
253 
254 #include <string.h>
255 #include "allheaders.h"
256 
257 static const l_int32  INITIAL_PTR_ARRAYSIZE = 20;    /* n'import quoi */
258 
259     /* In ccbaGenerateSinglePath(): don't save holes
260      * in c.c. with ridiculously many small holes   */
261 static const l_int32  NMAX_HOLES = 150;
262 
263     /*  Tables used to trace the border.
264      *   - The 8 pixel positions of neighbors Q are labeled clockwise
265      *     starting from the west:
266      *                  1   2   3
267      *                  0   P   4
268      *                  7   6   5
269      *     where the labels are the index offset [0, ... 7] of Q relative to P.
270      *   - xpostab[] and ypostab[] give the actual x and y pixel offsets
271      *     of Q relative to P, indexed by the index offset.
272      *   - qpostab[pos] gives the new index offset of Q relative to P, at
273      *     the time that a new P has been chosen to be in index offset
274      *     position 'pos' relative to the previous P.   The relation
275      *     between P and Q is always 4-connected.  */
276 static const l_int32   xpostab[] = {-1, -1, 0, 1, 1, 1, 0, -1};
277 static const l_int32   ypostab[] = {0, -1, -1, -1, 0, 1, 1, 1};
278 static const l_int32   qpostab[] = {6, 6, 0, 0, 2, 2, 4, 4};
279 
280     /* Static function */
281 static l_int32 ccbaExtendArray(CCBORDA  *ccba);
282 
283 #ifndef  NO_CONSOLE_IO
284 #define  DEBUG_PRINT   0
285 #endif   /* NO CONSOLE_IO */
286 
287 
288 /*---------------------------------------------------------------------*
289  *                   ccba and ccb creation and destruction             *
290  *---------------------------------------------------------------------*/
291 /*!
292  * \brief    ccbaCreate()
293  *
294  * \param[in]    pixs  binary image; can be null
295  * \param[in]    n  initial number of ptrs
296  * \return  ccba, or NULL on error
297  */
298 CCBORDA *
ccbaCreate(PIX * pixs,l_int32 n)299 ccbaCreate(PIX     *pixs,
300            l_int32  n)
301 {
302 CCBORDA  *ccba;
303 
304     PROCNAME("ccbaCreate");
305 
306     if (n <= 0)
307         n = INITIAL_PTR_ARRAYSIZE;
308 
309     ccba = (CCBORDA *)LEPT_CALLOC(1, sizeof(CCBORDA));
310     if (pixs) {
311         ccba->pix = pixClone(pixs);
312         ccba->w = pixGetWidth(pixs);
313         ccba->h = pixGetHeight(pixs);
314     }
315     ccba->n = 0;
316     ccba->nalloc = n;
317     if ((ccba->ccb = (CCBORD **)LEPT_CALLOC(n, sizeof(CCBORD *))) == NULL) {
318         ccbaDestroy(&ccba);
319         return (CCBORDA *)ERROR_PTR("ccba ptrs not made", procName, NULL);
320     }
321     return ccba;
322 }
323 
324 
325 /*!
326  * \brief   ccbaDestroy()
327  *
328  * \param[in,out]   pccba  to be nulled
329  * \return  void
330  */
331 void
ccbaDestroy(CCBORDA ** pccba)332 ccbaDestroy(CCBORDA  **pccba)
333 {
334 l_int32   i;
335 CCBORDA  *ccba;
336 
337     PROCNAME("ccbaDestroy");
338 
339     if (pccba == NULL) {
340         L_WARNING("ptr address is NULL!\n", procName);
341         return;
342     }
343 
344     if ((ccba = *pccba) == NULL)
345         return;
346 
347     pixDestroy(&ccba->pix);
348     for (i = 0; i < ccba->n; i++)
349         ccbDestroy(&ccba->ccb[i]);
350     LEPT_FREE(ccba->ccb);
351     LEPT_FREE(ccba);
352     *pccba = NULL;
353     return;
354 }
355 
356 
357 /*!
358  * \brief   ccbCreate()
359  *
360  * \param[in]    pixs  [optional]
361  * \return  ccb or NULL on error
362  */
363 CCBORD *
ccbCreate(PIX * pixs)364 ccbCreate(PIX  *pixs)
365 {
366 BOXA    *boxa;
367 CCBORD  *ccb;
368 PTA     *start;
369 PTAA    *local;
370 
371     PROCNAME("ccbCreate");
372 
373     if (pixs) {
374         if (pixGetDepth(pixs) != 1)
375             return (CCBORD *)ERROR_PTR("pixs not binary", procName, NULL);
376     }
377 
378     if ((ccb = (CCBORD *)LEPT_CALLOC(1, sizeof(CCBORD))) == NULL)
379         return (CCBORD *)ERROR_PTR("ccb not made", procName, NULL);
380     ccb->refcount++;
381     if (pixs)
382         ccb->pix = pixClone(pixs);
383     if ((boxa = boxaCreate(1)) == NULL)
384         return (CCBORD *)ERROR_PTR("boxa not made", procName, NULL);
385     ccb->boxa = boxa;
386     if ((start = ptaCreate(1)) == NULL)
387         return (CCBORD *)ERROR_PTR("start pta not made", procName, NULL);
388     ccb->start = start;
389     if ((local = ptaaCreate(1)) == NULL)
390         return (CCBORD *)ERROR_PTR("local ptaa not made", procName, NULL);
391     ccb->local = local;
392 
393     return ccb;
394 }
395 
396 
397 /*!
398  * \brief   ccbDestroy()
399  *
400  * \param[in,out]   pccb to be nulled
401  * \return  void
402  */
403 void
ccbDestroy(CCBORD ** pccb)404 ccbDestroy(CCBORD  **pccb)
405 {
406 CCBORD  *ccb;
407 
408     PROCNAME("ccbDestroy");
409 
410     if (pccb == NULL) {
411         L_WARNING("ptr address is NULL!\n", procName);
412         return;
413     }
414 
415     if ((ccb = *pccb) == NULL)
416         return;
417 
418     ccb->refcount--;
419     if (ccb->refcount == 0) {
420         if (ccb->pix)
421             pixDestroy(&ccb->pix);
422         if (ccb->boxa)
423             boxaDestroy(&ccb->boxa);
424         if (ccb->start)
425             ptaDestroy(&ccb->start);
426         if (ccb->local)
427             ptaaDestroy(&ccb->local);
428         if (ccb->global)
429             ptaaDestroy(&ccb->global);
430         if (ccb->step)
431             numaaDestroy(&ccb->step);
432         if (ccb->splocal)
433             ptaDestroy(&ccb->splocal);
434         if (ccb->spglobal)
435             ptaDestroy(&ccb->spglobal);
436         LEPT_FREE(ccb);
437         *pccb = NULL;
438     }
439     return;
440 }
441 
442 
443 /*---------------------------------------------------------------------*
444  *                            ccba addition                            *
445  *---------------------------------------------------------------------*/
446 /*!
447  * \brief   ccbaAddCcb()
448  *
449  * \param[in]    ccba
450  * \param[in]    ccb to be added by insertion
451  * \return  0 if OK; 1 on error
452  */
453 l_int32
ccbaAddCcb(CCBORDA * ccba,CCBORD * ccb)454 ccbaAddCcb(CCBORDA  *ccba,
455            CCBORD   *ccb)
456 {
457 l_int32  n;
458 
459     PROCNAME("ccbaAddCcb");
460 
461     if (!ccba)
462         return ERROR_INT("ccba not defined", procName, 1);
463     if (!ccb)
464         return ERROR_INT("ccb not defined", procName, 1);
465 
466     n = ccbaGetCount(ccba);
467     if (n >= ccba->nalloc)
468         ccbaExtendArray(ccba);
469     ccba->ccb[n] = ccb;
470     ccba->n++;
471     return 0;
472 }
473 
474 
475 /*!
476  * \brief   ccbaExtendArray()
477  *
478  * \param[in]    ccba
479  * \return  0 if OK; 1 on error
480  */
481 static l_int32
ccbaExtendArray(CCBORDA * ccba)482 ccbaExtendArray(CCBORDA  *ccba)
483 {
484     PROCNAME("ccbaExtendArray");
485 
486     if (!ccba)
487         return ERROR_INT("ccba not defined", procName, 1);
488 
489     if ((ccba->ccb = (CCBORD **)reallocNew((void **)&ccba->ccb,
490                                 sizeof(CCBORD *) * ccba->nalloc,
491                                 2 * sizeof(CCBORD *) * ccba->nalloc)) == NULL)
492         return ERROR_INT("new ptr array not returned", procName, 1);
493 
494     ccba->nalloc = 2 * ccba->nalloc;
495     return 0;
496 }
497 
498 
499 
500 /*---------------------------------------------------------------------*
501  *                            ccba accessors                           *
502  *---------------------------------------------------------------------*/
503 /*!
504  * \brief   ccbaGetCount()
505  *
506  * \param[in]    ccba
507  * \return  count, with 0 on error
508  */
509 l_int32
ccbaGetCount(CCBORDA * ccba)510 ccbaGetCount(CCBORDA  *ccba)
511 {
512 
513     PROCNAME("ccbaGetCount");
514 
515     if (!ccba)
516         return ERROR_INT("ccba not defined", procName, 0);
517 
518     return ccba->n;
519 }
520 
521 
522 /*!
523  * \brief   ccbaGetCcb()
524  *
525  * \param[in]    ccba
526  * \return  ccb, or NULL on error
527  *
528  * <pre>
529  * Notes:
530  *      (1) This returns a clone of the ccb; it must be destroyed
531  */
532 CCBORD *
ccbaGetCcb(CCBORDA * ccba,l_int32 index)533 ccbaGetCcb(CCBORDA  *ccba,
534            l_int32   index)
535 {
536 CCBORD  *ccb;
537 
538     PROCNAME("ccbaGetCcb");
539 
540     if (!ccba)
541         return (CCBORD *)ERROR_PTR("ccba not defined", procName, NULL);
542     if (index < 0 || index >= ccba->n)
543         return (CCBORD *)ERROR_PTR("index out of bounds", procName, NULL);
544 
545     ccb = ccba->ccb[index];
546     ccb->refcount++;
547     return ccb;
548 }
549 
550 
551 
552 /*---------------------------------------------------------------------*
553  *                   Top-level border-finding routines                 *
554  *---------------------------------------------------------------------*/
555 /*!
556  * \brief   pixGetAllCCBorders()
557  *
558  * \param[in]    pixs 1 bpp
559  * \return  ccborda, or NULL on error
560  */
561 CCBORDA *
pixGetAllCCBorders(PIX * pixs)562 pixGetAllCCBorders(PIX  *pixs)
563 {
564 l_int32   n, i;
565 BOX      *box;
566 BOXA     *boxa;
567 CCBORDA  *ccba;
568 CCBORD   *ccb;
569 PIX      *pix;
570 PIXA     *pixa;
571 
572     PROCNAME("pixGetAllCCBorders");
573 
574     if (!pixs)
575         return (CCBORDA *)ERROR_PTR("pixs not defined", procName, NULL);
576     if (pixGetDepth(pixs) != 1)
577         return (CCBORDA *)ERROR_PTR("pixs not binary", procName, NULL);
578 
579     if ((boxa = pixConnComp(pixs, &pixa, 8)) == NULL)
580         return (CCBORDA *)ERROR_PTR("boxa not made", procName, NULL);
581     n = boxaGetCount(boxa);
582 
583     if ((ccba = ccbaCreate(pixs, n)) == NULL) {
584         boxaDestroy(&boxa);
585         pixaDestroy(&pixa);
586         return (CCBORDA *)ERROR_PTR("ccba not made", procName, NULL);
587     }
588     for (i = 0; i < n; i++) {
589         if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL) {
590             ccbaDestroy(&ccba);
591             pixaDestroy(&pixa);
592             boxaDestroy(&boxa);
593             return (CCBORDA *)ERROR_PTR("pix not found", procName, NULL);
594         }
595         if ((box = pixaGetBox(pixa, i, L_CLONE)) == NULL) {
596             ccbaDestroy(&ccba);
597             pixaDestroy(&pixa);
598             boxaDestroy(&boxa);
599             pixDestroy(&pix);
600             return (CCBORDA *)ERROR_PTR("box not found", procName, NULL);
601         }
602         ccb = pixGetCCBorders(pix, box);
603         pixDestroy(&pix);
604         boxDestroy(&box);
605         if (!ccb) {
606             ccbaDestroy(&ccba);
607             pixaDestroy(&pixa);
608             boxaDestroy(&boxa);
609             return (CCBORDA *)ERROR_PTR("ccb not made", procName, NULL);
610         }
611 /*        ptaWriteStream(stderr, ccb->local, 1); */
612         ccbaAddCcb(ccba, ccb);
613     }
614 
615     boxaDestroy(&boxa);
616     pixaDestroy(&pixa);
617     return ccba;
618 }
619 
620 
621 /*!
622  * \brief   pixGetCCBorders()
623  *
624  * \param[in]    pixs 1 bpp, one 8-connected component
625  * \param[in]    box  xul, yul, width, height in global coords
626  * \return  ccbord, or NULL on error
627  *
628  * <pre>
629  * Notes:
630  *      (1) We are finding the exterior and interior borders
631  *          of an 8-connected component.   This should be used
632  *          on a pix that has exactly one 8-connected component.
633  *      (2) Typically, pixs is a c.c. in some larger pix.  The
634  *          input box gives its location in global coordinates.
635  *          This box is saved, as well as the boxes for the
636  *          borders of any holes within the c.c., but the latter
637  *          are given in relative coords within the c.c.
638  *      (3) The calculations for the exterior border are done
639  *          on a pix with a 1-pixel
640  *          added border, but the saved pixel coordinates
641  *          are the correct (relative) ones for the input pix
642  *          (without a 1-pixel border)
643  *      (4) For the definition of the three tables -- xpostab[], ypostab[]
644  *          and qpostab[] -- see above where they are defined.
645  * </pre>
646  */
647 CCBORD *
pixGetCCBorders(PIX * pixs,BOX * box)648 pixGetCCBorders(PIX      *pixs,
649                 BOX      *box)
650 {
651 l_int32   allzero, i, x, xh, w, nh;
652 l_int32   xs, ys;   /* starting hole border pixel, relative in pixs */
653 l_uint32  val;
654 BOX      *boxt, *boxe;
655 BOXA     *boxa;
656 CCBORD   *ccb;
657 PIX      *pixh;  /* for hole components */
658 PIX      *pixt;
659 PIXA     *pixa;
660 
661     PROCNAME("pixGetCCBorders");
662 
663     if (!pixs)
664         return (CCBORD *)ERROR_PTR("pixs not defined", procName, NULL);
665     if (!box)
666         return (CCBORD *)ERROR_PTR("box not defined", procName, NULL);
667     if (pixGetDepth(pixs) != 1)
668         return (CCBORD *)ERROR_PTR("pixs not binary", procName, NULL);
669 
670     pixZero(pixs, &allzero);
671     if (allzero)
672         return (CCBORD *)ERROR_PTR("pixs all 0", procName, NULL);
673 
674     if ((ccb = ccbCreate(pixs)) == NULL)
675         return (CCBORD *)ERROR_PTR("ccb not made", procName, NULL);
676 
677         /* Get the exterior border */
678     pixGetOuterBorder(ccb, pixs, box);
679 
680         /* Find the holes, if any */
681     if ((pixh = pixHolesByFilling(pixs, 4)) == NULL) {
682         ccbDestroy(&ccb);
683         return (CCBORD *)ERROR_PTR("pixh not made", procName, NULL);
684     }
685     pixZero(pixh, &allzero);
686     if (allzero) {  /* no holes */
687         pixDestroy(&pixh);
688         return ccb;
689     }
690 
691         /* Get c.c. and locations of the holes */
692     if ((boxa = pixConnComp(pixh, &pixa, 4)) == NULL) {
693         ccbDestroy(&ccb);
694         pixDestroy(&pixh);
695         return (CCBORD *)ERROR_PTR("boxa not made", procName, NULL);
696     }
697     nh = boxaGetCount(boxa);
698 /*    fprintf(stderr, "%d holes\n", nh); */
699 
700         /* For each hole, find an interior pixel within the hole,
701          * then march to the right and stop at the first border
702          * pixel.  Save the bounding box of the border, which
703          * is 1 pixel bigger on each side than the bounding box
704          * of the hole itself.  Note that we use a pix of the
705          * c.c. of the hole itself to be sure that we start
706          * with a pixel in the hole of the proper component.
707          * If we did everything from the parent component, it is
708          * possible to start in a different hole that is within
709          * the b.b. of a larger hole.  */
710     w = pixGetWidth(pixs);
711     for (i = 0; i < nh; i++) {
712         boxt = boxaGetBox(boxa, i, L_CLONE);
713         pixt = pixaGetPix(pixa, i, L_CLONE);
714         ys = boxt->y;   /* there must be a hole pixel on this raster line */
715         for (x = 0; x < boxt->w; x++) {  /* look for (fg) hole pixel */
716             pixGetPixel(pixt, x, 0, &val);
717             if (val == 1) {
718                 xh = x;
719                 break;
720             }
721         }
722         if (x == boxt->w) {
723             L_WARNING("no hole pixel found!\n", procName);
724             continue;
725         }
726         for (x = xh + boxt->x; x < w; x++) {  /* look for (fg) border pixel */
727             pixGetPixel(pixs, x, ys, &val);
728             if (val == 1) {
729                 xs = x;
730                 break;
731             }
732         }
733         boxe = boxCreate(boxt->x - 1, boxt->y - 1, boxt->w + 2, boxt->h + 2);
734 #if  DEBUG_PRINT
735         boxPrintStreamInfo(stderr, box);
736         boxPrintStreamInfo(stderr, boxe);
737         fprintf(stderr, "xs = %d, ys = %d\n", xs, ys);
738 #endif   /* DEBUG_PRINT */
739         pixGetHoleBorder(ccb, pixs, boxe, xs, ys);
740         boxDestroy(&boxt);
741         boxDestroy(&boxe);
742         pixDestroy(&pixt);
743     }
744 
745     boxaDestroy(&boxa);
746     pixaDestroy(&pixa);
747     pixDestroy(&pixh);
748     return ccb;
749 }
750 
751 
752 /*!
753  * \brief   pixGetOuterBordersPtaa()
754  *
755  * \param[in]    pixs 1 bpp
756  * \return  ptaa of outer borders, in global coords, or NULL on error
757  */
758 PTAA *
pixGetOuterBordersPtaa(PIX * pixs)759 pixGetOuterBordersPtaa(PIX  *pixs)
760 {
761 l_int32  i, n;
762 BOX     *box;
763 BOXA    *boxa;
764 PIX     *pix;
765 PIXA    *pixa;
766 PTA     *pta;
767 PTAA    *ptaa;
768 
769     PROCNAME("pixGetOuterBordersPtaa");
770 
771     if (!pixs)
772         return (PTAA *)ERROR_PTR("pixs not defined", procName, NULL);
773     if (pixGetDepth(pixs) != 1)
774         return (PTAA *)ERROR_PTR("pixs not binary", procName, NULL);
775 
776     boxa = pixConnComp(pixs, &pixa, 8);
777     n = boxaGetCount(boxa);
778     if (n == 0) {
779         boxaDestroy(&boxa);
780         pixaDestroy(&pixa);
781         return (PTAA *)ERROR_PTR("pixs empty", procName, NULL);
782     }
783 
784     ptaa = ptaaCreate(n);
785     for (i = 0; i < n; i++) {
786         box = boxaGetBox(boxa, i, L_CLONE);
787         pix = pixaGetPix(pixa, i, L_CLONE);
788         pta = pixGetOuterBorderPta(pix, box);
789         if (pta)
790             ptaaAddPta(ptaa, pta, L_INSERT);
791         boxDestroy(&box);
792         pixDestroy(&pix);
793     }
794 
795     pixaDestroy(&pixa);
796     boxaDestroy(&boxa);
797     return ptaa;
798 }
799 
800 
801 /*!
802  * \brief   pixGetOuterBorderPta()
803  *
804  * \param[in]    pixs 1 bpp, one 8-connected component
805  * \param[in]    box  [optional] of pixs, in global coordinates
806  * \return  pta of outer border, in global coords, or NULL on error
807  *
808  * <pre>
809  * Notes:
810  *      (1) We are finding the exterior border of a single 8-connected
811  *          component.
812  *      (2) If box is NULL, the outline returned is in the local coords
813  *          of the input pix.  Otherwise, box is assumed to give the
814  *          location of the pix in global coordinates, and the returned
815  *          pta will be in those global coordinates.
816  * </pre>
817  */
818 PTA *
pixGetOuterBorderPta(PIX * pixs,BOX * box)819 pixGetOuterBorderPta(PIX  *pixs,
820                      BOX  *box)
821 {
822 l_int32  allzero, x, y;
823 BOX     *boxt;
824 CCBORD  *ccb;
825 PTA     *ptaloc, *ptad;
826 
827     PROCNAME("pixGetOuterBorderPta");
828 
829     if (!pixs)
830         return (PTA *)ERROR_PTR("pixs not defined", procName, NULL);
831     if (pixGetDepth(pixs) != 1)
832         return (PTA *)ERROR_PTR("pixs not binary", procName, NULL);
833 
834     pixZero(pixs, &allzero);
835     if (allzero)
836         return (PTA *)ERROR_PTR("pixs all 0", procName, NULL);
837 
838     if ((ccb = ccbCreate(pixs)) == NULL)
839         return (PTA *)ERROR_PTR("ccb not made", procName, NULL);
840     if (!box)
841         boxt = boxCreate(0, 0, pixGetWidth(pixs), pixGetHeight(pixs));
842     else
843         boxt = boxClone(box);
844 
845         /* Get the exterior border in local coords */
846     pixGetOuterBorder(ccb, pixs, boxt);
847     if ((ptaloc = ptaaGetPta(ccb->local, 0, L_CLONE)) == NULL) {
848         ccbDestroy(&ccb);
849         boxDestroy(&boxt);
850         return (PTA *)ERROR_PTR("ptaloc not made", procName, NULL);
851     }
852 
853         /* Transform to global coordinates, if they are given */
854     if (box) {
855         boxGetGeometry(box, &x, &y, NULL, NULL);
856         ptad = ptaTransform(ptaloc, x, y, 1.0, 1.0);
857     } else {
858         ptad = ptaClone(ptaloc);
859     }
860 
861     ptaDestroy(&ptaloc);
862     boxDestroy(&boxt);
863     ccbDestroy(&ccb);
864     return ptad;
865 }
866 
867 
868 /*---------------------------------------------------------------------*
869  *                   Lower-level border-finding routines               *
870  *---------------------------------------------------------------------*/
871 /*!
872  * \brief   pixGetOuterBorder()
873  *
874  * \param[in]    ccb  unfilled
875  * \param[in]    pixs for the component at hand
876  * \param[in]    box  for the component, in global coords
877  * \return  0 if OK, 1 on error
878  *
879  * <pre>
880  * Notes:
881  *      (1) the border is saved in relative coordinates within
882  *          the c.c. (pixs).  Because the calculation is done
883  *          in pixb with added 1 pixel border, we must subtract
884  *          1 from each pixel value before storing it.
885  *      (2) the stopping condition is that after the first pixel is
886  *          returned to, the next pixel is the second pixel.  Having
887  *          these 2 pixels recur in sequence proves the path is closed,
888  *          and we do not store the second pixel again.
889  * </pre>
890  */
891 l_int32
pixGetOuterBorder(CCBORD * ccb,PIX * pixs,BOX * box)892 pixGetOuterBorder(CCBORD   *ccb,
893                   PIX      *pixs,
894                   BOX      *box)
895 {
896 l_int32    fpx, fpy, spx, spy, qpos;
897 l_int32    px, py, npx, npy;
898 l_int32    w, h, wpl;
899 l_uint32  *data;
900 PTA       *pta;
901 PIX       *pixb;  /* with 1 pixel border */
902 
903     PROCNAME("pixGetOuterBorder");
904 
905     if (!ccb)
906         return ERROR_INT("ccb not defined", procName, 1);
907     if (!pixs)
908         return ERROR_INT("pixs not defined", procName, 1);
909     if (!box)
910         return ERROR_INT("box not defined", procName, 1);
911 
912         /* Add 1-pixel border all around, and find start pixel */
913     if ((pixb = pixAddBorder(pixs, 1, 0)) == NULL)
914         return ERROR_INT("pixs not made", procName, 1);
915     if (!nextOnPixelInRaster(pixb, 1, 1, &px, &py)) {
916         pixDestroy(&pixb);
917         return ERROR_INT("no start pixel found", procName, 1);
918     }
919     qpos = 0;   /* relative to p */
920     fpx = px;  /* save location of first pixel on border */
921     fpy = py;
922 
923         /* Save box and start pixel in relative coords */
924     boxaAddBox(ccb->boxa, box, L_COPY);
925     ptaAddPt(ccb->start, px - 1, py - 1);
926 
927     pta = ptaCreate(0);
928     ptaaAddPta(ccb->local, pta, L_INSERT);
929     ptaAddPt(pta, px - 1, py - 1);   /* initial point */
930     pixGetDimensions(pixb, &w, &h, NULL);
931     data = pixGetData(pixb);
932     wpl = pixGetWpl(pixb);
933 
934         /* Get the second point; if there is none, return */
935     if (findNextBorderPixel(w, h, data, wpl, px, py, &qpos, &npx, &npy)) {
936         pixDestroy(&pixb);
937         return 0;
938     }
939 
940     spx = npx;  /* save location of second pixel on border */
941     spy = npy;
942     ptaAddPt(pta, npx - 1, npy - 1);   /* second point */
943     px = npx;
944     py = npy;
945 
946     while (1) {
947         findNextBorderPixel(w, h, data, wpl, px, py, &qpos, &npx, &npy);
948         if (px == fpx && py == fpy && npx == spx && npy == spy)
949             break;
950         ptaAddPt(pta, npx - 1, npy - 1);
951         px = npx;
952         py = npy;
953     }
954 
955     pixDestroy(&pixb);
956     return 0;
957 }
958 
959 
960 /*!
961  * \brief   pixGetHoleBorder()
962  *
963  * \param[in]    ccb  the exterior border is already made
964  * \param[in]    pixs for the connected component at hand
965  * \param[in]    box  for the specific hole border, in relative
966  *                    coordinates to the c.c.
967  * \param[in]    xs, ys   first pixel on hole border, relative to c.c.
968  * \return  0 if OK, 1 on error
969  *
970  * <pre>
971  * Notes:
972  *      (1) we trace out hole border on pixs without addition
973  *          of single pixel added border to pixs
974  *      (2) therefore all coordinates are relative within the c.c. (pixs)
975  *      (3) same position tables and stopping condition as for
976  *          exterior borders
977  * </pre>
978  */
979 l_int32
pixGetHoleBorder(CCBORD * ccb,PIX * pixs,BOX * box,l_int32 xs,l_int32 ys)980 pixGetHoleBorder(CCBORD   *ccb,
981                  PIX      *pixs,
982                  BOX      *box,
983                  l_int32   xs,
984                  l_int32   ys)
985 {
986 l_int32    fpx, fpy, spx, spy, qpos;
987 l_int32    px, py, npx, npy;
988 l_int32    w, h, wpl;
989 l_uint32  *data;
990 PTA       *pta;
991 
992     PROCNAME("pixGetHoleBorder");
993 
994     if (!ccb)
995         return ERROR_INT("ccb not defined", procName, 1);
996     if (!pixs)
997         return ERROR_INT("pixs not defined", procName, 1);
998     if (!box)
999         return ERROR_INT("box not defined", procName, 1);
1000 
1001         /* Add border and find start pixel */
1002     qpos = 0;   /* orientation of Q relative to P */
1003     fpx = xs;  /* save location of first pixel on border */
1004     fpy = ys;
1005 
1006         /* Save box and start pixel */
1007     boxaAddBox(ccb->boxa, box, L_COPY);
1008     ptaAddPt(ccb->start, xs, ys);
1009 
1010     if ((pta = ptaCreate(0)) == NULL)
1011         return ERROR_INT("pta not made", procName, 1);
1012     ptaaAddPta(ccb->local, pta, L_INSERT);
1013     ptaAddPt(pta, xs, ys);   /* initial pixel */
1014 
1015     w = pixGetWidth(pixs);
1016     h = pixGetHeight(pixs);
1017     data = pixGetData(pixs);
1018     wpl = pixGetWpl(pixs);
1019 
1020         /* Get the second point; there should always be at least 4 pts
1021          * in a minimal hole border!  */
1022     if (findNextBorderPixel(w, h, data, wpl, xs, ys, &qpos, &npx, &npy))
1023         return ERROR_INT("isolated hole border point!", procName, 1);
1024 
1025     spx = npx;  /* save location of second pixel on border */
1026     spy = npy;
1027     ptaAddPt(pta, npx, npy);   /* second pixel */
1028     px = npx;
1029     py = npy;
1030 
1031     while (1) {
1032         findNextBorderPixel(w, h, data, wpl, px, py, &qpos, &npx, &npy);
1033         if (px == fpx && py == fpy && npx == spx && npy == spy)
1034             break;
1035         ptaAddPt(pta, npx, npy);
1036         px = npx;
1037         py = npy;
1038     }
1039 
1040     return 0;
1041 }
1042 
1043 
1044 /*!
1045  * \brief   findNextBorderPixel()
1046  *
1047  * \param[in]       w, h, data, wpl
1048  * \param[in]       px, py      current P
1049  * \param[in,out]   pqpos       input current Q; new Q
1050  * \param[out]      pnpx, pnpy  new P
1051  * \return  0 if next pixel found; 1 otherwise
1052  *
1053  * <pre>
1054  * Notes:
1055  *      (1) qpos increases clockwise from 0 to 7, with 0 at
1056  *          location with Q to left of P:   Q P
1057  *      (2) this is a low-level function that does not check input
1058  *          parameters.  All calling functions should check them.
1059  * </pre>
1060  */
1061 l_int32
findNextBorderPixel(l_int32 w,l_int32 h,l_uint32 * data,l_int32 wpl,l_int32 px,l_int32 py,l_int32 * pqpos,l_int32 * pnpx,l_int32 * pnpy)1062 findNextBorderPixel(l_int32    w,
1063                     l_int32    h,
1064                     l_uint32  *data,
1065                     l_int32    wpl,
1066                     l_int32    px,
1067                     l_int32    py,
1068                     l_int32   *pqpos,
1069                     l_int32   *pnpx,
1070                     l_int32   *pnpy)
1071 {
1072 l_int32    qpos, i, pos, npx, npy, val;
1073 l_uint32  *line;
1074 
1075     qpos = *pqpos;
1076     for (i = 1; i < 8; i++) {
1077         pos = (qpos + i) % 8;
1078         npx = px + xpostab[pos];
1079         npy = py + ypostab[pos];
1080         line = data + npy * wpl;
1081         val = GET_DATA_BIT(line, npx);
1082         if (val) {
1083             *pnpx = npx;
1084             *pnpy = npy;
1085             *pqpos = qpostab[pos];
1086             return 0;
1087         }
1088     }
1089 
1090     return 1;
1091 }
1092 
1093 
1094 /*!
1095  * \brief   locateOutsideSeedPixel()
1096  *
1097  * \param[in]   fpx, fpy    location of first pixel
1098  * \param[in]   spx, spy    location of second pixel
1099  * \param[out]  pxs, pys    seed pixel to be returned
1100  *
1101  * <pre>
1102  * Notes:
1103  *      (1) the first and second pixels must be 8-adjacent,
1104  *          so |dx| <= 1 and |dy| <= 1 and both dx and dy
1105  *          cannot be 0.  There are 8 possible cases.
1106  *      (2) the seed pixel is OUTSIDE the foreground of the c.c.
1107  *      (3) these rules are for the situation where the INSIDE
1108  *          of the c.c. is on the right as you follow the border:
1109  *          cw for an exterior border and ccw for a hole border.
1110  * </pre>
1111  */
1112 void
locateOutsideSeedPixel(l_int32 fpx,l_int32 fpy,l_int32 spx,l_int32 spy,l_int32 * pxs,l_int32 * pys)1113 locateOutsideSeedPixel(l_int32   fpx,
1114                        l_int32   fpy,
1115                        l_int32   spx,
1116                        l_int32   spy,
1117                        l_int32  *pxs,
1118                        l_int32  *pys)
1119 {
1120 l_int32  dx, dy;
1121 
1122     dx = spx - fpx;
1123     dy = spy - fpy;
1124 
1125     if (dx * dy == 1) {
1126         *pxs = fpx + dx;
1127         *pys = fpy;
1128     } else if (dx * dy == -1) {
1129         *pxs = fpx;
1130         *pys = fpy + dy;
1131     } else if (dx == 0) {
1132         *pxs = fpx + dy;
1133         *pys = fpy + dy;
1134     } else  /* dy == 0 */ {
1135         *pxs = fpx + dx;
1136         *pys = fpy - dx;
1137     }
1138 
1139     return;
1140 }
1141 
1142 
1143 
1144 /*---------------------------------------------------------------------*
1145  *                            Border conversions                       *
1146  *---------------------------------------------------------------------*/
1147 /*!
1148  * \brief   ccbaGenerateGlobalLocs()
1149  *
1150  * \param[in]    ccba with local chain ptaa of borders computed
1151  * \return  0 if OK, 1 on error
1152  *
1153  *  Action: this uses the pixel locs in the local ptaa, which are all
1154  *          relative to each c.c., to find the global pixel locations,
1155  *          and stores them in the global ptaa.
1156  */
1157 l_int32
ccbaGenerateGlobalLocs(CCBORDA * ccba)1158 ccbaGenerateGlobalLocs(CCBORDA  *ccba)
1159 {
1160 l_int32  ncc, nb, n, i, j, k, xul, yul, x, y;
1161 CCBORD  *ccb;
1162 PTAA    *ptaal, *ptaag;
1163 PTA     *ptal, *ptag;
1164 
1165     PROCNAME("ccbaGenerateGlobalLocs");
1166 
1167     if (!ccba)
1168         return ERROR_INT("ccba not defined", procName, 1);
1169 
1170     ncc = ccbaGetCount(ccba);  /* number of c.c. */
1171     for (i = 0; i < ncc; i++) {
1172         ccb = ccbaGetCcb(ccba, i);
1173 
1174             /* Get the UL corner in global coords, (xul, yul), of the c.c. */
1175         boxaGetBoxGeometry(ccb->boxa, 0, &xul, &yul, NULL, NULL);
1176 
1177             /* Make a new global ptaa, removing any old one */
1178         ptaal = ccb->local;
1179         nb = ptaaGetCount(ptaal);   /* number of borders */
1180         if (ccb->global)   /* remove old one */
1181             ptaaDestroy(&ccb->global);
1182         if ((ptaag = ptaaCreate(nb)) == NULL)
1183             return ERROR_INT("ptaag not made", procName, 1);
1184         ccb->global = ptaag;  /* save new one */
1185 
1186             /* Iterate through the borders for this c.c. */
1187         for (j = 0; j < nb; j++) {
1188             ptal = ptaaGetPta(ptaal, j, L_CLONE);
1189             n = ptaGetCount(ptal);   /* number of pixels in border */
1190             if ((ptag = ptaCreate(n)) == NULL)
1191                 return ERROR_INT("ptag not made", procName, 1);
1192             ptaaAddPta(ptaag, ptag, L_INSERT);
1193             for (k = 0; k < n; k++) {
1194                 ptaGetIPt(ptal, k, &x, &y);
1195                 ptaAddPt(ptag, x  + xul, y + yul);
1196             }
1197             ptaDestroy(&ptal);
1198         }
1199         ccbDestroy(&ccb);
1200     }
1201 
1202     return 0;
1203 }
1204 
1205 
1206 /*!
1207  * \brief   ccbaGenerateStepChains()
1208  *
1209  * \param[in]    ccba with local chain ptaa of borders computed
1210  * \return  0 if OK, 1 on error
1211  *
1212  * <pre>
1213  * Notes:
1214  *      (1) This uses the pixel locs in the local ptaa,
1215  *          which are all relative to each c.c., to find
1216  *          the step directions for successive pixels in
1217  *          the chain, and stores them in the step numaa.
1218  *      (2) To get the step direction, use
1219  *              1   2   3
1220  *              0   P   4
1221  *              7   6   5
1222  *          where P is the previous pixel at (px, py).  The step direction
1223  *          is the number (from 0 through 7) for each relative location
1224  *          of the current pixel at (cx, cy).  It is easily found by
1225  *          indexing into a 2-d 3x3 array (dirtab).
1226  * </pre>
1227  */
1228 l_int32
ccbaGenerateStepChains(CCBORDA * ccba)1229 ccbaGenerateStepChains(CCBORDA  *ccba)
1230 {
1231 l_int32  ncc, nb, n, i, j, k;
1232 l_int32  px, py, cx, cy, stepdir;
1233 l_int32  dirtab[][3] = {{1, 2, 3}, {0, -1, 4}, {7, 6, 5}};
1234 CCBORD  *ccb;
1235 NUMA    *na;
1236 NUMAA   *naa;   /* step chain code; to be made */
1237 PTA     *ptal;
1238 PTAA    *ptaal;  /* local chain code */
1239 
1240     PROCNAME("ccbaGenerateStepChains");
1241 
1242     if (!ccba)
1243         return ERROR_INT("ccba not defined", procName, 1);
1244 
1245     ncc = ccbaGetCount(ccba);  /* number of c.c. */
1246     for (i = 0; i < ncc; i++) {
1247         ccb = ccbaGetCcb(ccba, i);
1248 
1249             /* Make a new step numaa, removing any old one */
1250         ptaal = ccb->local;
1251         nb = ptaaGetCount(ptaal);   /* number of borders */
1252         if (ccb->step)   /* remove old one */
1253             numaaDestroy(&ccb->step);
1254         if ((naa = numaaCreate(nb)) == NULL)
1255             return ERROR_INT("naa not made", procName, 1);
1256         ccb->step = naa;  /* save new one */
1257 
1258             /* Iterate through the borders for this c.c. */
1259         for (j = 0; j < nb; j++) {
1260             ptal = ptaaGetPta(ptaal, j, L_CLONE);
1261             n = ptaGetCount(ptal);   /* number of pixels in border */
1262             if (n == 1) {  /* isolated pixel */
1263                 na = numaCreate(1);   /* but leave it empty */
1264             } else {   /* trace out the boundary */
1265                 if ((na = numaCreate(n)) == NULL)
1266                     return ERROR_INT("na not made", procName, 1);
1267                 ptaGetIPt(ptal, 0, &px, &py);
1268                 for (k = 1; k < n; k++) {
1269                     ptaGetIPt(ptal, k, &cx, &cy);
1270                     stepdir = dirtab[1 + cy - py][1 + cx - px];
1271                     numaAddNumber(na, stepdir);
1272                     px = cx;
1273                     py = cy;
1274                 }
1275             }
1276             numaaAddNuma(naa, na, L_INSERT);
1277             ptaDestroy(&ptal);
1278         }
1279         ccbDestroy(&ccb);  /* just decrement refcount */
1280     }
1281 
1282     return 0;
1283 }
1284 
1285 
1286 /*!
1287  * \brief   ccbaStepChainsToPixCoords()
1288  *
1289  * \param[in]    ccba with step chains numaa of borders
1290  * \param[in]    coordtype  CCB_GLOBAL_COORDS or CCB_LOCAL_COORDS
1291  * \return  0 if OK, 1 on error
1292  *
1293  * <pre>
1294  * Notes:
1295  *      (1) This uses the step chain data in each ccb to determine
1296  *          the pixel locations, either global or local,
1297  *          and stores them in the appropriate ptaa,
1298  *          either global or local.  For the latter, the
1299  *          pixel locations are relative to the c.c.
1300  * </pre>
1301  */
1302 l_int32
ccbaStepChainsToPixCoords(CCBORDA * ccba,l_int32 coordtype)1303 ccbaStepChainsToPixCoords(CCBORDA  *ccba,
1304                           l_int32   coordtype)
1305 {
1306 l_int32  ncc, nb, n, i, j, k;
1307 l_int32  xul, yul, xstart, ystart, x, y, stepdir;
1308 BOXA    *boxa;
1309 CCBORD  *ccb;
1310 NUMA    *na;
1311 NUMAA   *naa;
1312 PTAA    *ptaan;  /* new pix coord ptaa */
1313 PTA     *ptas, *ptan;
1314 
1315     PROCNAME("ccbaStepChainsToPixCoords");
1316 
1317     if (!ccba)
1318         return ERROR_INT("ccba not defined", procName, 1);
1319     if (coordtype != CCB_GLOBAL_COORDS && coordtype != CCB_LOCAL_COORDS)
1320         return ERROR_INT("coordtype not valid", procName, 1);
1321 
1322     ncc = ccbaGetCount(ccba);  /* number of c.c. */
1323     for (i = 0; i < ncc; i++) {
1324         ccb = ccbaGetCcb(ccba, i);
1325         if ((naa = ccb->step) == NULL)
1326             return ERROR_INT("step numaa not found", procName, 1);
1327         if ((boxa = ccb->boxa) == NULL)
1328             return ERROR_INT("boxa not found", procName, 1);
1329         if ((ptas = ccb->start) == NULL)
1330             return ERROR_INT("start pta not found", procName, 1);
1331 
1332             /* For global coords, get the (xul, yul) of the c.c.;
1333              * otherwise, use relative coords. */
1334         if (coordtype == CCB_LOCAL_COORDS) {
1335             xul = 0;
1336             yul = 0;
1337         } else {  /* coordtype == CCB_GLOBAL_COORDS */
1338                 /* Get UL corner in global coords */
1339             if (boxaGetBoxGeometry(boxa, 0, &xul, &yul, NULL, NULL))
1340                 return ERROR_INT("bounding rectangle not found", procName, 1);
1341         }
1342 
1343             /* Make a new ptaa, removing any old one */
1344         nb = numaaGetCount(naa);   /* number of borders */
1345         if ((ptaan = ptaaCreate(nb)) == NULL)
1346             return ERROR_INT("ptaan not made", procName, 1);
1347         if (coordtype == CCB_LOCAL_COORDS) {
1348             if (ccb->local)   /* remove old one */
1349                 ptaaDestroy(&ccb->local);
1350             ccb->local = ptaan;  /* save new local chain */
1351         } else {   /* coordtype == CCB_GLOBAL_COORDS */
1352             if (ccb->global)   /* remove old one */
1353                 ptaaDestroy(&ccb->global);
1354             ccb->global = ptaan;  /* save new global chain */
1355         }
1356 
1357             /* Iterate through the borders for this c.c. */
1358         for (j = 0; j < nb; j++) {
1359             na = numaaGetNuma(naa, j, L_CLONE);
1360             n = numaGetCount(na);   /* number of steps in border */
1361             if ((ptan = ptaCreate(n + 1)) == NULL)
1362                 return ERROR_INT("ptan not made", procName, 1);
1363             ptaaAddPta(ptaan, ptan, L_INSERT);
1364             ptaGetIPt(ptas, j, &xstart, &ystart);
1365             x = xul + xstart;
1366             y = yul + ystart;
1367             ptaAddPt(ptan, x, y);
1368             for (k = 0; k < n; k++) {
1369                 numaGetIValue(na, k, &stepdir);
1370                 x += xpostab[stepdir];
1371                 y += ypostab[stepdir];
1372                 ptaAddPt(ptan, x, y);
1373             }
1374             numaDestroy(&na);
1375         }
1376         ccbDestroy(&ccb);
1377     }
1378 
1379     return 0;
1380 }
1381 
1382 
1383 /*!
1384  * \brief   ccbaGenerateSPGlobalLocs()
1385  *
1386  * \param[in]    ccba
1387  * \param[in]    ptsflag  CCB_SAVE_ALL_PTS or CCB_SAVE_TURNING_PTS
1388  * \return  0 if OK, 1 on error
1389  *
1390  * <pre>
1391  * Notes:
1392  *      (1) This calculates the splocal rep if not yet made.
1393  *      (2) It uses the local pixel values in splocal, the single
1394  *          path pta, which are all relative to each c.c., to find
1395  *          the corresponding global pixel locations, and stores
1396  *          them in the spglobal pta.
1397  *      (3) This lists only the turning points: it both makes a
1398  *          valid svg file and is typically about half the size
1399  *          when all border points are listed.
1400  * </pre>
1401  */
1402 l_int32
ccbaGenerateSPGlobalLocs(CCBORDA * ccba,l_int32 ptsflag)1403 ccbaGenerateSPGlobalLocs(CCBORDA  *ccba,
1404                          l_int32   ptsflag)
1405 {
1406 l_int32  ncc, npt, i, j, xul, yul, x, y, delx, dely;
1407 l_int32  xp, yp, delxp, delyp;   /* prev point and increments */
1408 CCBORD  *ccb;
1409 PTA     *ptal, *ptag;
1410 
1411     PROCNAME("ccbaGenerateSPGlobalLocs");
1412 
1413     if (!ccba)
1414         return ERROR_INT("ccba not defined", procName, 1);
1415 
1416         /* Make sure we have a local single path representation */
1417     if ((ccb = ccbaGetCcb(ccba, 0)) == NULL)
1418         return ERROR_INT("no ccb", procName, 1);
1419     if (!ccb->splocal)
1420         ccbaGenerateSinglePath(ccba);
1421     ccbDestroy(&ccb);  /* clone ref */
1422 
1423     ncc = ccbaGetCount(ccba);  /* number of c.c. */
1424     for (i = 0; i < ncc; i++) {
1425         ccb = ccbaGetCcb(ccba, i);
1426 
1427             /* Get the UL corner in global coords, (xul, yul), of the c.c. */
1428         if (boxaGetBoxGeometry(ccb->boxa, 0, &xul, &yul, NULL, NULL))
1429             return ERROR_INT("bounding rectangle not found", procName, 1);
1430 
1431             /* Make a new spglobal pta, removing any old one */
1432         ptal = ccb->splocal;
1433         npt = ptaGetCount(ptal);   /* number of points */
1434         if (ccb->spglobal)   /* remove old one */
1435             ptaDestroy(&ccb->spglobal);
1436         if ((ptag = ptaCreate(npt)) == NULL)
1437             return ERROR_INT("ptag not made", procName, 1);
1438         ccb->spglobal = ptag;  /* save new one */
1439 
1440             /* Convert local to global */
1441         if (ptsflag == CCB_SAVE_ALL_PTS) {
1442             for (j = 0; j < npt; j++) {
1443                 ptaGetIPt(ptal, j, &x, &y);
1444                 ptaAddPt(ptag, x  + xul, y + yul);
1445             }
1446         } else {   /* ptsflag = CCB_SAVE_TURNING_PTS */
1447             ptaGetIPt(ptal, 0, &xp, &yp);   /* get the 1st pt */
1448             ptaAddPt(ptag, xp  + xul, yp + yul);   /* save the 1st pt */
1449             if (npt == 2) {  /* get and save the 2nd pt  */
1450                 ptaGetIPt(ptal, 1, &x, &y);
1451                 ptaAddPt(ptag, x  + xul, y + yul);
1452             } else if (npt > 2)  {
1453                 ptaGetIPt(ptal, 1, &x, &y);
1454                 delxp = x - xp;
1455                 delyp = y - yp;
1456                 xp = x;
1457                 yp = y;
1458                 for (j = 2; j < npt; j++) {
1459                     ptaGetIPt(ptal, j, &x, &y);
1460                     delx = x - xp;
1461                     dely = y - yp;
1462                     if (delx != delxp || dely != delyp)
1463                         ptaAddPt(ptag, xp  + xul, yp + yul);
1464                     xp = x;
1465                     yp = y;
1466                     delxp = delx;
1467                     delyp = dely;
1468                 }
1469                 ptaAddPt(ptag, xp  + xul, yp + yul);
1470             }
1471         }
1472 
1473         ccbDestroy(&ccb);  /* clone ref */
1474     }
1475 
1476     return 0;
1477 }
1478 
1479 
1480 
1481 /*---------------------------------------------------------------------*
1482  *                       Conversion to single path                     *
1483  *---------------------------------------------------------------------*/
1484 /*!
1485  * \brief   ccbaGenerateSinglePath()
1486  *
1487  * \param[in]    ccba
1488  * \return  0 if OK, 1 on error
1489  *
1490  * <pre>
1491  * Notes:
1492  *      (1) Generates a single border in local pixel coordinates.
1493  *          For each c.c., if there is just an outer border, copy it.
1494  *          If there are also hole borders, for each hole border,
1495  *          determine the smallest horizontal or vertical
1496  *          distance from the border to the outside of the c.c.,
1497  *          and find a path through the c.c. for this cut.
1498  *          We do this in a way that guarantees a pixel from the
1499  *          hole border is the starting point of the path, and
1500  *          we must verify that the path intersects the outer
1501  *          border (if it intersects it, then it ends on it).
1502  *          One can imagine pathological cases, but they may not
1503  *          occur in images of text characters and un-textured
1504  *          line graphics.
1505  *      (2) Once it is verified that the path through the c.c.
1506  *          intersects both the hole and outer borders, we
1507  *          generate the full single path for all borders in the
1508  *          c.c.  Starting at the start point on the outer
1509  *          border, when we hit a line on a cut, we take
1510  *          the cut, do the hold border, and return on the cut
1511  *          to the outer border.  We compose a pta of the
1512  *          outer border pts that are on cut paths, and for
1513  *          every point on the outer border (as we go around),
1514  *          we check against this pta.  When we find a matching
1515  *          point in the pta, we do its cut path and hole border.
1516  *          The single path is saved in the ccb.
1517  * </pre>
1518  */
1519 l_int32
ccbaGenerateSinglePath(CCBORDA * ccba)1520 ccbaGenerateSinglePath(CCBORDA  *ccba)
1521 {
1522 l_int32   i, j, k, ncc, nb, ncut, npt, dir, len, state, lostholes;
1523 l_int32   x, y, xl, yl, xf, yf;
1524 BOX      *boxinner;
1525 BOXA     *boxa;
1526 CCBORD   *ccb;
1527 PTA      *pta, *ptac, *ptah;
1528 PTA      *ptahc;  /* cyclic permutation of hole border, with end pts at cut */
1529 PTA      *ptas;  /* output result: new single path for c.c. */
1530 PTA      *ptaf;  /* points on the hole borders that intersect with cuts */
1531 PTA      *ptal;  /* points on outer border that intersect with cuts */
1532 PTA      *ptap, *ptarp;   /* path and reverse path between borders */
1533 PTAA     *ptaa;
1534 PTAA     *ptaap;  /* ptaa for all paths between borders */
1535 
1536     PROCNAME("ccbaGenerateSinglePath");
1537 
1538     if (!ccba)
1539         return ERROR_INT("ccba not defined", procName, 1);
1540 
1541     ncc = ccbaGetCount(ccba);   /* number of c.c. */
1542     lostholes = 0;
1543     for (i = 0; i < ncc; i++) {
1544         ccb = ccbaGetCcb(ccba, i);
1545         if ((ptaa = ccb->local) == NULL) {
1546             L_WARNING("local pixel loc array not found\n", procName);
1547             continue;
1548         }
1549         nb = ptaaGetCount(ptaa);   /* number of borders in the c.c.  */
1550 
1551             /* Prepare the output pta */
1552         if (ccb->splocal)
1553             ptaDestroy(&ccb->splocal);
1554         ptas = ptaCreate(0);
1555         ccb->splocal = ptas;
1556 
1557             /* If no holes, just concat the outer border */
1558         pta = ptaaGetPta(ptaa, 0, L_CLONE);
1559         if (nb == 1 || nb > NMAX_HOLES + 1) {
1560             ptaJoin(ptas, pta, 0, -1);
1561             ptaDestroy(&pta);  /* remove clone */
1562             ccbDestroy(&ccb);  /* remove clone */
1563             continue;
1564         }
1565 
1566             /* Find the (nb - 1) cut paths that connect holes
1567              * with outer border */
1568         boxa = ccb->boxa;
1569         ptaap = ptaaCreate(nb - 1);
1570         ptaf = ptaCreate(nb - 1);
1571         ptal = ptaCreate(nb - 1);
1572         for (j = 1; j < nb; j++) {
1573             boxinner = boxaGetBox(boxa, j, L_CLONE);
1574 
1575                 /* Find a short path and store it */
1576             ptac = getCutPathForHole(ccb->pix, pta, boxinner, &dir, &len);
1577             if (len == 0) {  /* bad: we lose the hole! */
1578                 lostholes++;
1579 /*                boxPrintStreamInfo(stderr, boxa->box[0]); */
1580             }
1581             ptaaAddPta(ptaap, ptac, L_INSERT);
1582 /*            fprintf(stderr, "dir = %d, length = %d\n", dir, len); */
1583 /*            ptaWriteStream(stderr, ptac, 1); */
1584 
1585                 /* Store the first and last points in the cut path,
1586                  * which must be on a hole border and the outer
1587                  * border, respectively */
1588             ncut = ptaGetCount(ptac);
1589             if (ncut == 0) {   /* missed hole; neg coords won't match */
1590                 ptaAddPt(ptaf, -1, -1);
1591                 ptaAddPt(ptal, -1, -1);
1592             } else {
1593                 ptaGetIPt(ptac, 0, &x, &y);
1594                 ptaAddPt(ptaf, x, y);
1595                 ptaGetIPt(ptac, ncut - 1, &x, &y);
1596                 ptaAddPt(ptal, x, y);
1597             }
1598             boxDestroy(&boxinner);
1599         }
1600 
1601             /* Make a single path for the c.c. using these connections */
1602         npt = ptaGetCount(pta);  /* outer border pts */
1603         for (k = 0; k < npt; k++) {
1604             ptaGetIPt(pta, k, &x, &y);
1605             if (k == 0) {   /* if there is a cut at the first point,
1606                              * we can wait until the end to take it */
1607                 ptaAddPt(ptas, x, y);
1608                 continue;
1609             }
1610             state = L_NOT_FOUND;
1611             for (j = 0; j < nb - 1; j++) {  /* iterate over cut end pts */
1612                 ptaGetIPt(ptal, j, &xl, &yl);  /* cut point on outer border */
1613                 if (x == xl && y == yl) {  /* take this cut to the hole */
1614                     state = L_FOUND;
1615                     ptap = ptaaGetPta(ptaap, j, L_CLONE);
1616                     ptarp = ptaReverse(ptap, 1);
1617                         /* Cut point on hole border: */
1618                     ptaGetIPt(ptaf, j, &xf, &yf);
1619                         /* Hole border: */
1620                     ptah = ptaaGetPta(ptaa, j + 1, L_CLONE);
1621                     ptahc = ptaCyclicPerm(ptah, xf, yf);
1622 /*                    ptaWriteStream(stderr, ptahc, 1); */
1623                     ptaJoin(ptas, ptarp, 0, -1);
1624                     ptaJoin(ptas, ptahc, 0, -1);
1625                     ptaJoin(ptas, ptap, 0, -1);
1626                     ptaDestroy(&ptap);
1627                     ptaDestroy(&ptarp);
1628                     ptaDestroy(&ptah);
1629                     ptaDestroy(&ptahc);
1630                     break;
1631                 }
1632             }
1633             if (state == L_NOT_FOUND)
1634                 ptaAddPt(ptas, x, y);
1635         }
1636 
1637 /*        ptaWriteStream(stderr, ptas, 1); */
1638         ptaaDestroy(&ptaap);
1639         ptaDestroy(&ptaf);
1640         ptaDestroy(&ptal);
1641         ptaDestroy(&pta);  /* remove clone */
1642         ccbDestroy(&ccb);  /* remove clone */
1643     }
1644 
1645     if (lostholes > 0)
1646         L_WARNING("***** %d lost holes *****\n", procName, lostholes);
1647 
1648     return 0;
1649 }
1650 
1651 
1652 /*!
1653  * \brief   getCutPathForHole()
1654  *
1655  * \param[in]    pix  of c.c.
1656  * \param[in]    pta  of outer border
1657  * \param[in]    boxinner b.b. of hole path
1658  * \param[out]   pdir  direction (0-3), returned; only needed for debug
1659  * \param[out]   plen  length of path, returned
1660  * \return  pta of pts on cut path from the hole border
1661  *              to the outer border, including end points on
1662  *              both borders; or NULL on error
1663  *
1664  * <pre>
1665  * Notes:
1666  *      (1) If we don't find a path, we return a pta with no pts
1667  *          in it and len = 0.
1668  *      (2) The goal is to get a reasonably short path between the
1669  *          inner and outer borders, that goes entirely within the fg of
1670  *          the pix.  This function is cheap-and-dirty, may fail for some
1671  *          holes in complex topologies such as those you might find in a
1672  *          moderately dark scanned halftone.  If it fails to find a
1673  *          path to any particular hole, it gives a warning, and because
1674  *          that hole path is not included, the hole will not be rendered.
1675  * </pre>
1676  */
1677 PTA *
getCutPathForHole(PIX * pix,PTA * pta,BOX * boxinner,l_int32 * pdir,l_int32 * plen)1678 getCutPathForHole(PIX      *pix,
1679                   PTA      *pta,
1680                   BOX      *boxinner,
1681                   l_int32  *pdir,
1682                   l_int32  *plen)
1683 {
1684 l_int32   w, h, nc, x, y, xl, yl, xmid, ymid;
1685 l_uint32  val;
1686 PTA      *ptac;
1687 
1688     PROCNAME("getCutPathForHole");
1689 
1690     if (!pix)
1691         return (PTA *)ERROR_PTR("pix not defined", procName, NULL);
1692     if (!pta)
1693         return (PTA *)ERROR_PTR("pta not defined", procName, NULL);
1694     if (!boxinner)
1695         return (PTA *)ERROR_PTR("boxinner not defined", procName, NULL);
1696 
1697     w = pixGetWidth(pix);
1698     h = pixGetHeight(pix);
1699 
1700     if ((ptac = ptaCreate(4)) == NULL)
1701         return (PTA *)ERROR_PTR("ptac not made", procName, NULL);
1702     xmid = boxinner->x + boxinner->w / 2;
1703     ymid = boxinner->y + boxinner->h / 2;
1704 
1705         /* try top first */
1706     for (y = ymid; y >= 0; y--) {
1707         pixGetPixel(pix, xmid, y, &val);
1708         if (val == 1) {
1709             ptaAddPt(ptac, xmid, y);
1710             break;
1711         }
1712     }
1713     for (y = y - 1; y >= 0; y--) {
1714         pixGetPixel(pix, xmid, y, &val);
1715         if (val == 1)
1716             ptaAddPt(ptac, xmid, y);
1717         else
1718             break;
1719     }
1720     nc = ptaGetCount(ptac);
1721     ptaGetIPt(ptac, nc - 1, &xl, &yl);
1722     if (ptaContainsPt(pta, xl, yl)) {
1723         *pdir = 1;
1724         *plen = nc;
1725         return ptac;
1726     }
1727 
1728         /* Next try bottom */
1729     ptaEmpty(ptac);
1730     for (y = ymid; y < h; y++) {
1731         pixGetPixel(pix, xmid, y, &val);
1732         if (val == 1) {
1733             ptaAddPt(ptac, xmid, y);
1734             break;
1735         }
1736     }
1737     for (y = y + 1; y < h; y++) {
1738         pixGetPixel(pix, xmid, y, &val);
1739         if (val == 1)
1740             ptaAddPt(ptac, xmid, y);
1741         else
1742             break;
1743     }
1744     nc = ptaGetCount(ptac);
1745     ptaGetIPt(ptac, nc - 1, &xl, &yl);
1746     if (ptaContainsPt(pta, xl, yl)) {
1747         *pdir = 3;
1748         *plen = nc;
1749         return ptac;
1750     }
1751 
1752         /* Next try left */
1753     ptaEmpty(ptac);
1754     for (x = xmid; x >= 0; x--) {
1755         pixGetPixel(pix, x, ymid, &val);
1756         if (val == 1) {
1757             ptaAddPt(ptac, x, ymid);
1758             break;
1759         }
1760     }
1761     for (x = x - 1; x >= 0; x--) {
1762         pixGetPixel(pix, x, ymid, &val);
1763         if (val == 1)
1764             ptaAddPt(ptac, x, ymid);
1765         else
1766             break;
1767     }
1768     nc = ptaGetCount(ptac);
1769     ptaGetIPt(ptac, nc - 1, &xl, &yl);
1770     if (ptaContainsPt(pta, xl, yl)) {
1771         *pdir = 0;
1772         *plen = nc;
1773         return ptac;
1774     }
1775 
1776         /* Finally try right */
1777     ptaEmpty(ptac);
1778     for (x = xmid; x < w; x++) {
1779         pixGetPixel(pix, x, ymid, &val);
1780         if (val == 1) {
1781             ptaAddPt(ptac, x, ymid);
1782             break;
1783         }
1784     }
1785     for (x = x + 1; x < w; x++) {
1786         pixGetPixel(pix, x, ymid, &val);
1787         if (val == 1)
1788             ptaAddPt(ptac, x, ymid);
1789         else
1790             break;
1791     }
1792     nc = ptaGetCount(ptac);
1793     ptaGetIPt(ptac, nc - 1, &xl, &yl);
1794     if (ptaContainsPt(pta, xl, yl)) {
1795         *pdir = 2;
1796         *plen = nc;
1797         return ptac;
1798     }
1799 
1800         /* If we get here, we've failed! */
1801     ptaEmpty(ptac);
1802     L_WARNING("no path found\n", procName);
1803     *plen = 0;
1804     return ptac;
1805 }
1806 
1807 
1808 
1809 /*---------------------------------------------------------------------*
1810  *                            Border rendering                         *
1811  *---------------------------------------------------------------------*/
1812 /*!
1813  * \brief   ccbaDisplayBorder()
1814  *
1815  * \param[in]    ccba
1816  * \return  pix of border pixels, or NULL on error
1817  *
1818  * <pre>
1819  * Notes:
1820  *      (1) Uses global ptaa, which gives each border pixel in
1821  *          global coordinates, and must be computed in advance
1822  *          by calling ccbaGenerateGlobalLocs().
1823  * </pre>
1824  */
1825 PIX *
ccbaDisplayBorder(CCBORDA * ccba)1826 ccbaDisplayBorder(CCBORDA  *ccba)
1827 {
1828 l_int32  ncc, nb, n, i, j, k, x, y;
1829 CCBORD  *ccb;
1830 PIX     *pixd;
1831 PTAA    *ptaa;
1832 PTA     *pta;
1833 
1834     PROCNAME("ccbaDisplayBorder");
1835 
1836     if (!ccba)
1837         return (PIX *)ERROR_PTR("ccba not defined", procName, NULL);
1838 
1839     if ((pixd = pixCreate(ccba->w, ccba->h, 1)) == NULL)
1840         return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
1841     ncc = ccbaGetCount(ccba);   /* number of c.c. */
1842     for (i = 0; i < ncc; i++) {
1843         ccb = ccbaGetCcb(ccba, i);
1844         if ((ptaa = ccb->global) == NULL) {
1845             L_WARNING("global pixel loc array not found", procName);
1846             continue;
1847         }
1848         nb = ptaaGetCount(ptaa);   /* number of borders in the c.c.  */
1849         for (j = 0; j < nb; j++) {
1850             pta = ptaaGetPta(ptaa, j, L_CLONE);
1851             n = ptaGetCount(pta);   /* number of pixels in the border */
1852             for (k = 0; k < n; k++) {
1853                 ptaGetIPt(pta, k, &x, &y);
1854                 pixSetPixel(pixd, x, y, 1);
1855             }
1856             ptaDestroy(&pta);
1857         }
1858         ccbDestroy(&ccb);
1859     }
1860 
1861     return pixd;
1862 }
1863 
1864 
1865 /*!
1866  * \brief   ccbaDisplaySPBorder()
1867  *
1868  * \param[in]    ccba
1869  * \return  pix of border pixels, or NULL on error
1870  *
1871  * <pre>
1872  * Notes:
1873  *      (1) Uses spglobal pta, which gives each border pixel in
1874  *          global coordinates, one path per c.c., and must
1875  *          be computed in advance by calling ccbaGenerateSPGlobalLocs().
1876  * </pre>
1877  */
1878 PIX *
ccbaDisplaySPBorder(CCBORDA * ccba)1879 ccbaDisplaySPBorder(CCBORDA  *ccba)
1880 {
1881 l_int32  ncc, npt, i, j, x, y;
1882 CCBORD  *ccb;
1883 PIX     *pixd;
1884 PTA     *ptag;
1885 
1886     PROCNAME("ccbaDisplaySPBorder");
1887 
1888     if (!ccba)
1889         return (PIX *)ERROR_PTR("ccba not defined", procName, NULL);
1890 
1891     if ((pixd = pixCreate(ccba->w, ccba->h, 1)) == NULL)
1892         return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
1893     ncc = ccbaGetCount(ccba);   /* number of c.c. */
1894     for (i = 0; i < ncc; i++) {
1895         ccb = ccbaGetCcb(ccba, i);
1896         if ((ptag = ccb->spglobal) == NULL) {
1897             L_WARNING("spglobal pixel loc array not found\n", procName);
1898             continue;
1899         }
1900         npt = ptaGetCount(ptag);   /* number of pixels on path */
1901         for (j = 0; j < npt; j++) {
1902             ptaGetIPt(ptag, j, &x, &y);
1903             pixSetPixel(pixd, x, y, 1);
1904         }
1905         ccbDestroy(&ccb);  /* clone ref */
1906     }
1907 
1908     return pixd;
1909 }
1910 
1911 
1912 /*!
1913  * \brief   ccbaDisplayImage1()
1914  *
1915  * \param[in]    ccba
1916  * \return  pix of image, or NULL on error
1917  *
1918  * <pre>
1919  * Notes:
1920  *      (1) Uses local ptaa, which gives each border pixel in
1921  *          local coordinates, so the actual pixel positions must
1922  *          be computed using all offsets.
1923  *      (2) For the holes, use coordinates relative to the c.c.
1924  *      (3) This is slower than Method 2.
1925  *      (4) This uses topological properties (Method 1) to do scan
1926  *          conversion to raster
1927  *
1928  *  This algorithm deserves some commentary.
1929  *
1930  *  I first tried the following:
1931  *    ~ outer borders: 4-fill from outside, stopping at the
1932  *         border, using pixFillClosedBorders()
1933  *    ~ inner borders: 4-fill from outside, stopping again
1934  *         at the border, XOR with the border, and invert
1935  *         to get the hole.  This did not work, because if
1936  *         you have a hole border that looks like:
1937  *
1938  *                x x x x x x
1939  *                x          x
1940  *                x   x x x   x
1941  *                  x x o x   x
1942  *                      x     x
1943  *                      x     x
1944  *                        x x x
1945  *
1946  *         if you 4-fill from the outside, the pixel 'o' will
1947  *         not be filled!  XORing with the border leaves it OFF.
1948  *         Inverting then gives a single bad ON pixel that is not
1949  *         actually part of the hole.
1950  *
1951  *  So what you must do instead is 4-fill the holes from inside.
1952  *  You can do this from a seedfill, using a pix with the hole
1953  *  border as the filling mask.  But you need to start with a
1954  *  pixel inside the hole.  How is this determined?  The best
1955  *  way is from the contour.  We have a right-hand shoulder
1956  *  rule for inside (i.e., the filled region).   Take the
1957  *  first 2 pixels of the hole border, and compute dx and dy
1958  *  (second coord minus first coord:  dx = sx - fx, dy = sy - fy).
1959  *  There are 8 possibilities, depending on the values of dx and
1960  *  dy (which can each be -1, 0, and +1, but not both 0).
1961  *  These 8 cases can be broken into 4; see the simple algorithm below.
1962  *  Once you have an interior seed pixel, you fill from the seed,
1963  *  clipping with the hole border pix by filling into its invert.
1964  *
1965  *  You then successively XOR these interior filled components, in any order.
1966  * </pre>
1967  */
1968 PIX *
ccbaDisplayImage1(CCBORDA * ccba)1969 ccbaDisplayImage1(CCBORDA  *ccba)
1970 {
1971 l_int32  ncc, i, nb, n, j, k, x, y, xul, yul, xoff, yoff, w, h;
1972 l_int32  fpx, fpy, spx, spy, xs, ys;
1973 BOX     *box;
1974 BOXA    *boxa;
1975 CCBORD  *ccb;
1976 PIX     *pixd, *pixt, *pixh;
1977 PTAA    *ptaa;
1978 PTA     *pta;
1979 
1980     PROCNAME("ccbaDisplayImage1");
1981 
1982     if (!ccba)
1983         return (PIX *)ERROR_PTR("ccba not defined", procName, NULL);
1984 
1985     if ((pixd = pixCreate(ccba->w, ccba->h, 1)) == NULL)
1986         return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
1987     ncc = ccbaGetCount(ccba);
1988     for (i = 0; i < ncc; i++) {
1989         ccb = ccbaGetCcb(ccba, i);
1990         if ((boxa = ccb->boxa) == NULL) {
1991             pixDestroy(&pixd);
1992             return (PIX *)ERROR_PTR("boxa not found", procName, NULL);
1993         }
1994 
1995             /* Render border in pixt */
1996         if ((ptaa = ccb->local) == NULL) {
1997             L_WARNING("local chain array not found\n", procName);
1998             continue;
1999         }
2000 
2001         nb = ptaaGetCount(ptaa);   /* number of borders in the c.c.  */
2002         for (j = 0; j < nb; j++) {
2003             if ((box = boxaGetBox(boxa, j, L_CLONE)) == NULL) {
2004                 pixDestroy(&pixd);
2005                 return (PIX *)ERROR_PTR("b. box not found", procName, NULL);
2006             }
2007             if (j == 0) {
2008                 boxGetGeometry(box, &xul, &yul, &w, &h);
2009                 xoff = yoff = 0;
2010             } else {
2011                 boxGetGeometry(box, &xoff, &yoff, &w, &h);
2012             }
2013             boxDestroy(&box);
2014 
2015                 /* Render the border in a minimum-sized pix;
2016                  * subtract xoff and yoff because the pixel
2017                  * location is stored relative to the c.c., but
2018                  * we need it relative to just the hole border. */
2019             if ((pixt = pixCreate(w, h, 1)) == NULL) {
2020                 pixDestroy(&pixd);
2021                 return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
2022             }
2023             pta = ptaaGetPta(ptaa, j, L_CLONE);
2024             n = ptaGetCount(pta);   /* number of pixels in the border */
2025             for (k = 0; k < n; k++) {
2026                 ptaGetIPt(pta, k, &x, &y);
2027                 pixSetPixel(pixt, x - xoff, y - yoff, 1);
2028                 if (j > 0) {   /* need this for finding hole border pixel */
2029                     if (k == 0) {
2030                         fpx = x - xoff;
2031                         fpy = y - yoff;
2032                     }
2033                     if (k == 1) {
2034                         spx = x - xoff;
2035                         spy = y - yoff;
2036                     }
2037                 }
2038             }
2039             ptaDestroy(&pta);
2040 
2041                 /* Get the filled component */
2042             if (j == 0) {  /* if outer border, fill from outer boundary */
2043                 if ((pixh = pixFillClosedBorders(pixt, 4)) == NULL) {
2044                     pixDestroy(&pixd);
2045                     pixDestroy(&pixt);
2046                     return (PIX *)ERROR_PTR("pixh not made", procName, NULL);
2047                 }
2048             } else {   /* fill the hole from inside */
2049                     /* get the location of a seed pixel in the hole */
2050                 locateOutsideSeedPixel(fpx, fpy, spx, spy, &xs, &ys);
2051 
2052                     /* Put seed in hole and fill interior of hole,
2053                      * using pixt as clipping mask */
2054                 pixh = pixCreateTemplate(pixt);
2055                 pixSetPixel(pixh, xs, ys, 1);  /* put seed pixel in hole */
2056                 pixInvert(pixt, pixt);  /* to make filling mask */
2057                 pixSeedfillBinary(pixh, pixh, pixt, 4);  /* 4-fill hole */
2058             }
2059 
2060                 /* XOR into the dest */
2061             pixRasterop(pixd, xul + xoff, yul + yoff, w, h, PIX_XOR,
2062                         pixh, 0, 0);
2063             pixDestroy(&pixt);
2064             pixDestroy(&pixh);
2065         }
2066         ccbDestroy(&ccb);
2067     }
2068     return pixd;
2069 }
2070 
2071 
2072 
2073 /*!
2074  * \brief   ccbaDisplayImage2()
2075  *
2076  * \param[in]   ccba
2077  * \return  pix of image, or NULL on error
2078  *
2079  * <pre>
2080  * Notes:
2081  *      (1) Uses local chain ptaa, which gives each border pixel in
2082  *          local coordinates, so the actual pixel positions must
2083  *          be computed using all offsets.
2084  *      (2) Treats exterior and hole borders on equivalent
2085  *          footing, and does all calculations on a pix
2086  *          that spans the c.c. with a 1 pixel added boundary.
2087  *      (3) This uses topological properties (Method 2) to do scan
2088  *          conversion to raster
2089  *      (4) The algorithm is described at the top of this file (Method 2).
2090  *          It is preferred to Method 1 because it is between 1.2x and 2x
2091  *          faster than Method 1.
2092  * </pre>
2093  */
2094 PIX *
ccbaDisplayImage2(CCBORDA * ccba)2095 ccbaDisplayImage2(CCBORDA  *ccba)
2096 {
2097 l_int32  ncc, nb, n, i, j, k, x, y, xul, yul, w, h;
2098 l_int32  fpx, fpy, spx, spy, xs, ys;
2099 BOXA    *boxa;
2100 CCBORD  *ccb;
2101 PIX     *pixd, *pixc, *pixs;
2102 PTAA    *ptaa;
2103 PTA     *pta;
2104 
2105     PROCNAME("ccbaDisplayImage2");
2106 
2107     if (!ccba)
2108         return (PIX *)ERROR_PTR("ccba not defined", procName, NULL);
2109 
2110     if ((pixd = pixCreate(ccba->w, ccba->h, 1)) == NULL)
2111         return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
2112     ncc = ccbaGetCount(ccba);
2113     for (i = 0; i < ncc; i++) {
2114             /* Generate clipping mask from border pixels and seed image
2115              * from one seed for each closed border. */
2116         ccb = ccbaGetCcb(ccba, i);
2117         if ((boxa = ccb->boxa) == NULL) {
2118             pixDestroy(&pixd);
2119             ccbDestroy(&ccb);
2120             return (PIX *)ERROR_PTR("boxa not found", procName, NULL);
2121         }
2122         if (boxaGetBoxGeometry(boxa, 0, &xul, &yul, &w, &h)) {
2123             pixDestroy(&pixd);
2124             ccbDestroy(&ccb);
2125             return (PIX *)ERROR_PTR("b. box not found", procName, NULL);
2126         }
2127         pixc = pixCreate(w + 2, h + 2, 1);
2128         pixs = pixCreateTemplate(pixc);
2129 
2130         if ((ptaa = ccb->local) == NULL) {
2131             pixDestroy(&pixc);
2132             pixDestroy(&pixs);
2133             ccbDestroy(&ccb);
2134             L_WARNING("local chain array not found\n", procName);
2135             continue;
2136         }
2137         nb = ptaaGetCount(ptaa);   /* number of borders in the c.c.  */
2138         for (j = 0; j < nb; j++) {
2139             pta = ptaaGetPta(ptaa, j, L_CLONE);
2140             n = ptaGetCount(pta);   /* number of pixels in the border */
2141 
2142                 /* Render border pixels in pixc */
2143             for (k = 0; k < n; k++) {
2144                 ptaGetIPt(pta, k, &x, &y);
2145                 pixSetPixel(pixc, x + 1, y + 1, 1);
2146                 if (k == 0) {
2147                     fpx = x + 1;
2148                     fpy = y + 1;
2149                 } else if (k == 1) {
2150                     spx = x + 1;
2151                     spy = y + 1;
2152                 }
2153             }
2154 
2155                 /* Get and set seed pixel for this border in pixs */
2156             if (n > 1)
2157                 locateOutsideSeedPixel(fpx, fpy, spx, spy, &xs, &ys);
2158             else  /* isolated c.c. */
2159                 xs = ys = 0;
2160             pixSetPixel(pixs, xs, ys, 1);
2161             ptaDestroy(&pta);
2162         }
2163 
2164             /* Fill from seeds in pixs, using pixc as the clipping mask,
2165              * to reconstruct the c.c. */
2166         pixInvert(pixc, pixc);  /* to convert clipping -> filling mask */
2167         pixSeedfillBinary(pixs, pixs, pixc, 4);  /* 4-fill */
2168         pixInvert(pixs, pixs);  /* to make the c.c. */
2169 
2170             /* XOR into the dest */
2171         pixRasterop(pixd, xul, yul, w, h, PIX_XOR, pixs, 1, 1);
2172 
2173         pixDestroy(&pixc);
2174         pixDestroy(&pixs);
2175         ccbDestroy(&ccb);  /* ref-counted */
2176     }
2177     return pixd;
2178 }
2179 
2180 
2181 
2182 /*---------------------------------------------------------------------*
2183  *                            Serialize for I/O                        *
2184  *---------------------------------------------------------------------*/
2185 /*!
2186  * \brief   ccbaWrite()
2187  *
2188  * \param[in]    filename
2189  * \param[in]    ccba
2190  * \return  0 if OK, 1 on error
2191  */
2192 l_int32
ccbaWrite(const char * filename,CCBORDA * ccba)2193 ccbaWrite(const char  *filename,
2194           CCBORDA     *ccba)
2195 {
2196 FILE  *fp;
2197 
2198     PROCNAME("ccbaWrite");
2199 
2200     if (!filename)
2201         return ERROR_INT("filename not defined", procName, 1);
2202     if (!ccba)
2203         return ERROR_INT("ccba not defined", procName, 1);
2204 
2205     if ((fp = fopenWriteStream(filename, "wb+")) == NULL)
2206         return ERROR_INT("stream not opened", procName, 1);
2207     if (ccbaWriteStream(fp, ccba)) {
2208         fclose(fp);
2209         return ERROR_INT("ccba not written to stream", procName, 1);
2210     }
2211 
2212     fclose(fp);
2213     return 0;
2214 }
2215 
2216 
2217 
2218 /*!
2219  * \brief   ccbaWriteStream()
2220  *
2221  * \param[in]    fp file stream
2222  * \param[in]    ccba
2223  * \return  0 if OK; 1 on error
2224  *
2225  *  Format:
2226  * \code
2227  *           ccba: %7d cc\n num. c.c.) (ascii)   (18B
2228  *           pix width 4B
2229  *           pix height 4B
2230  *           [for i = 1, ncc]
2231  *               ulx  4B
2232  *               uly  4B
2233  *               w    4B       -- not req'd for reconstruction
2234  *               h    4B       -- not req'd for reconstruction
2235  *               number of borders 4B
2236  *               [for j = 1, nb]
2237  *                   startx  4B
2238  *                   starty  4B
2239  *                   [for k = 1, nb]
2240  *                        2 steps 1B
2241  *                   end in z8 or 88  1B
2242  * \endcode
2243  */
2244 l_int32
ccbaWriteStream(FILE * fp,CCBORDA * ccba)2245 ccbaWriteStream(FILE     *fp,
2246                 CCBORDA  *ccba)
2247 {
2248 char        strbuf[256];
2249 l_uint8     bval;
2250 l_uint8    *datain, *dataout;
2251 l_int32     i, j, k, bx, by, bw, bh, val, startx, starty;
2252 l_int32     ncc, nb, n;
2253 l_uint32    w, h;
2254 size_t      inbytes, outbytes;
2255 L_BBUFFER  *bbuf;
2256 CCBORD     *ccb;
2257 NUMA       *na;
2258 NUMAA      *naa;
2259 PTA        *pta;
2260 
2261     PROCNAME("ccbaWriteStream");
2262 
2263 #if  !HAVE_LIBZ  /* defined in environ.h */
2264     return ERROR_INT("no libz: can't write data", procName, 1);
2265 #else
2266 
2267     if (!fp)
2268         return ERROR_INT("stream not open", procName, 1);
2269     if (!ccba)
2270         return ERROR_INT("ccba not defined", procName, 1);
2271 
2272     if ((bbuf = bbufferCreate(NULL, 1000)) == NULL)
2273         return ERROR_INT("bbuf not made", procName, 1);
2274 
2275     ncc = ccbaGetCount(ccba);
2276     snprintf(strbuf, sizeof(strbuf), "ccba: %7d cc\n", ncc);
2277     bbufferRead(bbuf, (l_uint8 *)strbuf, 18);
2278     w = pixGetWidth(ccba->pix);
2279     h = pixGetHeight(ccba->pix);
2280     bbufferRead(bbuf, (l_uint8 *)&w, 4);  /* width */
2281     bbufferRead(bbuf, (l_uint8 *)&h, 4);  /* height */
2282     for (i = 0; i < ncc; i++) {
2283         ccb = ccbaGetCcb(ccba, i);
2284         if (boxaGetBoxGeometry(ccb->boxa, 0, &bx, &by, &bw, &bh)) {
2285             bbufferDestroy(&bbuf);
2286             return ERROR_INT("bounding box not found", procName, 1);
2287         }
2288         bbufferRead(bbuf, (l_uint8 *)&bx, 4);  /* ulx of c.c. */
2289         bbufferRead(bbuf, (l_uint8 *)&by, 4);  /* uly of c.c. */
2290         bbufferRead(bbuf, (l_uint8 *)&bw, 4);  /* w of c.c. */
2291         bbufferRead(bbuf, (l_uint8 *)&bh, 4);  /* h of c.c. */
2292         if ((naa = ccb->step) == NULL) {
2293             ccbaGenerateStepChains(ccba);
2294             naa = ccb->step;
2295         }
2296         nb = numaaGetCount(naa);
2297         bbufferRead(bbuf, (l_uint8 *)&nb, 4);  /* number of borders in c.c. */
2298         pta = ccb->start;
2299         for (j = 0; j < nb; j++) {
2300             ptaGetIPt(pta, j, &startx, &starty);
2301             bbufferRead(bbuf, (l_uint8 *)&startx, 4); /* starting x in border */
2302             bbufferRead(bbuf, (l_uint8 *)&starty, 4); /* starting y in border */
2303             na = numaaGetNuma(naa, j, L_CLONE);
2304             n = numaGetCount(na);
2305             for (k = 0; k < n; k++) {
2306                 numaGetIValue(na, k, &val);
2307                 if (k % 2 == 0)
2308                     bval = (l_uint8)val << 4;
2309                 else
2310                     bval |= (l_uint8)val;
2311                 if (k % 2 == 1)
2312                     bbufferRead(bbuf, (l_uint8 *)&bval, 1); /* 2 border steps */
2313             }
2314             if (n % 2 == 1) {
2315                 bval |= 0x8;
2316                 bbufferRead(bbuf, (l_uint8 *)&bval, 1); /* end with 0xz8,   */
2317                                              /* where z = {0..7} */
2318             } else {  /* n % 2 == 0 */
2319                 bval = 0x88;
2320                 bbufferRead(bbuf, (l_uint8 *)&bval, 1);   /* end with 0x88 */
2321             }
2322             numaDestroy(&na);
2323         }
2324         ccbDestroy(&ccb);
2325     }
2326 
2327     datain = bbufferDestroyAndSaveData(&bbuf, &inbytes);
2328     dataout = zlibCompress(datain, inbytes, &outbytes);
2329     fwrite(dataout, 1, outbytes, fp);
2330 
2331     LEPT_FREE(datain);
2332     LEPT_FREE(dataout);
2333     return 0;
2334 
2335 #endif  /* !HAVE_LIBZ */
2336 }
2337 
2338 
2339 /*!
2340  * \brief   ccbaRead()
2341  *
2342  * \param[in]    filename
2343  * \return  ccba, or NULL on error
2344  */
2345 CCBORDA *
ccbaRead(const char * filename)2346 ccbaRead(const char  *filename)
2347 {
2348 FILE     *fp;
2349 CCBORDA  *ccba;
2350 
2351     PROCNAME("ccbaRead");
2352 
2353     if (!filename)
2354         return (CCBORDA *)ERROR_PTR("filename not defined", procName, NULL);
2355 
2356     if ((fp = fopenReadStream(filename)) == NULL)
2357         return (CCBORDA *)ERROR_PTR("stream not opened", procName, NULL);
2358     ccba = ccbaReadStream(fp);
2359     fclose(fp);
2360 
2361     if (!ccba)
2362         return (CCBORDA *)ERROR_PTR("ccba not returned", procName, NULL);
2363     return ccba;
2364 }
2365 
2366 
2367 /*!
2368  * \brief   ccbaReadStream()
2369  *
2370  * \param[in]     fp file stream
2371  * \return   ccba, or NULL on error
2372  *
2373  * \code
2374  *  Format:  ccba: %7d cc\n num. c.c.) (ascii)   (17B
2375  *           pix width 4B
2376  *           pix height 4B
2377  *           [for i = 1, ncc]
2378  *               ulx  4B
2379  *               uly  4B
2380  *               w    4B       -- not req'd for reconstruction
2381  *               h    4B       -- not req'd for reconstruction
2382  *               number of borders 4B
2383  *               [for j = 1, nb]
2384  *                   startx  4B
2385  *                   starty  4B
2386  *                   [for k = 1, nb]
2387  *                        2 steps 1B
2388  *                   end in z8 or 88  1B
2389  * \endcode
2390  */
2391 CCBORDA *
ccbaReadStream(FILE * fp)2392 ccbaReadStream(FILE  *fp)
2393 {
2394 char      strbuf[256];
2395 l_uint8   bval;
2396 l_uint8  *datain, *dataout;
2397 l_int32   i, j, startx, starty;
2398 l_int32   offset, nib1, nib2;
2399 l_int32   ncc, nb;
2400 l_uint32  width, height, w, h, xoff, yoff;
2401 size_t    inbytes, outbytes;
2402 BOX      *box;
2403 CCBORD   *ccb;
2404 CCBORDA  *ccba;
2405 NUMA     *na;
2406 NUMAA    *step;
2407 
2408     PROCNAME("ccbaReadStream");
2409 
2410 #if  !HAVE_LIBZ  /* defined in environ.h */
2411     return (CCBORDA *)ERROR_PTR("no libz: can't read data", procName, NULL);
2412 #else
2413 
2414     if (!fp)
2415         return (CCBORDA *)ERROR_PTR("stream not open", procName, NULL);
2416 
2417     if ((datain = l_binaryReadStream(fp, &inbytes)) == NULL)
2418         return (CCBORDA *)ERROR_PTR("data not read from file", procName, NULL);
2419     dataout = zlibUncompress(datain, inbytes, &outbytes);
2420     LEPT_FREE(datain);
2421     if (!dataout)
2422         return (CCBORDA *)ERROR_PTR("dataout not made", procName, NULL);
2423 
2424     offset = 18;
2425     memcpy((void *)strbuf, (void *)dataout, offset);
2426     strbuf[17] = '\0';
2427     if (strncmp(strbuf, "ccba:", 5) != 0) {
2428         LEPT_FREE(dataout);
2429         return (CCBORDA *)ERROR_PTR("file not type ccba", procName, NULL);
2430     }
2431     sscanf(strbuf, "ccba: %7d cc\n", &ncc);
2432 /*    fprintf(stderr, "ncc = %d\n", ncc); */
2433     if ((ccba = ccbaCreate(NULL, ncc)) == NULL) {
2434         LEPT_FREE(dataout);
2435         return (CCBORDA *)ERROR_PTR("ccba not made", procName, NULL);
2436     }
2437 
2438     memcpy((void *)&width, (void *)(dataout + offset), 4);
2439     offset += 4;
2440     memcpy((void *)&height, (void *)(dataout + offset), 4);
2441     offset += 4;
2442     ccba->w = width;
2443     ccba->h = height;
2444 /*    fprintf(stderr, "width = %d, height = %d\n", width, height); */
2445 
2446     for (i = 0; i < ncc; i++) {  /* should be ncc */
2447         ccb = ccbCreate(NULL);
2448         ccbaAddCcb(ccba, ccb);
2449 
2450         memcpy((void *)&xoff, (void *)(dataout + offset), 4);
2451         offset += 4;
2452         memcpy((void *)&yoff, (void *)(dataout + offset), 4);
2453         offset += 4;
2454         memcpy((void *)&w, (void *)(dataout + offset), 4);
2455         offset += 4;
2456         memcpy((void *)&h, (void *)(dataout + offset), 4);
2457         offset += 4;
2458         box = boxCreate(xoff, yoff, w, h);
2459         boxaAddBox(ccb->boxa, box, L_INSERT);
2460 /*        fprintf(stderr, "xoff = %d, yoff = %d, w = %d, h = %d\n",
2461                 xoff, yoff, w, h); */
2462 
2463         memcpy((void *)&nb, (void *)(dataout + offset), 4);
2464         offset += 4;
2465 /*        fprintf(stderr, "num borders = %d\n", nb); */
2466         step = numaaCreate(nb);
2467         ccb->step = step;
2468 
2469         for (j = 0; j < nb; j++) {  /* should be nb */
2470             memcpy((void *)&startx, (void *)(dataout + offset), 4);
2471             offset += 4;
2472             memcpy((void *)&starty, (void *)(dataout + offset), 4);
2473             offset += 4;
2474             ptaAddPt(ccb->start, startx, starty);
2475 /*            fprintf(stderr, "startx = %d, starty = %d\n", startx, starty); */
2476             na = numaCreate(0);
2477             numaaAddNuma(step, na, L_INSERT);
2478 
2479             while(1) {
2480                 bval = *(dataout + offset);
2481                 offset++;
2482                 nib1 = (bval >> 4);
2483                 nib2 = bval & 0xf;
2484                 if (nib1 != 8)
2485                     numaAddNumber(na, nib1);
2486                 else
2487                     break;
2488                 if (nib2 != 8)
2489                     numaAddNumber(na, nib2);
2490                 else
2491                     break;
2492             }
2493         }
2494     }
2495     LEPT_FREE(dataout);
2496     return ccba;
2497 
2498 #endif  /* !HAVE_LIBZ */
2499 }
2500 
2501 
2502 /*---------------------------------------------------------------------*
2503  *                                SVG Output                           *
2504  *---------------------------------------------------------------------*/
2505 /*!
2506  * \brief   ccbaWriteSVG()
2507  *
2508  * \param[in]    filename
2509  * \param[in]    ccba
2510  * \return  0 if OK, 1 on error
2511  */
2512 l_int32
ccbaWriteSVG(const char * filename,CCBORDA * ccba)2513 ccbaWriteSVG(const char  *filename,
2514              CCBORDA     *ccba)
2515 {
2516 char  *svgstr;
2517 
2518     PROCNAME("ccbaWriteSVG");
2519 
2520     if (!filename)
2521         return ERROR_INT("filename not defined", procName, 1);
2522     if (!ccba)
2523         return ERROR_INT("ccba not defined", procName, 1);
2524 
2525     if ((svgstr = ccbaWriteSVGString(filename, ccba)) == NULL)
2526         return ERROR_INT("svgstr not made", procName, 1);
2527 
2528     l_binaryWrite(filename, "w", svgstr, strlen(svgstr));
2529     LEPT_FREE(svgstr);
2530 
2531     return 0;
2532 }
2533 
2534 
2535 /*!
2536  * \brief   ccbaWriteSVGString()
2537  *
2538  * \param[in]    filename
2539  * \param[in]    ccba
2540  * \return  string in svg-formatted, that can be written to file,
2541  *              or NULL on error.
2542  */
2543 char  *
ccbaWriteSVGString(const char * filename,CCBORDA * ccba)2544 ccbaWriteSVGString(const char  *filename,
2545                    CCBORDA     *ccba)
2546 {
2547 char    *svgstr;
2548 char     smallbuf[256];
2549 char     line0[] = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>";
2550 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\">";
2551 char     line2[] = "<svg>";
2552 char     line3[] = "<polygon style=\"stroke-width:1;stroke:black;\" points=\"";
2553 char     line4[] = "\" />";
2554 char     line5[] = "</svg>";
2555 char     space[] = " ";
2556 l_int32  i, j, ncc, npt, x, y;
2557 CCBORD  *ccb;
2558 PTA     *pta;
2559 SARRAY  *sa;
2560 
2561     PROCNAME("ccbaWriteSVGString");
2562 
2563     if (!filename)
2564         return (char *)ERROR_PTR("filename not defined", procName, NULL);
2565     if (!ccba)
2566         return (char *)ERROR_PTR("ccba not defined", procName, NULL);
2567 
2568     sa = sarrayCreate(0);
2569     sarrayAddString(sa, line0, L_COPY);
2570     sarrayAddString(sa, line1, L_COPY);
2571     sarrayAddString(sa, line2, L_COPY);
2572     ncc = ccbaGetCount(ccba);
2573     for (i = 0; i < ncc; i++) {
2574         if ((ccb = ccbaGetCcb(ccba, i)) == NULL) {
2575             sarrayDestroy(&sa);
2576             return (char *)ERROR_PTR("ccb not found", procName, NULL);
2577         }
2578         if ((pta = ccb->spglobal) == NULL) {
2579             sarrayDestroy(&sa);
2580             ccbDestroy(&ccb);
2581             return (char *)ERROR_PTR("spglobal not made", procName, NULL);
2582         }
2583         sarrayAddString(sa, line3, L_COPY);
2584         npt = ptaGetCount(pta);
2585         for (j = 0; j < npt; j++) {
2586             ptaGetIPt(pta, j, &x, &y);
2587             snprintf(smallbuf, sizeof(smallbuf), "%0d,%0d", x, y);
2588             sarrayAddString(sa, smallbuf, L_COPY);
2589         }
2590         sarrayAddString(sa, line4, L_COPY);
2591         ccbDestroy(&ccb);
2592     }
2593     sarrayAddString(sa, line5, L_COPY);
2594     sarrayAddString(sa, space, L_COPY);
2595 
2596     svgstr = sarrayToString(sa, 1);
2597 /*    fprintf(stderr, "%s", svgstr); */
2598 
2599     sarrayDestroy(&sa);
2600     return svgstr;
2601 }
2602