Coverage Report

Created: 2026-09-14 06:59

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