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