/*! * lheapSwapDown() * * Input: lh (heap) * Return: 0 if OK, 1 on error * * Notes: * (1) This is called after an item has been popped off the * root of the heap, and the last item in the heap has * been placed at the root. * (2) To regain the heap order, we let it bubble down, * iteratively swapping with one of its children. For a * decreasing sort, it swaps with the largest child; for * an increasing sort, the smallest. This continues until * it either reaches the lowest level in the heap, or the * parent finds that neither child should swap with it * (e.g., for a decreasing heap, the parent is larger * than or equal to both children). */ l_int32 lheapSwapDown(L_HEAP *lh) { l_int32 ip; /* index to heap for parent; 1 larger than array index */ l_int32 icr, icl; /* index into heap for left/right children */ l_float32 valp, valcl, valcr; PROCNAME("lheapSwapDown"); if (!lh) return ERROR_INT("lh not defined", procName, 1); if (lheapGetCount(lh) < 1) return 0; ip = 1; /* index into top of heap: corresponds to array[0] */ if (lh->direction == L_SORT_INCREASING) { while (1) { icl = 2 * ip; if (icl > lh->n) break; valp = *(l_float32 *)(lh->array[ip - 1]); valcl = *(l_float32 *)(lh->array[icl - 1]); icr = icl + 1; if (icr > lh->n) { /* only a left child; no iters below */ if (valp > valcl) SWAP_ITEMS(ip - 1, icl - 1); break; } else { /* both children exist; swap with the smallest if bigger */ valcr = *(l_float32 *)(lh->array[icr - 1]); if (valp <= valcl && valp <= valcr) /* smaller than both */ break; if (valcl <= valcr) { /* left smaller; swap */ SWAP_ITEMS(ip - 1, icl - 1); ip = icl; } else { /* right smaller; swap */ SWAP_ITEMS(ip - 1, icr - 1); ip = icr; } } } } else { /* lh->direction == L_SORT_DECREASING */ while (1) { icl = 2 * ip; if (icl > lh->n) break; valp = *(l_float32 *)(lh->array[ip - 1]); valcl = *(l_float32 *)(lh->array[icl - 1]); icr = icl + 1; if (icr > lh->n) { /* only a left child; no iters below */ if (valp < valcl) SWAP_ITEMS(ip - 1, icl - 1); break; } else { /* both children exist; swap with the biggest if smaller */ valcr = *(l_float32 *)(lh->array[icr - 1]); if (valp >= valcl && valp >= valcr) /* bigger than both */ break; if (valcl >= valcr) { /* left bigger; swap */ SWAP_ITEMS(ip - 1, icl - 1); ip = icl; } else { /* right bigger; swap */ SWAP_ITEMS(ip - 1, icr - 1); ip = icr; } } } } return 0; }
/*! * pixSearchGrayMaze() * * Input: pixs (1 bpp, maze) * xi, yi (beginning point; use same initial point * that was used to generate the maze) * xf, yf (end point, or close to it) * &ppixd (<optional return> maze with path illustrated, or * if no path possible, the part of the maze * that was searched) * Return: pta (shortest path), or null if either no path * exists or on error * * Commentary: * Consider first a slight generalization of the binary maze * search problem. Suppose that you can go through walls, * but the cost is higher (say, an increment of 3 to go into * a wall pixel rather than 1)? You're still trying to find * the shortest path. One way to do this is with an ordered * queue, and a simple way to visualize an ordered queue is as * a set of stacks, each stack being marked with the distance * of each pixel in the stack from the start. We place the * start pixel in stack 0, pop it, and process its 4 children. * Each pixel is given a distance that is incremented from that * of its parent (0 in this case), depending on if it is a wall * pixel or not. That value may be recorded on a distance map, * according to the algorithm below. For children of the first * pixel, those not on a wall go in stack 1, and wall * children go in stack 3. Stack 0 being emptied, the process * then continues with pixels being popped from stack 1. * Here is the algorithm for each child pixel. The pixel's * distance value, were it to be placed on a stack, is compared * with the value for it that is on the distance map. There * are three possible cases: * (1) If the pixel has not yet been registered, it is pushed * on its stack and the distance is written to the map. * (2) If it has previously been registered with a higher distance, * the distance on the map is relaxed to that of the * current pixel, which is then placed on its stack. * (3) If it has previously been registered with an equal * or lower value, the pixel is discarded. * The pixels are popped and processed successively from * stack 1, and when stack 1 is empty, popping starts on stack 2. * This continues until the destination pixel is popped off * a stack. The minimum path is then derived from the distance map, * going back from the end point as before. This is just Dijkstra's * algorithm for a directed graph; here, the underlying graph * (consisting of the pixels and four edges connecting each pixel * to its 4-neighbor) is a special case of a directed graph, where * each edge is bi-directional. The implementation of this generalized * maze search is left as an exercise to the reader. * * Let's generalize a bit further. Suppose the "maze" is just * a grayscale image -- think of it as an elevation map. The cost * of moving on this surface depends on the height, or the gradient, * or whatever you want. All that is required is that the cost * is specified and non-negative on each link between adjacent * pixels. Now the problem becomes: find the least cost path * moving on this surface between two specified end points. * For example, if the cost across an edge between two pixels * depends on the "gradient", you can use: * cost = 1 + L_ABS(deltaV) * where deltaV is the difference in value between two adjacent * pixels. If the costs are all integers, we can still use an array * of stacks to avoid ordering the queue (e.g., by using a heap sort.) * This is a neat problem, because you don't even have to build a * maze -- you can can use it on any grayscale image! * * Rather than using an array of stacks, a more practical * approach is to implement with a priority queue, which is * a queue that is sorted so that the elements with the largest * (or smallest) key values always come off first. The * priority queue is efficiently implemented as a heap, and * this is how we do it. Suppose you run the algorithm * using a priority queue, doing the bookkeeping with an * auxiliary image data structure that saves the distance of * each pixel put on the queue as before, according to the method * described above. We implement it as a 2-way choice by * initializing the distance array to a large value and putting * a pixel on the queue if its distance is less than the value * found on the array. When you finally pop the end pixel from * the queue, you're done, and you can trace the path backward, * either always going downhill or using an auxiliary image to * give you the direction to go at each step. This is implemented * here in searchGrayMaze(). * * Do we really have to use a sorted queue? Can we solve this * generalized maze with an unsorted queue of pixels? (Or even * an unsorted stack, doing a depth-first search (DFS)?) * Consider a different algorithm for this generalized maze, where * we travel again breadth first, but this time use a single, * unsorted queue. An auxiliary image is used as before to * store the distances and to determine if pixels get pushed * on the stack or dropped. As before, we must allow pixels * to be revisited, with relaxation of the distance if a shorter * path arrives later. As a result, we will in general have * multiple instances of the same pixel on the stack with different * distances. However, because the queue is not ordered, some of * these pixels will be popped when another instance with a lower * distance is still on the stack. Here, we're just popping them * in the order they go on, rather than setting up a priority * based on minimum distance. Thus, unlike the priority queue, * when a pixel is popped we have to check the distance map to * see if a pixel with a lower distance has been put on the queue, * and, if so, we discard the pixel we just popped. So the * "while" loop looks like this: * - pop a pixel from the queue * - check its distance against the distance stored in the * distance map; if larger, discard * - otherwise, for each of its neighbors: * - compute its distance from the start pixel * - compare this distance with that on the distance map: * - if the distance map value higher, relax the distance * and push the pixel on the queue * - if the distance map value is lower, discard the pixel * * How does this loop terminate? Before, with an ordered queue, * it terminates when you pop the end pixel. But with an unordered * queue (or stack), the first time you hit the end pixel, the * distance is not guaranteed to be correct, because the pixels * along the shortest path may not have yet been visited and relaxed. * Because the shortest path can theoretically go anywhere, * we must keep going. How do we know when to stop? Dijkstra * uses an ordered queue to systematically remove nodes from * further consideration. (Each time a pixel is popped, we're * done with it; it's "finalized" in the Dijkstra sense because * we know the shortest path to it.) However, with an unordered * queue, the brute force answer is: stop when the queue * (or stack) is empty, because then every pixel in the image * has been assigned its minimum "distance" from the start pixel. * * This is similar to the situation when you use a stack for the * simpler uniform-step problem: with breadth-first search (BFS) * the pixels on the queue are automatically ordered, so you are * done when you locate the end pixel as a neighbor of a popped pixel; * whereas depth-first search (DFS), using a stack, requires, * in general, a search of every accessible pixel. Further, if * a pixel is revisited with a smaller distance, that distance is * recorded and the pixel is put on the stack again. * * But surely, you ask, can't we stop sooner? What if the * start and end pixels are very close to each other? * OK, suppose they are, and you have very high walls and a * long snaking level path that is actually the minimum cost. * That long path can wind back and forth across the entire * maze many times before ending up at the end point, which * could be just over a wall from the start. With the unordered * queue, you very quickly get a high distance for the end * pixel, which will be relaxed to the minimum distance only * after all the pixels of the path have been visited and placed * on the queue, multiple times for many of them. So that's the * price for not ordering the queue! */ PTA * pixSearchGrayMaze(PIX *pixs, l_int32 xi, l_int32 yi, l_int32 xf, l_int32 yf, PIX **ppixd) { l_int32 x, y, w, h, d; l_uint32 val, valr, vals, rpixel, gpixel, bpixel; void **lines8, **liner32, **linep8; l_int32 cost, dist, distparent, sival, sivals; MAZEEL *el, *elp; PIX *pixd; /* optionally plot the path on this RGB version of pixs */ PIX *pixr; /* for bookkeeping, to indicate the minimum distance */ /* to pixels already visited */ PIX *pixp; /* for bookkeeping, to indicate direction to parent */ L_HEAP *lh; PTA *pta; PROCNAME("pixSearchGrayMaze"); if (ppixd) *ppixd = NULL; if (!pixs) return (PTA *)ERROR_PTR("pixs not defined", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); if (d != 8) return (PTA *)ERROR_PTR("pixs not 8 bpp", procName, NULL); if (xi <= 0 || xi >= w) return (PTA *)ERROR_PTR("xi not valid", procName, NULL); if (yi <= 0 || yi >= h) return (PTA *)ERROR_PTR("yi not valid", procName, NULL); pixd = NULL; pta = NULL; pixr = pixCreate(w, h, 32); pixSetAll(pixr); /* initialize to max value */ pixp = pixCreate(w, h, 8); /* direction to parent stored as enum val */ lines8 = pixGetLinePtrs(pixs, NULL); linep8 = pixGetLinePtrs(pixp, NULL); liner32 = pixGetLinePtrs(pixr, NULL); lh = lheapCreate(0, L_SORT_INCREASING); /* always remove closest pixels */ /* Prime the heap with the first pixel */ pixGetPixel(pixs, xi, yi, &val); el = mazeelCreate(xi, yi, 0); /* don't need direction here */ el->distance = 0; pixGetPixel(pixs, xi, yi, &val); el->val = val; pixSetPixel(pixr, xi, yi, 0); /* distance is 0 */ lheapAdd(lh, el); /* Breadth-first search with priority queue (implemented by a heap), labeling direction to parents in pixp and minimum distance to visited pixels in pixr. Stop when we pull the destination point (xf, yf) off the queue. */ while (lheapGetCount(lh) > 0) { elp = (MAZEEL *)lheapRemove(lh); if (!elp) return (PTA *)ERROR_PTR("heap broken!!", procName, NULL); x = elp->x; y = elp->y; if (x == xf && y == yf) { /* exit condition */ FREE(elp); break; } distparent = (l_int32)elp->distance; val = elp->val; sival = val; if (x > 0) { /* check to west */ vals = GET_DATA_BYTE(lines8[y], x - 1); valr = GET_DATA_FOUR_BYTES(liner32[y], x - 1); sivals = (l_int32)vals; cost = 1 + L_ABS(sivals - sival); /* cost to move to this pixel */ dist = distparent + cost; if (dist < valr) { /* shortest path so far to this pixel */ SET_DATA_FOUR_BYTES(liner32[y], x - 1, dist); /* new dist */ SET_DATA_BYTE(linep8[y], x - 1, DIR_EAST); /* parent to E */ el = mazeelCreate(x - 1, y, 0); el->val = vals; el->distance = dist; lheapAdd(lh, el); } } if (y > 0) { /* check north */ vals = GET_DATA_BYTE(lines8[y - 1], x); valr = GET_DATA_FOUR_BYTES(liner32[y - 1], x); sivals = (l_int32)vals; cost = 1 + L_ABS(sivals - sival); /* cost to move to this pixel */ dist = distparent + cost; if (dist < valr) { /* shortest path so far to this pixel */ SET_DATA_FOUR_BYTES(liner32[y - 1], x, dist); /* new dist */ SET_DATA_BYTE(linep8[y - 1], x, DIR_SOUTH); /* parent to S */ el = mazeelCreate(x, y - 1, 0); el->val = vals; el->distance = dist; lheapAdd(lh, el); } } if (x < w - 1) { /* check east */ vals = GET_DATA_BYTE(lines8[y], x + 1); valr = GET_DATA_FOUR_BYTES(liner32[y], x + 1); sivals = (l_int32)vals; cost = 1 + L_ABS(sivals - sival); /* cost to move to this pixel */ dist = distparent + cost; if (dist < valr) { /* shortest path so far to this pixel */ SET_DATA_FOUR_BYTES(liner32[y], x + 1, dist); /* new dist */ SET_DATA_BYTE(linep8[y], x + 1, DIR_WEST); /* parent to W */ el = mazeelCreate(x + 1, y, 0); el->val = vals; el->distance = dist; lheapAdd(lh, el); } } if (y < h - 1) { /* check south */ vals = GET_DATA_BYTE(lines8[y + 1], x); valr = GET_DATA_FOUR_BYTES(liner32[y + 1], x); sivals = (l_int32)vals; cost = 1 + L_ABS(sivals - sival); /* cost to move to this pixel */ dist = distparent + cost; if (dist < valr) { /* shortest path so far to this pixel */ SET_DATA_FOUR_BYTES(liner32[y + 1], x, dist); /* new dist */ SET_DATA_BYTE(linep8[y + 1], x, DIR_NORTH); /* parent to N */ el = mazeelCreate(x, y + 1, 0); el->val = vals; el->distance = dist; lheapAdd(lh, el); } } FREE(elp); } lheapDestroy(&lh, TRUE); if (ppixd) { pixd = pixConvert8To32(pixs); *ppixd = pixd; } composeRGBPixel(255, 0, 0, &rpixel); /* start point */ composeRGBPixel(0, 255, 0, &gpixel); composeRGBPixel(0, 0, 255, &bpixel); /* end point */ x = xf; y = yf; pta = ptaCreate(0); while (1) { /* write path onto pixd */ ptaAddPt(pta, x, y); if (x == xi && y == yi) break; if (pixd) pixSetPixel(pixd, x, y, gpixel); pixGetPixel(pixp, x, y, &val); if (val == DIR_NORTH) y--; else if (val == DIR_SOUTH) y++; else if (val == DIR_EAST) x++; else if (val == DIR_WEST) x--; pixGetPixel(pixr, x, y, &val); #if DEBUG_PATH fprintf(stderr, "(x,y) = (%d, %d); dist = %d\n", x, y, val); #endif /* DEBUG_PATH */ } if (pixd) { pixSetPixel(pixd, xi, yi, rpixel); pixSetPixel(pixd, xf, yf, bpixel); } pixDestroy(&pixp); pixDestroy(&pixr); FREE(lines8); FREE(linep8); FREE(liner32); return pta; }
/*! * wshedApply() * * Input: wshed (generated from wshedCreate()) * Return: 0 if OK, 1 on error * * Iportant note: * (1) This is buggy. It seems to locate watersheds that are * duplicates. The watershed extraction after complete fill * grabs some regions belonging to existing watersheds. * See prog/watershedtest.c for testing. */ l_int32 wshedApply(L_WSHED *wshed) { char two_new_watersheds[] = "Two new watersheds"; char seed_absorbed_into_seeded_basin[] = "Seed absorbed into seeded basin"; char one_new_watershed_label[] = "One new watershed (label)"; char one_new_watershed_index[] = "One new watershed (index)"; char minima_absorbed_into_seeded_basin[] = "Minima absorbed into seeded basin"; char minima_absorbed_by_filler_or_another[] = "Minima absorbed by filler or another"; l_int32 nseeds, nother, nboth, arraysize; l_int32 i, j, val, x, y, w, h, index, mindepth; l_int32 imin, imax, jmin, jmax, cindex, clabel, nindex; l_int32 hindex, hlabel, hmin, hmax, minhindex, maxhindex; l_int32 *lut; l_uint32 ulabel, uval; void **lines8, **linelab32; NUMA *nalut, *nalevels, *nash, *namh, *nasi; NUMA **links; L_HEAP *lh; PIX *pixmin, *pixsd; PIXA *pixad; L_STACK *rstack; PTA *ptas, *ptao; PROCNAME("wshedApply"); if (!wshed) return ERROR_INT("wshed not defined", procName, 1); /* ------------------------------------------------------------ * * Initialize priority queue and pixlab with seeds and minima * * ------------------------------------------------------------ */ lh = lheapCreate(0, L_SORT_INCREASING); /* remove lowest values first */ rstack = lstackCreate(0); /* for reusing the WSPixels */ pixGetDimensions(wshed->pixs, &w, &h, NULL); lines8 = wshed->lines8; /* wshed owns this */ linelab32 = wshed->linelab32; /* ditto */ /* Identify seed (marker) pixels, 1 for each c.c. in pixm */ pixSelectMinInConnComp(wshed->pixs, wshed->pixm, &ptas, &nash); pixsd = pixGenerateFromPta(ptas, w, h); nseeds = ptaGetCount(ptas); for (i = 0; i < nseeds; i++) { ptaGetIPt(ptas, i, &x, &y); uval = GET_DATA_BYTE(lines8[y], x); pushWSPixel(lh, rstack, (l_int32) uval, x, y, i); } wshed->ptas = ptas; nasi = numaMakeConstant(1, nseeds); /* indicator array */ wshed->nasi = nasi; wshed->nash = nash; wshed->nseeds = nseeds; /* Identify minima that are not seeds. Use these 4 steps: * (1) Get the local minima, which can have components * of arbitrary size. This will be a clipping mask. * (2) Get the image of the actual seeds (pixsd) * (3) Remove all elements of the clipping mask that have a seed. * (4) Shrink each of the remaining elements of the minima mask * to a single pixel. */ pixLocalExtrema(wshed->pixs, 200, 0, &pixmin, NULL); pixRemoveSeededComponents(pixmin, pixsd, pixmin, 8, 2); pixSelectMinInConnComp(wshed->pixs, pixmin, &ptao, &namh); nother = ptaGetCount(ptao); for (i = 0; i < nother; i++) { ptaGetIPt(ptao, i, &x, &y); uval = GET_DATA_BYTE(lines8[y], x); pushWSPixel(lh, rstack, (l_int32) uval, x, y, nseeds + i); } wshed->namh = namh; /* ------------------------------------------------------------ * * Initialize merging lookup tables * * ------------------------------------------------------------ */ /* nalut should always give the current after-merging index. * links are effectively backpointers: they are numas associated with * a dest index of all indices in nalut that point to that index. */ mindepth = wshed->mindepth; nboth = nseeds + nother; arraysize = 2 * nboth; wshed->arraysize = arraysize; nalut = numaMakeSequence(0, 1, arraysize); lut = numaGetIArray(nalut); wshed->lut = lut; /* wshed owns this */ links = (NUMA **) CALLOC(arraysize, sizeof(NUMA * )); wshed->links = links; /* wshed owns this */ nindex = nseeds + nother; /* the next unused index value */ /* ------------------------------------------------------------ * * Fill the basins, using the priority queue * * ------------------------------------------------------------ */ pixad = pixaCreate(nseeds); wshed->pixad = pixad; /* wshed owns this */ nalevels = numaCreate(nseeds); wshed->nalevels = nalevels; /* wshed owns this */ L_INFO("nseeds = %d, nother = %d\n", procName, nseeds, nother); while (lheapGetCount(lh) > 0) { popWSPixel(lh, rstack, &val, &x, &y, &index); /* fprintf(stderr, "x = %d, y = %d, index = %d\n", x, y, index); */ ulabel = GET_DATA_FOUR_BYTES(linelab32[y], x); if (ulabel == MAX_LABEL_VALUE) clabel = ulabel; else clabel = lut[ulabel]; cindex = lut[index]; if (clabel == cindex) continue; /* have already seen this one */ if (clabel == MAX_LABEL_VALUE) { /* new one; assign index and try to * propagate to all neighbors */ SET_DATA_FOUR_BYTES(linelab32[y], x, cindex); imin = L_MAX(0, y - 1); imax = L_MIN(h - 1, y + 1); jmin = L_MAX(0, x - 1); jmax = L_MIN(w - 1, x + 1); for (i = imin; i <= imax; i++) { for (j = jmin; j <= jmax; j++) { if (i == y && j == x) continue; uval = GET_DATA_BYTE(lines8[i], j); pushWSPixel(lh, rstack, (l_int32) uval, j, i, cindex); } } } else { /* pixel is already labeled (differently); must resolve */ /* If both indices are seeds, check if the min height is * greater than mindepth. If so, we have two new watersheds; * locate them and assign to both regions a new index * for further waterfill. If not, absorb the shallower * watershed into the deeper one and continue filling it. */ pixGetPixel(pixsd, x, y, &uval); if (clabel < nseeds && cindex < nseeds) { wshedGetHeight(wshed, val, clabel, &hlabel); wshedGetHeight(wshed, val, cindex, &hindex); hmin = L_MIN(hlabel, hindex); hmax = L_MAX(hlabel, hindex); if (hmin == hmax) { hmin = hlabel; hmax = hindex; } if (wshed->debug) { fprintf(stderr, "clabel,hlabel = %d,%d\n", clabel, hlabel); fprintf(stderr, "hmin = %d, hmax = %d\n", hmin, hmax); fprintf(stderr, "cindex,hindex = %d,%d\n", cindex, hindex); if (hmin < mindepth) fprintf(stderr, "Too shallow!\n"); } if (hmin >= mindepth) { debugWshedMerge(wshed, two_new_watersheds, x, y, clabel, cindex); wshedSaveBasin(wshed, cindex, val - 1); wshedSaveBasin(wshed, clabel, val - 1); numaSetValue(nasi, cindex, 0); numaSetValue(nasi, clabel, 0); if (wshed->debug) fprintf(stderr, "nindex = %d\n", nindex); debugPrintLUT(lut, nindex, wshed->debug); mergeLookup(wshed, clabel, nindex); debugPrintLUT(lut, nindex, wshed->debug); mergeLookup(wshed, cindex, nindex); debugPrintLUT(lut, nindex, wshed->debug); nindex++; } else /* extraneous seed within seeded basin; absorb */ { debugWshedMerge(wshed, seed_absorbed_into_seeded_basin, x, y, clabel, cindex); } maxhindex = clabel; /* TODO: is this part of above 'else'? */ minhindex = cindex; if (hindex > hlabel) { maxhindex = cindex; minhindex = clabel; } mergeLookup(wshed, minhindex, maxhindex); } else if (clabel < nseeds && cindex >= nboth) { /* If one index is a seed and the other is a merge of * 2 watersheds, generate a single watershed. */ debugWshedMerge(wshed, one_new_watershed_label, x, y, clabel, cindex); wshedSaveBasin(wshed, clabel, val - 1); numaSetValue(nasi, clabel, 0); mergeLookup(wshed, clabel, cindex); } else if (cindex < nseeds && clabel >= nboth) { debugWshedMerge(wshed, one_new_watershed_index, x, y, clabel, cindex); wshedSaveBasin(wshed, cindex, val - 1); numaSetValue(nasi, cindex, 0); mergeLookup(wshed, cindex, clabel); } else if (clabel < nseeds) { /* cindex from minima; absorb */ /* If one index is a seed and the other is from a minimum, * merge the minimum wshed into the seed wshed. */ debugWshedMerge(wshed, minima_absorbed_into_seeded_basin, x, y, clabel, cindex); mergeLookup(wshed, cindex, clabel); } else if (cindex < nseeds) { /* clabel from minima; absorb */ debugWshedMerge(wshed, minima_absorbed_into_seeded_basin, x, y, clabel, cindex); mergeLookup(wshed, clabel, cindex); } else { /* If neither index is a seed, just merge */ debugWshedMerge(wshed, minima_absorbed_by_filler_or_another, x, y, clabel, cindex); mergeLookup(wshed, clabel, cindex); } } } #if 0 /* Use the indicator array to save any watersheds that fill * to the maximum value. This seems to screw things up! */ for (i = 0; i < nseeds; i++) { numaGetIValue(nasi, i, &ival); if (ival == 1) { wshedSaveBasin(wshed, lut[i], val - 1); numaSetValue(nasi, i, 0); } } #endif numaDestroy(&nalut); pixDestroy(&pixmin); pixDestroy(&pixsd); ptaDestroy(&ptao); lheapDestroy(&lh, TRUE); lstackDestroy(&rstack, TRUE); return 0; }