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