| /*====================================================================* |
| - Copyright (C) 2001 Leptonica. All rights reserved. |
| - This software is distributed in the hope that it will be |
| - useful, but with NO WARRANTY OF ANY KIND. |
| - No author or distributor accepts responsibility to anyone for the |
| - consequences of using this software, or for whether it serves any |
| - particular purpose or works at all, unless he or she says so in |
| - writing. Everyone is granted permission to copy, modify and |
| - redistribute this source code, for commercial or non-commercial |
| - purposes, with the following restrictions: (1) the origin of this |
| - source code must not be misrepresented; (2) modified versions must |
| - be plainly marked as such; and (3) this notice may not be removed |
| - or altered from any source or modified source distribution. |
| *====================================================================*/ |
| |
| |
| /* |
| * maze.c |
| * |
| * This is a game with a pedagogical slant. A maze is represented |
| * by a binary image. The ON pixels (fg) are walls. The goal is |
| * to navigate on OFF pixels (bg), using Manhattan steps |
| * (N, S, E, W), between arbitrary start and end positions. |
| * The problem is thus to find the shortest route between two points |
| * in a binary image that are 4-connected in the bg. This is done |
| * with a breadth-first search, implemented with a queue. |
| * We also use a queue of pointers to generate the maze (image). |
| * |
| * PIX *generateBinaryMaze() |
| * static MAZEEL *mazeelCreate() |
| * |
| * PIX *pixSearchBinaryMaze() |
| * static l_int32 localSearchForBackground() |
| * |
| * Generalizing a maze to a grayscale image, the search is |
| * now for the "shortest" or least cost path, for some given |
| * cost function. |
| * |
| * PIX *pixSearchGrayMaze() |
| * |
| * Display functions |
| * PIX *pixDisplayPta() |
| * PIX *pixDisplayPtaa() |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include "allheaders.h" |
| |
| static const l_int32 MIN_MAZE_WIDTH = 50; |
| static const l_int32 MIN_MAZE_HEIGHT = 50; |
| |
| static const l_float32 DEFAULT_WALL_PROBABILITY = 0.65; |
| static const l_float32 DEFAULT_ANISOTROPY_RATIO = 0.25; |
| |
| enum { /* direction from parent to newly created element */ |
| START_LOC = 0, |
| DIR_NORTH = 1, |
| DIR_SOUTH = 2, |
| DIR_WEST = 3, |
| DIR_EAST = 4 |
| }; |
| |
| struct MazeElement { |
| l_float32 distance; |
| l_int32 x; |
| l_int32 y; |
| l_uint32 val; /* value of maze pixel at this location */ |
| l_int32 dir; /* direction from parent to child */ |
| }; |
| typedef struct MazeElement MAZEEL; |
| |
| |
| static MAZEEL *mazeelCreate(l_int32 x, l_int32 y, l_int32 dir); |
| static l_int32 localSearchForBackground(PIX *pix, l_int32 *px, |
| l_int32 *py, l_int32 maxrad); |
| |
| #ifndef NO_CONSOLE_IO |
| #define DEBUG_PATH 0 |
| #define DEBUG_MAZE 0 |
| #endif /* ~NO_CONSOLE_IO */ |
| |
| |
| /*---------------------------------------------------------------------* |
| * Binary maze generation as cellular automaton * |
| *---------------------------------------------------------------------*/ |
| /*! |
| * 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; |
| |
| 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; |
| } |
| |
| |
| static MAZEEL * |
| mazeelCreate(l_int32 x, |
| l_int32 y, |
| l_int32 dir) |
| { |
| MAZEEL *el; |
| |
| el = (MAZEEL *)CALLOC(1, sizeof(MAZEEL)); |
| el->x = x; |
| el->y = y; |
| el->dir = dir; |
| return el; |
| } |
| |
| |
| /*---------------------------------------------------------------------* |
| * Binary maze search * |
| *---------------------------------------------------------------------*/ |
| /*! |
| * 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 |
| * |
| * Note: 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. |
| * |
| * 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; |
| } |
| |
| |
| /*! |
| * localSearchForBackground() |
| * |
| * Input: &x, &y (starting position for search; return found position) |
| * maxrad (max distance to search from starting location) |
| * Return: 0 if bg pixel found; 1 if not found |
| */ |
| static l_int32 |
| localSearchForBackground(PIX *pix, |
| l_int32 *px, |
| l_int32 *py, |
| l_int32 maxrad) |
| { |
| l_int32 x, y, w, h, r, i, j; |
| l_uint32 val; |
| |
| x = *px; |
| y = *py; |
| pixGetPixel(pix, x, y, &val); |
| if (val == 0) return 0; |
| |
| /* For each value of r, restrict the search to the boundary |
| * pixels in a square centered on (x,y), clipping to the |
| * image boundaries if necessary. */ |
| pixGetDimensions(pix, &w, &h, NULL); |
| for (r = 1; r < maxrad; r++) { |
| for (i = -r; i <= r; i++) { |
| if (y + i < 0 || y + i >= h) |
| continue; |
| for (j = -r; j <= r; j++) { |
| if (x + j < 0 || x + j >= w) |
| continue; |
| if (L_ABS(i) != r && L_ABS(j) != r) /* not on "r ring" */ |
| continue; |
| pixGetPixel(pix, x + j, y + i, &val); |
| if (val == 0) { |
| *px = x + j; |
| *py = y + i; |
| return 0; |
| } |
| } |
| } |
| } |
| return 1; |
| } |
| |
| |
| |
| /*---------------------------------------------------------------------* |
| * Gray maze search * |
| *---------------------------------------------------------------------*/ |
| /*! |
| * 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; |
| } |
| |
| |
| /*---------------------------------------------------------------------* |
| * Display Path(s) * |
| *---------------------------------------------------------------------*/ |
| /*! |
| * pixDisplayPta() |
| * |
| * Input: pixs (1, 2, 4, 8, 16 or 32 bpp) |
| * pta (of path to be plotted) |
| * Return: pixd (32 bpp RGB version of pixs, with path in green), |
| * or null on error |
| */ |
| PIX * |
| pixDisplayPta(PIX *pixs, |
| PTA *pta) |
| { |
| l_int32 i, n, x, y; |
| l_uint32 rpixel, gpixel, bpixel; |
| PIX *pixd; |
| |
| PROCNAME("pixDisplayPta"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); |
| if (!pta) |
| return (PIX *)ERROR_PTR("pta not defined", procName, NULL); |
| |
| if ((pixd = pixConvertTo32(pixs)) == NULL) |
| return (PIX *)ERROR_PTR("pixd not made", procName, NULL); |
| composeRGBPixel(255, 0, 0, &rpixel); /* start point */ |
| composeRGBPixel(0, 255, 0, &gpixel); |
| composeRGBPixel(0, 0, 255, &bpixel); /* end point */ |
| |
| n = ptaGetCount(pta); |
| for (i = 0; i < n; i++) { |
| ptaGetIPt(pta, i, &x, &y); |
| if (i == 0) |
| pixSetPixel(pixd, x, y, rpixel); |
| else if (i < n - 1) |
| pixSetPixel(pixd, x, y, gpixel); |
| else |
| pixSetPixel(pixd, x, y, bpixel); |
| } |
| |
| return pixd; |
| } |
| |
| |
| /*! |
| * pixDisplayPtaa() |
| * |
| * Input: pixs (1, 2, 4, 8, 16 or 32 bpp) |
| * ptaa (array of paths to be plotted) |
| * Return: pixd (32 bpp RGB version of pixs, with paths plotted |
| * in different colors), or null on error |
| */ |
| PIX * |
| pixDisplayPtaa(PIX *pixs, |
| PTAA *ptaa) |
| { |
| l_int32 i, j, npta, npt, x, y; |
| l_int32 rv, gv, bv; |
| l_uint32 *pixela; |
| PIX *pixd; |
| PTA *pta; |
| |
| PROCNAME("pixDisplayPtaa"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); |
| if (!ptaa) |
| return (PIX *)ERROR_PTR("ptaa not defined", procName, NULL); |
| npta = ptaaGetCount(ptaa); |
| if (npta == 0) |
| return (PIX *)ERROR_PTR("no pta", procName, NULL); |
| |
| if ((pixd = pixConvertTo32(pixs)) == NULL) |
| return (PIX *)ERROR_PTR("pixd not made", procName, NULL); |
| |
| /* make a colormap for the paths; this one approximates |
| * three functions that are linear for each color over |
| * half the number of paths. */ |
| pixela = (l_uint32 *)CALLOC(npta, sizeof(l_uint32)); |
| for (i = 0; i < npta; i++) { |
| rv = L_MAX(0, 255 - 255 * (2 * i) / (npta + 1)); |
| bv = L_MIN(255, L_MAX(0, (255 * (3 + 2 * i - npta) / (npta + 1)))); |
| if (i < npta / 2) |
| gv = L_MIN(255, (255 * 2 * i) / (npta + 1)); |
| else |
| gv = L_MIN(255, L_MAX(0, 255 - 255 * (2 * i - npta) / npta)); |
| /* fprintf(stderr, "rv = %d, gv = %d, bv = %d\n", rv, gv, bv); */ |
| composeRGBPixel(rv, gv, bv, &pixela[i]); |
| } |
| |
| for (i = 0; i < npta; i++) { |
| pta = ptaaGetPta(ptaa, i, L_CLONE); |
| npt = ptaGetCount(pta); |
| for (j = 0; j < npt; j++) { |
| ptaGetIPt(pta, j, &x, &y); |
| pixSetPixel(pixd, x, y, pixela[i]); |
| } |
| ptaDestroy(&pta); |
| } |
| |
| FREE(pixela); |
| return pixd; |
| } |
| |
| |
| |