comparison mupdf-source/thirdparty/leptonica/src/maze.c @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
1 /*====================================================================*
2 - Copyright (C) 2001 Leptonica. All rights reserved.
3 -
4 - Redistribution and use in source and binary forms, with or without
5 - modification, are permitted provided that the following conditions
6 - are met:
7 - 1. Redistributions of source code must retain the above copyright
8 - notice, this list of conditions and the following disclaimer.
9 - 2. Redistributions in binary form must reproduce the above
10 - copyright notice, this list of conditions and the following
11 - disclaimer in the documentation and/or other materials
12 - provided with the distribution.
13 -
14 - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15 - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16 - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17 - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
18 - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23 - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 *====================================================================*/
26
27
28 /*!
29 * \file maze.c
30 * <pre>
31 *
32 * This is a game with a pedagogical slant. A maze is represented
33 * by a binary image. The ON pixels (fg) are walls. The goal is
34 * to navigate on OFF pixels (bg), using Manhattan steps
35 * (N, S, E, W), between arbitrary start and end positions.
36 * The problem is thus to find the shortest route between two points
37 * in a binary image that are 4-connected in the bg. This is done
38 * with a breadth-first search, implemented with a queue.
39 * We also use a queue of pointers to generate the maze (image).
40 *
41 * PIX *generateBinaryMaze()
42 * static MAZEEL *mazeelCreate()
43 *
44 * PIX *pixSearchBinaryMaze()
45 * static l_int32 localSearchForBackground()
46 *
47 * Generalizing a maze to a grayscale image, the search is
48 * now for the "shortest" or least cost path, for some given
49 * cost function.
50 *
51 * PIX *pixSearchGrayMaze()
52 * </pre>
53 */
54
55 #ifdef HAVE_CONFIG_H
56 #include <config_auto.h>
57 #endif /* HAVE_CONFIG_H */
58
59 #include <string.h>
60 #ifdef _WIN32
61 #include <stdlib.h>
62 #include <time.h>
63 #endif /* _WIN32 */
64 #include "allheaders.h"
65
66 static const l_int32 MinMazeWidth = 50;
67 static const l_int32 MinMazeHeight = 50;
68
69 static const l_float32 DefaultWallProbability = 0.65f;
70 static const l_float32 DefaultAnisotropyRatio = 0.25f;
71
72 enum { /* direction from parent to newly created element */
73 START_LOC = 0,
74 DIR_NORTH = 1,
75 DIR_SOUTH = 2,
76 DIR_WEST = 3,
77 DIR_EAST = 4
78 };
79
80 struct MazeElement {
81 l_float32 distance;
82 l_int32 x;
83 l_int32 y;
84 l_uint32 val; /* value of maze pixel at this location */
85 l_int32 dir; /* direction from parent to child */
86 };
87 typedef struct MazeElement MAZEEL;
88
89
90 static MAZEEL *mazeelCreate(l_int32 x, l_int32 y, l_int32 dir);
91 static l_int32 localSearchForBackground(PIX *pix, l_int32 *px,
92 l_int32 *py, l_int32 maxrad);
93
94 #ifndef NO_CONSOLE_IO
95 #define DEBUG_PATH 0
96 #define DEBUG_MAZE 0
97 #endif /* ~NO_CONSOLE_IO */
98
99 /*---------------------------------------------------------------------*
100 * Binary maze generation as cellular automaton *
101 *---------------------------------------------------------------------*/
102 /*!
103 * \brief generateBinaryMaze()
104 *
105 * \param[in] w, h size of maze
106 * \param[in] xi, yi initial location
107 * \param[in] wallps probability that a pixel to the side is ON
108 * \param[in] ranis ratio of prob that pixel in forward direction
109 * is a wall to the probability that pixel in
110 * side directions is a wall
111 * \return pix, or NULL on error
112 *
113 * <pre>
114 * Notes:
115 * (1) We have two input probability factors that determine the
116 * density of walls and average length of straight passages.
117 * When ranis < 1.0, you are more likely to generate a wall
118 * to the side than going forward. Enter 0.0 for either if
119 * you want to use the default values.
120 * (2) This is a type of percolation problem, and exhibits
121 * different phases for different parameters wallps and ranis.
122 * For larger values of these parameters, regions in the maze
123 * are not explored because the maze generator walls them
124 * off and cannot get through. The boundary between the
125 * two phases in this two-dimensional parameter space goes
126 * near these values:
127 * wallps ranis
128 * 0.35 1.00
129 * 0.40 0.85
130 * 0.45 0.70
131 * 0.50 0.50
132 * 0.55 0.40
133 * 0.60 0.30
134 * 0.65 0.25
135 * 0.70 0.19
136 * 0.75 0.15
137 * 0.80 0.11
138 * (3) Because there is a considerable amount of overhead in calling
139 * pixGetPixel() and pixSetPixel(), this function can be sped
140 * up with little effort using raster line pointers and the
141 * GET_DATA* and SET_DATA* macros.
142 * </pre>
143 */
144 PIX *
145 generateBinaryMaze(l_int32 w,
146 l_int32 h,
147 l_int32 xi,
148 l_int32 yi,
149 l_float32 wallps,
150 l_float32 ranis)
151 {
152 l_int32 x, y, dir;
153 l_uint32 val;
154 l_float32 frand, wallpf, testp;
155 MAZEEL *el, *elp;
156 PIX *pixd; /* the destination maze */
157 PIX *pixm; /* for bookkeeping, to indicate pixels already visited */
158 L_QUEUE *lq;
159
160 /* On Windows, seeding is apparently necessary to get decent mazes.
161 * Windows rand() returns a value up to 2^15 - 1, whereas unix
162 * rand() returns a value up to 2^31 - 1. Therefore the generated
163 * mazes will differ on the two platforms. */
164 #ifdef _WIN32
165 srand(28*333);
166 #endif /* _WIN32 */
167
168 if (w < MinMazeWidth)
169 w = MinMazeWidth;
170 if (h < MinMazeHeight)
171 h = MinMazeHeight;
172 if (xi <= 0 || xi >= w)
173 xi = w / 6;
174 if (yi <= 0 || yi >= h)
175 yi = h / 5;
176 if (wallps < 0.05 || wallps > 0.95)
177 wallps = DefaultWallProbability;
178 if (ranis < 0.05 || ranis > 1.0)
179 ranis = DefaultAnisotropyRatio;
180 wallpf = wallps * ranis;
181
182 #if DEBUG_MAZE
183 lept_stderr("(w, h) = (%d, %d), (xi, yi) = (%d, %d)\n", w, h, xi, yi);
184 lept_stderr("Using: prob(wall) = %7.4f, anisotropy factor = %7.4f\n",
185 wallps, ranis);
186 #endif /* DEBUG_MAZE */
187
188 /* These are initialized to OFF */
189 pixd = pixCreate(w, h, 1);
190 pixm = pixCreate(w, h, 1);
191
192 lq = lqueueCreate(0);
193
194 /* Prime the queue with the first pixel; it is OFF */
195 el = mazeelCreate(xi, yi, START_LOC);
196 pixSetPixel(pixm, xi, yi, 1); /* mark visited */
197 lqueueAdd(lq, el);
198
199 /* While we're at it ... */
200 while (lqueueGetCount(lq) > 0) {
201 elp = (MAZEEL *)lqueueRemove(lq);
202 x = elp->x;
203 y = elp->y;
204 dir = elp->dir;
205 if (x > 0) { /* check west */
206 pixGetPixel(pixm, x - 1, y, &val);
207 if (val == 0) { /* not yet visited */
208 pixSetPixel(pixm, x - 1, y, 1); /* mark visited */
209 frand = (l_float32)rand() / (l_float32)RAND_MAX;
210 testp = wallps;
211 if (dir == DIR_WEST)
212 testp = wallpf;
213 if (frand <= testp) { /* make it a wall */
214 pixSetPixel(pixd, x - 1, y, 1);
215 } else { /* not a wall */
216 el = mazeelCreate(x - 1, y, DIR_WEST);
217 lqueueAdd(lq, el);
218 }
219 }
220 }
221 if (y > 0) { /* check north */
222 pixGetPixel(pixm, x, y - 1, &val);
223 if (val == 0) { /* not yet visited */
224 pixSetPixel(pixm, x, y - 1, 1); /* mark visited */
225 frand = (l_float32)rand() / (l_float32)RAND_MAX;
226 testp = wallps;
227 if (dir == DIR_NORTH)
228 testp = wallpf;
229 if (frand <= testp) { /* make it a wall */
230 pixSetPixel(pixd, x, y - 1, 1);
231 } else { /* not a wall */
232 el = mazeelCreate(x, y - 1, DIR_NORTH);
233 lqueueAdd(lq, el);
234 }
235 }
236 }
237 if (x < w - 1) { /* check east */
238 pixGetPixel(pixm, x + 1, y, &val);
239 if (val == 0) { /* not yet visited */
240 pixSetPixel(pixm, x + 1, y, 1); /* mark visited */
241 frand = (l_float32)rand() / (l_float32)RAND_MAX;
242 testp = wallps;
243 if (dir == DIR_EAST)
244 testp = wallpf;
245 if (frand <= testp) { /* make it a wall */
246 pixSetPixel(pixd, x + 1, y, 1);
247 } else { /* not a wall */
248 el = mazeelCreate(x + 1, y, DIR_EAST);
249 lqueueAdd(lq, el);
250 }
251 }
252 }
253 if (y < h - 1) { /* check south */
254 pixGetPixel(pixm, x, y + 1, &val);
255 if (val == 0) { /* not yet visited */
256 pixSetPixel(pixm, x, y + 1, 1); /* mark visited */
257 frand = (l_float32)rand() / (l_float32)RAND_MAX;
258 testp = wallps;
259 if (dir == DIR_SOUTH)
260 testp = wallpf;
261 if (frand <= testp) { /* make it a wall */
262 pixSetPixel(pixd, x, y + 1, 1);
263 } else { /* not a wall */
264 el = mazeelCreate(x, y + 1, DIR_SOUTH);
265 lqueueAdd(lq, el);
266 }
267 }
268 }
269 LEPT_FREE(elp);
270 }
271
272 lqueueDestroy(&lq, TRUE);
273 pixDestroy(&pixm);
274 return pixd;
275 }
276
277
278 static MAZEEL *
279 mazeelCreate(l_int32 x,
280 l_int32 y,
281 l_int32 dir)
282 {
283 MAZEEL *el;
284
285 el = (MAZEEL *)LEPT_CALLOC(1, sizeof(MAZEEL));
286 el->x = x;
287 el->y = y;
288 el->dir = dir;
289 return el;
290 }
291
292
293 /*---------------------------------------------------------------------*
294 * Binary maze search *
295 *---------------------------------------------------------------------*/
296 /*!
297 * \brief pixSearchBinaryMaze()
298 *
299 * \param[in] pixs 1 bpp, maze
300 * \param[in] xi, yi beginning point; use same initial point
301 * that was used to generate the maze
302 * \param[in] xf, yf end point, or close to it
303 * \param[out] ppixd [optional] maze with path illustrated, or
304 * if no path possible, the part of the maze
305 * that was searched
306 * \return pta shortest path, or NULL if either no path
307 * exists or on error
308 *
309 * <pre>
310 * Notes:
311 * (1) Because of the overhead in calling pixGetPixel() and
312 * pixSetPixel(), we have used raster line pointers and the
313 * GET_DATA* and SET_DATA* macros for many of the pix accesses.
314 * (2) Commentary:
315 * The goal is to find the shortest path between beginning and
316 * end points, without going through walls, and there are many
317 * ways to solve this problem.
318 * We use a queue to implement a breadth-first search. Two auxiliary
319 * "image" data structures can be used: one to mark the visited
320 * pixels and one to give the direction to the parent for each
321 * visited pixel. The first structure is used to avoid putting
322 * pixels on the queue more than once, and the second is used
323 * for retracing back to the origin, like the breadcrumbs in
324 * Hansel and Gretel. Each pixel taken off the queue is destroyed
325 * after it is used to locate the allowed neighbors. In fact,
326 * only one distance image is required, if you initialize it
327 * to some value that signifies "not yet visited." (We use
328 * a binary image for marking visited pixels because it is clearer.)
329 * This method for a simple search of a binary maze is implemented in
330 * pixSearchBinaryMaze().
331 * An alternative method would store the (manhattan) distance
332 * from the start point with each pixel on the queue. The children
333 * of each pixel get a distance one larger than the parent. These
334 * values can be stored in an auxiliary distance map image
335 * that is constructed simultaneously with the search. Once the
336 * end point is reached, the distance map is used to backtrack
337 * along a minimum path. There may be several equal length
338 * minimum paths, any one of which can be chosen this way.
339 * </pre>
340 */
341 PTA *
342 pixSearchBinaryMaze(PIX *pixs,
343 l_int32 xi,
344 l_int32 yi,
345 l_int32 xf,
346 l_int32 yf,
347 PIX **ppixd)
348 {
349 l_int32 i, j, x, y, w, h, d, found;
350 l_uint32 val, rpixel, gpixel, bpixel;
351 void **lines1, **linem1, **linep8, **lined32;
352 MAZEEL *el, *elp;
353 PIX *pixd; /* the shortest path written on the maze image */
354 PIX *pixm; /* for bookkeeping, to indicate pixels already visited */
355 PIX *pixp; /* for bookkeeping, to indicate direction to parent */
356 L_QUEUE *lq;
357 PTA *pta;
358
359 if (ppixd) *ppixd = NULL;
360 if (!pixs)
361 return (PTA *)ERROR_PTR("pixs not defined", __func__, NULL);
362 pixGetDimensions(pixs, &w, &h, &d);
363 if (d != 1)
364 return (PTA *)ERROR_PTR("pixs not 1 bpp", __func__, NULL);
365 if (xi <= 0 || xi >= w)
366 return (PTA *)ERROR_PTR("xi not valid", __func__, NULL);
367 if (yi <= 0 || yi >= h)
368 return (PTA *)ERROR_PTR("yi not valid", __func__, NULL);
369 pixGetPixel(pixs, xi, yi, &val);
370 if (val != 0)
371 return (PTA *)ERROR_PTR("(xi,yi) not bg pixel", __func__, NULL);
372 pixd = NULL;
373 pta = NULL;
374
375 /* Find a bg pixel near input point (xf, yf) */
376 localSearchForBackground(pixs, &xf, &yf, 5);
377
378 #if DEBUG_MAZE
379 lept_stderr("(xi, yi) = (%d, %d), (xf, yf) = (%d, %d)\n",
380 xi, yi, xf, yf);
381 #endif /* DEBUG_MAZE */
382
383 pixm = pixCreate(w, h, 1); /* initialized to OFF */
384 pixp = pixCreate(w, h, 8); /* direction to parent stored as enum val */
385 lines1 = pixGetLinePtrs(pixs, NULL);
386 linem1 = pixGetLinePtrs(pixm, NULL);
387 linep8 = pixGetLinePtrs(pixp, NULL);
388
389 lq = lqueueCreate(0);
390
391 /* Prime the queue with the first pixel; it is OFF */
392 el = mazeelCreate(xi, yi, 0); /* don't need direction here */
393 pixSetPixel(pixm, xi, yi, 1); /* mark visited */
394 lqueueAdd(lq, el);
395
396 /* Fill up the pix storing directions to parents,
397 * stopping when we hit the point (xf, yf) */
398 found = FALSE;
399 while (lqueueGetCount(lq) > 0) {
400 elp = (MAZEEL *)lqueueRemove(lq);
401 x = elp->x;
402 y = elp->y;
403 if (x == xf && y == yf) {
404 found = TRUE;
405 LEPT_FREE(elp);
406 break;
407 }
408
409 if (x > 0) { /* check to west */
410 val = GET_DATA_BIT(linem1[y], x - 1);
411 if (val == 0) { /* not yet visited */
412 SET_DATA_BIT(linem1[y], x - 1); /* mark visited */
413 val = GET_DATA_BIT(lines1[y], x - 1);
414 if (val == 0) { /* bg, not a wall */
415 SET_DATA_BYTE(linep8[y], x - 1, DIR_EAST); /* parent E */
416 el = mazeelCreate(x - 1, y, 0);
417 lqueueAdd(lq, el);
418 }
419 }
420 }
421 if (y > 0) { /* check north */
422 val = GET_DATA_BIT(linem1[y - 1], x);
423 if (val == 0) { /* not yet visited */
424 SET_DATA_BIT(linem1[y - 1], x); /* mark visited */
425 val = GET_DATA_BIT(lines1[y - 1], x);
426 if (val == 0) { /* bg, not a wall */
427 SET_DATA_BYTE(linep8[y - 1], x, DIR_SOUTH); /* parent S */
428 el = mazeelCreate(x, y - 1, 0);
429 lqueueAdd(lq, el);
430 }
431 }
432 }
433 if (x < w - 1) { /* check east */
434 val = GET_DATA_BIT(linem1[y], x + 1);
435 if (val == 0) { /* not yet visited */
436 SET_DATA_BIT(linem1[y], x + 1); /* mark visited */
437 val = GET_DATA_BIT(lines1[y], x + 1);
438 if (val == 0) { /* bg, not a wall */
439 SET_DATA_BYTE(linep8[y], x + 1, DIR_WEST); /* parent W */
440 el = mazeelCreate(x + 1, y, 0);
441 lqueueAdd(lq, el);
442 }
443 }
444 }
445 if (y < h - 1) { /* check south */
446 val = GET_DATA_BIT(linem1[y + 1], x);
447 if (val == 0) { /* not yet visited */
448 SET_DATA_BIT(linem1[y + 1], x); /* mark visited */
449 val = GET_DATA_BIT(lines1[y + 1], x);
450 if (val == 0) { /* bg, not a wall */
451 SET_DATA_BYTE(linep8[y + 1], x, DIR_NORTH); /* parent N */
452 el = mazeelCreate(x, y + 1, 0);
453 lqueueAdd(lq, el);
454 }
455 }
456 }
457 LEPT_FREE(elp);
458 }
459
460 lqueueDestroy(&lq, TRUE);
461 pixDestroy(&pixm);
462 LEPT_FREE(linem1);
463
464 if (ppixd) {
465 pixd = pixUnpackBinary(pixs, 32, 1);
466 *ppixd = pixd;
467 }
468 composeRGBPixel(255, 0, 0, &rpixel); /* start point */
469 composeRGBPixel(0, 255, 0, &gpixel);
470 composeRGBPixel(0, 0, 255, &bpixel); /* end point */
471
472 if (found) {
473 L_INFO(" Path found\n", __func__);
474 pta = ptaCreate(0);
475 x = xf;
476 y = yf;
477 while (1) {
478 ptaAddPt(pta, x, y);
479 if (x == xi && y == yi)
480 break;
481 if (pixd) /* write 'gpixel' onto the path */
482 pixSetPixel(pixd, x, y, gpixel);
483 pixGetPixel(pixp, x, y, &val);
484 if (val == DIR_NORTH)
485 y--;
486 else if (val == DIR_SOUTH)
487 y++;
488 else if (val == DIR_EAST)
489 x++;
490 else if (val == DIR_WEST)
491 x--;
492 }
493 } else {
494 L_INFO(" No path found\n", __func__);
495 if (pixd) { /* paint all visited locations */
496 lined32 = pixGetLinePtrs(pixd, NULL);
497 for (i = 0; i < h; i++) {
498 for (j = 0; j < w; j++) {
499 if (GET_DATA_BYTE(linep8[i], j) != 0)
500 SET_DATA_FOUR_BYTES(lined32[i], j, gpixel);
501 }
502 }
503 LEPT_FREE(lined32);
504 }
505 }
506 if (pixd) {
507 pixSetPixel(pixd, xi, yi, rpixel);
508 pixSetPixel(pixd, xf, yf, bpixel);
509 }
510
511 pixDestroy(&pixp);
512 LEPT_FREE(lines1);
513 LEPT_FREE(linep8);
514 return pta;
515 }
516
517
518 /*!
519 * \brief localSearchForBackground()
520 *
521 * \param[in] pix
522 * \param[out] px, py starting position for search; return found position
523 * \param[in] maxrad max distance to search from starting location
524 * \return 0 if bg pixel found; 1 if not found
525 */
526 static l_int32
527 localSearchForBackground(PIX *pix,
528 l_int32 *px,
529 l_int32 *py,
530 l_int32 maxrad)
531 {
532 l_int32 x, y, w, h, r, i, j;
533 l_uint32 val;
534
535 x = *px;
536 y = *py;
537 pixGetPixel(pix, x, y, &val);
538 if (val == 0) return 0;
539
540 /* For each value of r, restrict the search to the boundary
541 * pixels in a square centered on (x,y), clipping to the
542 * image boundaries if necessary. */
543 pixGetDimensions(pix, &w, &h, NULL);
544 for (r = 1; r < maxrad; r++) {
545 for (i = -r; i <= r; i++) {
546 if (y + i < 0 || y + i >= h)
547 continue;
548 for (j = -r; j <= r; j++) {
549 if (x + j < 0 || x + j >= w)
550 continue;
551 if (L_ABS(i) != r && L_ABS(j) != r) /* not on "r ring" */
552 continue;
553 pixGetPixel(pix, x + j, y + i, &val);
554 if (val == 0) {
555 *px = x + j;
556 *py = y + i;
557 return 0;
558 }
559 }
560 }
561 }
562 return 1;
563 }
564
565
566
567 /*---------------------------------------------------------------------*
568 * Gray maze search *
569 *---------------------------------------------------------------------*/
570 /*!
571 * \brief pixSearchGrayMaze()
572 *
573 * \param[in] pixs 1 bpp maze; w and h must be >= 50
574 * \param[in] xi, yi beginning point; use same initial point
575 * that was used to generate the maze
576 * \param[in] xf, yf end point, or close to it
577 * \param[out] ppixd [optional] maze with path illustrated, or
578 * if no path possible, the part of the maze
579 * that was searched
580 * \return pta shortest path, or NULL if either no path exists or on error
581 *
582 * <pre>
583 * Commentary:
584 * Consider first a slight generalization of the binary maze
585 * search problem. Suppose that you can go through walls,
586 * but the cost is higher say, an increment of 3 to go into
587 * a wall pixel rather than 1? You're still trying to find
588 * the shortest path. One way to do this is with an ordered
589 * queue, and a simple way to visualize an ordered queue is as
590 * a set of stacks, each stack being marked with the distance
591 * of each pixel in the stack from the start. We place the
592 * start pixel in stack 0, pop it, and process its 4 children.
593 * Each pixel is given a distance that is incremented from that
594 * of its parent 0 in this case, depending on if it is a wall
595 * pixel or not. That value may be recorded on a distance map,
596 * according to the algorithm below. For children of the first
597 * pixel, those not on a wall go in stack 1, and wall
598 * children go in stack 3. Stack 0 being emptied, the process
599 * then continues with pixels being popped from stack 1.
600 * Here is the algorithm for each child pixel. The pixel's
601 * distance value, were it to be placed on a stack, is compared
602 * with the value for it that is on the distance map. There
603 * are three possible cases:
604 * 1 If the pixel has not yet been registered, it is pushed
605 * on its stack and the distance is written to the map.
606 * 2 If it has previously been registered with a higher distance,
607 * the distance on the map is relaxed to that of the
608 * current pixel, which is then placed on its stack.
609 * 3 If it has previously been registered with an equal
610 * or lower value, the pixel is discarded.
611 * The pixels are popped and processed successively from
612 * stack 1, and when stack 1 is empty, popping starts on stack 2.
613 * This continues until the destination pixel is popped off
614 * a stack. The minimum path is then derived from the distance map,
615 * going back from the end point as before. This is just Dijkstra's
616 * algorithm for a directed graph; here, the underlying graph
617 * consisting of the pixels and four edges connecting each pixel
618 * to its 4-neighbor is a special case of a directed graph, where
619 * each edge is bi-directional. The implementation of this generalized
620 * maze search is left as an exercise to the reader.
621 *
622 * Let's generalize a bit further. Suppose the "maze" is just
623 * a grayscale image -- think of it as an elevation map. The cost
624 * of moving on this surface depends on the height, or the gradient,
625 * or whatever you want. All that is required is that the cost
626 * is specified and non-negative on each link between adjacent
627 * pixels. Now the problem becomes: find the least cost path
628 * moving on this surface between two specified end points.
629 * For example, if the cost across an edge between two pixels
630 * depends on the "gradient", you can use:
631 * cost = 1 + L_ABSdeltaV
632 * where deltaV is the difference in value between two adjacent
633 * pixels. If the costs are all integers, we can still use an array
634 * of stacks to avoid ordering the queue e.g., by using a heap sort.
635 * This is a neat problem, because you don't even have to build a
636 * maze -- you can can use it on any grayscale image!
637 *
638 * Rather than using an array of stacks, a more practical
639 * approach is to implement with a priority queue, which is
640 * a queue that is sorted so that the elements with the largest
641 * or smallest key values always come off first. The
642 * priority queue is efficiently implemented as a heap, and
643 * this is how we do it. Suppose you run the algorithm
644 * using a priority queue, doing the bookkeeping with an
645 * auxiliary image data structure that saves the distance of
646 * each pixel put on the queue as before, according to the method
647 * described above. We implement it as a 2-way choice by
648 * initializing the distance array to a large value and putting
649 * a pixel on the queue if its distance is less than the value
650 * found on the array. When you finally pop the end pixel from
651 * the queue, you're done, and you can trace the path backward,
652 * either always going downhill or using an auxiliary image to
653 * give you the direction to go at each step. This is implemented
654 * here in searchGrayMaze.
655 *
656 * Do we really have to use a sorted queue? Can we solve this
657 * generalized maze with an unsorted queue of pixels? Or even
658 * an unsorted stack, doing a depth-first search (DFS)?
659 * Consider a different algorithm for this generalized maze, where
660 * we travel again breadth first, but this time use a single,
661 * unsorted queue. An auxiliary image is used as before to
662 * store the distances and to determine if pixels get pushed
663 * on the stack or dropped. As before, we must allow pixels
664 * to be revisited, with relaxation of the distance if a shorter
665 * path arrives later. As a result, we will in general have
666 * multiple instances of the same pixel on the stack with different
667 * distances. However, because the queue is not ordered, some of
668 * these pixels will be popped when another instance with a lower
669 * distance is still on the stack. Here, we're just popping them
670 * in the order they go on, rather than setting up a priority
671 * based on minimum distance. Thus, unlike the priority queue,
672 * when a pixel is popped we have to check the distance map to
673 * see if a pixel with a lower distance has been put on the queue,
674 * and, if so, we discard the pixel we just popped. So the
675 * "while" loop looks like this:
676 * ~ pop a pixel from the queue
677 * ~ check its distance against the distance stored in the
678 * distance map; if larger, discard
679 * ~ otherwise, for each of its neighbors:
680 * ~ compute its distance from the start pixel
681 * ~ compare this distance with that on the distance map:
682 * ~ if the distance map value higher, relax the distance
683 * and push the pixel on the queue
684 * ~ if the distance map value is lower, discard the pixel
685 *
686 * How does this loop terminate? Before, with an ordered queue,
687 * it terminates when you pop the end pixel. But with an unordered
688 * queue or stack, the first time you hit the end pixel, the
689 * distance is not guaranteed to be correct, because the pixels
690 * along the shortest path may not have yet been visited and relaxed.
691 * Because the shortest path can theoretically go anywhere,
692 * we must keep going. How do we know when to stop? Dijkstra
693 * uses an ordered queue to systematically remove nodes from
694 * further consideration. Each time a pixel is popped, we're
695 * done with it; it's "finalized" in the Dijkstra sense because
696 * we know the shortest path to it. However, with an unordered
697 * queue, the brute force answer is: stop when the queue
698 * or stack is empty, because then every pixel in the image
699 * has been assigned its minimum "distance" from the start pixel.
700 *
701 * This is similar to the situation when you use a stack for the
702 * simpler uniform-step problem: with breadth-first search BFS
703 * the pixels on the queue are automatically ordered, so you are
704 * done when you locate the end pixel as a neighbor of a popped pixel;
705 * whereas depth-first search DFS, using a stack, requires,
706 * in general, a search of every accessible pixel. Further, if
707 * a pixel is revisited with a smaller distance, that distance is
708 * recorded and the pixel is put on the stack again.
709 *
710 * But surely, you ask, can't we stop sooner? What if the
711 * start and end pixels are very close to each other?
712 * OK, suppose they are, and you have very high walls and a
713 * long snaking level path that is actually the minimum cost.
714 * That long path can wind back and forth across the entire
715 * maze many times before ending up at the end point, which
716 * could be just over a wall from the start. With the unordered
717 * queue, you very quickly get a high distance for the end
718 * pixel, which will be relaxed to the minimum distance only
719 * after all the pixels of the path have been visited and placed
720 * on the queue, multiple times for many of them. So that's the
721 * price for not ordering the queue!
722 * </pre>
723 */
724 PTA *
725 pixSearchGrayMaze(PIX *pixs,
726 l_int32 xi,
727 l_int32 yi,
728 l_int32 xf,
729 l_int32 yf,
730 PIX **ppixd)
731 {
732 l_int32 x, y, w, h, d;
733 l_uint32 val, valr, vals, rpixel, gpixel, bpixel;
734 void **lines8, **liner32, **linep8;
735 l_int32 cost, dist, distparent, sival, sivals;
736 MAZEEL *el, *elp;
737 PIX *pixd; /* optionally plot the path on this RGB version of pixs */
738 PIX *pixr; /* for bookkeeping, to indicate the minimum distance */
739 /* to pixels already visited */
740 PIX *pixp; /* for bookkeeping, to indicate direction to parent */
741 L_HEAP *lh;
742 PTA *pta;
743
744 if (ppixd) *ppixd = NULL;
745 if (!pixs)
746 return (PTA *)ERROR_PTR("pixs not defined", __func__, NULL);
747 pixGetDimensions(pixs, &w, &h, &d);
748 if (w < 50 || h < 50)
749 return (PTA *)ERROR_PTR("too small: w and h not >= 50", __func__, NULL);
750 if (d != 8)
751 return (PTA *)ERROR_PTR("pixs not 8 bpp", __func__, NULL);
752 if (xi <= 0 || xi >= w)
753 return (PTA *)ERROR_PTR("xi not valid", __func__, NULL);
754 if (yi <= 0 || yi >= h)
755 return (PTA *)ERROR_PTR("yi not valid", __func__, NULL);
756 pixd = NULL;
757 pta = NULL;
758
759 /* Allocate stuff */
760 pixr = pixCreate(w, h, 32);
761 pixSetAll(pixr); /* initialize to max value */
762 pixp = pixCreate(w, h, 8); /* direction to parent stored as enum val */
763 lines8 = pixGetLinePtrs(pixs, NULL);
764 linep8 = pixGetLinePtrs(pixp, NULL);
765 liner32 = pixGetLinePtrs(pixr, NULL);
766 lh = lheapCreate(0, L_SORT_INCREASING); /* always remove closest pixels */
767
768 /* Prime the heap with the first pixel */
769 pixGetPixel(pixs, xi, yi, &val);
770 el = mazeelCreate(xi, yi, 0); /* don't need direction here */
771 el->distance = 0;
772 pixGetPixel(pixs, xi, yi, &val);
773 el->val = val;
774 pixSetPixel(pixr, xi, yi, 0); /* distance is 0 */
775 lheapAdd(lh, el);
776
777 /* Breadth-first search with priority queue (implemented by
778 a heap), labeling direction to parents in pixp and minimum
779 distance to visited pixels in pixr. Stop when we pull
780 the destination point (xf, yf) off the queue. */
781 while (lheapGetCount(lh) > 0) {
782 elp = (MAZEEL *)lheapRemove(lh);
783 if (!elp) {
784 L_ERROR("heap broken!!\n", __func__);
785 goto cleanup_stuff;
786 }
787 x = elp->x;
788 y = elp->y;
789 if (x == xf && y == yf) { /* exit condition */
790 LEPT_FREE(elp);
791 break;
792 }
793 distparent = (l_int32)elp->distance;
794 val = elp->val;
795 sival = val;
796
797 if (x > 0) { /* check to west */
798 vals = GET_DATA_BYTE(lines8[y], x - 1);
799 valr = GET_DATA_FOUR_BYTES(liner32[y], x - 1);
800 sivals = (l_int32)vals;
801 cost = 1 + L_ABS(sivals - sival); /* cost to move to this pixel */
802 dist = distparent + cost;
803 if (dist < valr) { /* shortest path so far to this pixel */
804 SET_DATA_FOUR_BYTES(liner32[y], x - 1, dist); /* new dist */
805 SET_DATA_BYTE(linep8[y], x - 1, DIR_EAST); /* parent to E */
806 el = mazeelCreate(x - 1, y, 0);
807 el->val = vals;
808 el->distance = dist;
809 lheapAdd(lh, el);
810 }
811 }
812 if (y > 0) { /* check north */
813 vals = GET_DATA_BYTE(lines8[y - 1], x);
814 valr = GET_DATA_FOUR_BYTES(liner32[y - 1], x);
815 sivals = (l_int32)vals;
816 cost = 1 + L_ABS(sivals - sival); /* cost to move to this pixel */
817 dist = distparent + cost;
818 if (dist < valr) { /* shortest path so far to this pixel */
819 SET_DATA_FOUR_BYTES(liner32[y - 1], x, dist); /* new dist */
820 SET_DATA_BYTE(linep8[y - 1], x, DIR_SOUTH); /* parent to S */
821 el = mazeelCreate(x, y - 1, 0);
822 el->val = vals;
823 el->distance = dist;
824 lheapAdd(lh, el);
825 }
826 }
827 if (x < w - 1) { /* check east */
828 vals = GET_DATA_BYTE(lines8[y], x + 1);
829 valr = GET_DATA_FOUR_BYTES(liner32[y], x + 1);
830 sivals = (l_int32)vals;
831 cost = 1 + L_ABS(sivals - sival); /* cost to move to this pixel */
832 dist = distparent + cost;
833 if (dist < valr) { /* shortest path so far to this pixel */
834 SET_DATA_FOUR_BYTES(liner32[y], x + 1, dist); /* new dist */
835 SET_DATA_BYTE(linep8[y], x + 1, DIR_WEST); /* parent to W */
836 el = mazeelCreate(x + 1, y, 0);
837 el->val = vals;
838 el->distance = dist;
839 lheapAdd(lh, el);
840 }
841 }
842 if (y < h - 1) { /* check south */
843 vals = GET_DATA_BYTE(lines8[y + 1], x);
844 valr = GET_DATA_FOUR_BYTES(liner32[y + 1], x);
845 sivals = (l_int32)vals;
846 cost = 1 + L_ABS(sivals - sival); /* cost to move to this pixel */
847 dist = distparent + cost;
848 if (dist < valr) { /* shortest path so far to this pixel */
849 SET_DATA_FOUR_BYTES(liner32[y + 1], x, dist); /* new dist */
850 SET_DATA_BYTE(linep8[y + 1], x, DIR_NORTH); /* parent to N */
851 el = mazeelCreate(x, y + 1, 0);
852 el->val = vals;
853 el->distance = dist;
854 lheapAdd(lh, el);
855 }
856 }
857 LEPT_FREE(elp);
858 }
859
860 lheapDestroy(&lh, TRUE);
861
862 if (ppixd) {
863 pixd = pixConvert8To32(pixs);
864 *ppixd = pixd;
865 }
866 composeRGBPixel(255, 0, 0, &rpixel); /* start point */
867 composeRGBPixel(0, 255, 0, &gpixel);
868 composeRGBPixel(0, 0, 255, &bpixel); /* end point */
869
870 x = xf;
871 y = yf;
872 pta = ptaCreate(0);
873 while (1) { /* write path onto pixd */
874 ptaAddPt(pta, x, y);
875 if (x == xi && y == yi)
876 break;
877 if (pixd)
878 pixSetPixel(pixd, x, y, gpixel);
879 pixGetPixel(pixp, x, y, &val);
880 if (val == DIR_NORTH)
881 y--;
882 else if (val == DIR_SOUTH)
883 y++;
884 else if (val == DIR_EAST)
885 x++;
886 else if (val == DIR_WEST)
887 x--;
888 pixGetPixel(pixr, x, y, &val);
889
890 #if DEBUG_PATH
891 lept_stderr("(x,y) = (%d, %d); dist = %d\n", x, y, val);
892 #endif /* DEBUG_PATH */
893
894 }
895 if (pixd) {
896 pixSetPixel(pixd, xi, yi, rpixel);
897 pixSetPixel(pixd, xf, yf, bpixel);
898 }
899
900 cleanup_stuff:
901 lheapDestroy(&lh, TRUE);
902 pixDestroy(&pixp);
903 pixDestroy(&pixr);
904 LEPT_FREE(lines8);
905 LEPT_FREE(linep8);
906 LEPT_FREE(liner32);
907 return pta;
908 }