/*! * generateBinaryMaze() * * Input: w, h (size of maze) * xi, yi (initial location) * wallps (probability that a pixel to the side is ON) * ranis (ratio of prob that pixel in forward direction * is a wall to the probability that pixel in * side directions is a wall) * Return: pix, or null on error * * Notes: * (1) We have two input probability factors that determine the * density of walls and average length of straight passages. * When ranis < 1.0, you are more likely to generate a wall * to the side than going forward. Enter 0.0 for either if * you want to use the default values. * (2) This is a type of percolation problem, and exhibits * different phases for different parameters wallps and ranis. * For larger values of these parameters, regions in the maze * are not explored because the maze generator walls them * off and cannot get through. The boundary between the * two phases in this two-dimensional parameter space goes * near these values: * wallps ranis * 0.35 1.00 * 0.40 0.85 * 0.45 0.70 * 0.50 0.50 * 0.55 0.40 * 0.60 0.30 * 0.65 0.25 * 0.70 0.19 * 0.75 0.15 * 0.80 0.11 * (3) Because here is a considerable amount of overhead in calling * pixGetPixel() and pixSetPixel(), this function can be sped * up with little effort using raster line pointers and the * GET_DATA* and SET_DATA* macros. */ PIX * generateBinaryMaze(l_int32 w, l_int32 h, l_int32 xi, l_int32 yi, l_float32 wallps, l_float32 ranis) { l_int32 x, y, dir; l_uint32 val; l_float32 frand, wallpf, testp; MAZEEL *el, *elp; PIX *pixd; /* the destination maze */ PIX *pixm; /* for bookkeeping, to indicate pixels already visited */ L_QUEUE *lq; /* On Windows, seeding is apparently necessary to get decent mazes. * Windows rand() returns a value up to 2^15 - 1, whereas unix * rand() returns a value up to 2^31 - 1. Therefore the generated * mazes will differ on the two platforms. */ #ifdef _WIN32 srand(28*333); #endif /* _WIN32 */ if (w < MIN_MAZE_WIDTH) w = MIN_MAZE_WIDTH; if (h < MIN_MAZE_HEIGHT) h = MIN_MAZE_HEIGHT; if (xi <= 0 || xi >= w) xi = w / 6; if (yi <= 0 || yi >= h) yi = h / 5; if (wallps < 0.05 || wallps > 0.95) wallps = DEFAULT_WALL_PROBABILITY; if (ranis < 0.05 || ranis > 1.0) ranis = DEFAULT_ANISOTROPY_RATIO; wallpf = wallps * ranis; #if DEBUG_MAZE fprintf(stderr, "(w, h) = (%d, %d), (xi, yi) = (%d, %d)\n", w, h, xi, yi); fprintf(stderr, "Using: prob(wall) = %7.4f, anisotropy factor = %7.4f\n", wallps, ranis); #endif /* DEBUG_MAZE */ /* These are initialized to OFF */ pixd = pixCreate(w, h, 1); pixm = pixCreate(w, h, 1); lq = lqueueCreate(0); /* Prime the queue with the first pixel; it is OFF */ el = mazeelCreate(xi, yi, START_LOC); pixSetPixel(pixm, xi, yi, 1); /* mark visited */ lqueueAdd(lq, el); /* While we're at it ... */ while (lqueueGetCount(lq) > 0) { elp = (MAZEEL *)lqueueRemove(lq); x = elp->x; y = elp->y; dir = elp->dir; if (x > 0) { /* check west */ pixGetPixel(pixm, x - 1, y, &val); if (val == 0) { /* not yet visited */ pixSetPixel(pixm, x - 1, y, 1); /* mark visited */ frand = (l_float32)rand() / (l_float32)RAND_MAX; testp = wallps; if (dir == DIR_WEST) testp = wallpf; if (frand <= testp) { /* make it a wall */ pixSetPixel(pixd, x - 1, y, 1); } else { /* not a wall */ el = mazeelCreate(x - 1, y, DIR_WEST); lqueueAdd(lq, el); } } } if (y > 0) { /* check north */ pixGetPixel(pixm, x, y - 1, &val); if (val == 0) { /* not yet visited */ pixSetPixel(pixm, x, y - 1, 1); /* mark visited */ frand = (l_float32)rand() / (l_float32)RAND_MAX; testp = wallps; if (dir == DIR_NORTH) testp = wallpf; if (frand <= testp) { /* make it a wall */ pixSetPixel(pixd, x, y - 1, 1); } else { /* not a wall */ el = mazeelCreate(x, y - 1, DIR_NORTH); lqueueAdd(lq, el); } } } if (x < w - 1) { /* check east */ pixGetPixel(pixm, x + 1, y, &val); if (val == 0) { /* not yet visited */ pixSetPixel(pixm, x + 1, y, 1); /* mark visited */ frand = (l_float32)rand() / (l_float32)RAND_MAX; testp = wallps; if (dir == DIR_EAST) testp = wallpf; if (frand <= testp) { /* make it a wall */ pixSetPixel(pixd, x + 1, y, 1); } else { /* not a wall */ el = mazeelCreate(x + 1, y, DIR_EAST); lqueueAdd(lq, el); } } } if (y < h - 1) { /* check south */ pixGetPixel(pixm, x, y + 1, &val); if (val == 0) { /* not yet visited */ pixSetPixel(pixm, x, y + 1, 1); /* mark visited */ frand = (l_float32)rand() / (l_float32)RAND_MAX; testp = wallps; if (dir == DIR_SOUTH) testp = wallpf; if (frand <= testp) { /* make it a wall */ pixSetPixel(pixd, x, y + 1, 1); } else { /* not a wall */ el = mazeelCreate(x, y + 1, DIR_SOUTH); lqueueAdd(lq, el); } } } FREE(elp); } lqueueDestroy(&lq, TRUE); pixDestroy(&pixm); return pixd; }
/*! * pixSearchBinaryMaze() * * 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 * * Notes: * (1) Because of the overhead in calling pixGetPixel() and * pixSetPixel(), we have used raster line pointers and the * GET_DATA* and SET_DATA* macros for many of the pix accesses. * (2) Commentary: * The goal is to find the shortest path between beginning and * end points, without going through walls, and there are many * ways to solve this problem. * We use a queue to implement a breadth-first search. Two auxiliary * "image" data structures can be used: one to mark the visited * pixels and one to give the direction to the parent for each * visited pixels. The first structure is used to avoid putting * pixels on the queue more than once, and the second is used * for retracing back to the origin, like the breadcrumbs in * Hansel and Gretel. Each pixel taken off the queue is destroyed * after it is used to locate the allowed neighbors. In fact, * only one distance image is required, if you initialize it * to some value that signifies "not yet visited." (We use * a binary image for marking visited pixels because it is clearer.) * This method for a simple search of a binary maze is implemented in * searchBinaryMaze(). * An alternative method would store the (manhattan) distance * from the start point with each pixel on the queue. The children * of each pixel get a distance one larger than the parent. These * values can be stored in an auxiliary distance map image * that is constructed simultaneously with the search. Once the * end point is reached, the distance map is used to backtrack * along a minimum path. There may be several equal length * minimum paths, any one of which can be chosen this way. */ PTA * pixSearchBinaryMaze(PIX *pixs, l_int32 xi, l_int32 yi, l_int32 xf, l_int32 yf, PIX **ppixd) { l_int32 i, j, x, y, w, h, d, found; l_uint32 val, rpixel, gpixel, bpixel; void **lines1, **linem1, **linep8, **lined32; MAZEEL *el, *elp; PIX *pixd; /* the shortest path written on the maze image */ PIX *pixm; /* for bookkeeping, to indicate pixels already visited */ PIX *pixp; /* for bookkeeping, to indicate direction to parent */ L_QUEUE *lq; PTA *pta; PROCNAME("pixSearchBinaryMaze"); if (ppixd) *ppixd = NULL; if (!pixs) return (PTA *)ERROR_PTR("pixs not defined", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); if (d != 1) return (PTA *)ERROR_PTR("pixs not 1 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); pixGetPixel(pixs, xi, yi, &val); if (val != 0) return (PTA *)ERROR_PTR("(xi,yi) not bg pixel", procName, NULL); pixd = NULL; pta = NULL; /* Find a bg pixel near input point (xf, yf) */ localSearchForBackground(pixs, &xf, &yf, 5); #if DEBUG_MAZE fprintf(stderr, "(xi, yi) = (%d, %d), (xf, yf) = (%d, %d)\n", xi, yi, xf, yf); #endif /* DEBUG_MAZE */ pixm = pixCreate(w, h, 1); /* initialized to OFF */ pixp = pixCreate(w, h, 8); /* direction to parent stored as enum val */ lines1 = pixGetLinePtrs(pixs, NULL); linem1 = pixGetLinePtrs(pixm, NULL); linep8 = pixGetLinePtrs(pixp, NULL); lq = lqueueCreate(0); /* Prime the queue with the first pixel; it is OFF */ el = mazeelCreate(xi, yi, 0); /* don't need direction here */ pixSetPixel(pixm, xi, yi, 1); /* mark visited */ lqueueAdd(lq, el); /* Fill up the pix storing directions to parents, * stopping when we hit the point (xf, yf) */ found = FALSE; while (lqueueGetCount(lq) > 0) { elp = (MAZEEL *)lqueueRemove(lq); x = elp->x; y = elp->y; if (x == xf && y == yf) { found = TRUE; FREE(elp); break; } if (x > 0) { /* check to west */ val = GET_DATA_BIT(linem1[y], x - 1); if (val == 0) { /* not yet visited */ SET_DATA_BIT(linem1[y], x - 1); /* mark visited */ val = GET_DATA_BIT(lines1[y], x - 1); if (val == 0) { /* bg, not a wall */ SET_DATA_BYTE(linep8[y], x - 1, DIR_EAST); /* parent E */ el = mazeelCreate(x - 1, y, 0); lqueueAdd(lq, el); } } } if (y > 0) { /* check north */ val = GET_DATA_BIT(linem1[y - 1], x); if (val == 0) { /* not yet visited */ SET_DATA_BIT(linem1[y - 1], x); /* mark visited */ val = GET_DATA_BIT(lines1[y - 1], x); if (val == 0) { /* bg, not a wall */ SET_DATA_BYTE(linep8[y - 1], x, DIR_SOUTH); /* parent S */ el = mazeelCreate(x, y - 1, 0); lqueueAdd(lq, el); } } } if (x < w - 1) { /* check east */ val = GET_DATA_BIT(linem1[y], x + 1); if (val == 0) { /* not yet visited */ SET_DATA_BIT(linem1[y], x + 1); /* mark visited */ val = GET_DATA_BIT(lines1[y], x + 1); if (val == 0) { /* bg, not a wall */ SET_DATA_BYTE(linep8[y], x + 1, DIR_WEST); /* parent W */ el = mazeelCreate(x + 1, y, 0); lqueueAdd(lq, el); } } } if (y < h - 1) { /* check south */ val = GET_DATA_BIT(linem1[y + 1], x); if (val == 0) { /* not yet visited */ SET_DATA_BIT(linem1[y + 1], x); /* mark visited */ val = GET_DATA_BIT(lines1[y + 1], x); if (val == 0) { /* bg, not a wall */ SET_DATA_BYTE(linep8[y + 1], x, DIR_NORTH); /* parent N */ el = mazeelCreate(x, y + 1, 0); lqueueAdd(lq, el); } } } FREE(elp); } lqueueDestroy(&lq, TRUE); pixDestroy(&pixm); FREE(linem1); if (ppixd) { pixd = pixUnpackBinary(pixs, 32, 1); *ppixd = pixd; } composeRGBPixel(255, 0, 0, &rpixel); /* start point */ composeRGBPixel(0, 255, 0, &gpixel); composeRGBPixel(0, 0, 255, &bpixel); /* end point */ if (!found) { L_INFO(" No path found", procName); if (pixd) { /* paint all visited locations */ lined32 = pixGetLinePtrs(pixd, NULL); for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { val = GET_DATA_BYTE(linep8[i], j); if (val != 0 && pixd) SET_DATA_FOUR_BYTES(lined32[i], j, gpixel); } } FREE(lined32); } } else { /* write path onto pixd */ L_INFO(" Path found", procName); pta = ptaCreate(0); x = xf; y = yf; while (1) { 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--; } } if (pixd) { pixSetPixel(pixd, xi, yi, rpixel); pixSetPixel(pixd, xf, yf, bpixel); } pixDestroy(&pixp); FREE(lines1); FREE(linep8); return pta; }
/*! * identifyWatershedBasin() * * Input: wshed * index (index of basin to be located) * level (of basin at point at which the two basins met) * &box (<return> bounding box of basin) * &pixd (<return> pix of basin, cropped to its bounding box) * Return: 0 if OK, 1 on error * * Notes: * (1) This is a static function, so we assume pixlab, pixs and pixt * exist and are the same size. * (2) It selects all pixels that have the label @index in pixlab * and that have a value in pixs that is less than @level. * (3) It is used whenever two seeded basins meet (typically at a saddle), * or when one seeded basin meets a 'filler'. All identified * basins are saved as a watershed. */ static l_int32 identifyWatershedBasin(L_WSHED *wshed, l_int32 index, l_int32 level, BOX **pbox, PIX **ppixd) { l_int32 imin, imax, jmin, jmax, minx, miny, maxx, maxy; l_int32 bw, bh, i, j, w, h, x, y; l_int32 *lut; l_uint32 label, bval, lval; void **lines8, **linelab32, **linet1; BOX *box; PIX *pixs, *pixt, *pixd; L_QUEUE *lq; PROCNAME("identifyWatershedBasin"); if (!pbox) return ERROR_INT("&box not defined", procName, 1); *pbox = NULL; if (!ppixd) return ERROR_INT("&pixd not defined", procName, 1); *ppixd = NULL; if (!wshed) return ERROR_INT("wshed not defined", procName, 1); /* Make a queue and an auxiliary stack */ lq = lqueueCreate(0); lq->stack = lstackCreate(0); pixs = wshed->pixs; pixt = wshed->pixt; lines8 = wshed->lines8; linelab32 = wshed->linelab32; linet1 = wshed->linet1; lut = wshed->lut; pixGetDimensions(pixs, &w, &h, NULL); /* Prime the queue with the seed pixel for this watershed. */ minx = miny = 1000000; maxx = maxy = 0; ptaGetIPt(wshed->ptas, index, &x, &y); pixSetPixel(pixt, x, y, 1); pushNewPixel(lq, x, y, &minx, &maxx, &miny, &maxy); if (wshed->debug) fprintf(stderr, "prime: (x,y) = (%d, %d)\n", x, y); /* Each pixel in a spreading breadth-first search is inspected. * It is accepted as part of this watershed, and pushed on * the search queue, if: * (1) It has a label value equal to @index * (2) The pixel value is less than @level, the overflow * height at which the two basins join. * (3) It has not yet been seen in this search. */ while (lqueueGetCount(lq) > 0) { popNewPixel(lq, &x, &y); 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 (j == x && i == y) continue; /* parent */ label = GET_DATA_FOUR_BYTES(linelab32[i], j); if (label == MAX_LABEL_VALUE || lut[label] != index) continue; bval = GET_DATA_BIT(linet1[i], j); if (bval == 1) continue; /* already seen */ lval = GET_DATA_BYTE(lines8[i], j); if (lval >= level) continue; /* too high */ SET_DATA_BIT(linet1[i], j); pushNewPixel(lq, j, i, &minx, &maxx, &miny, &maxy); } } } /* Extract the box and pix, and clear pixt */ bw = maxx - minx + 1; bh = maxy - miny + 1; box = boxCreate(minx, miny, bw, bh); pixd = pixClipRectangle(pixt, box, NULL); pixRasterop(pixt, minx, miny, bw, bh, PIX_SRC ^ PIX_DST, pixd, 0, 0); *pbox = box; *ppixd = pixd; lqueueDestroy(&lq, 1); return 0; }