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