forked from thi-ng/umbrella
-
Notifications
You must be signed in to change notification settings - Fork 0
/
flood-fill.ts
95 lines (93 loc) · 2.13 KB
/
flood-fill.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import type { Predicate2 } from "@thi.ng/api";
import { BitField, defBitField } from "@thi.ng/bitfield/bitfield";
/**
* Yields an iterator of 2D coordinates of the connected region around `x,y` for
* which the given predicate succeeds. I.e. The function recursively explores
* (in a row-major manner) the space in the `[0,0]..(width,height)` interval,
* starting at given `x,y` and continues as long given predicate function
* returns a truthy value.
*
* @remarks
* Only the behavior is recursive, not the actual implementation (stack based).
* Grid cells are visited max. once. A bit field is used to mark visited cells.
*
* @example
* ```ts
* const img = [
* 1,0,1,0,
* 0,0,0,0,
* 0,1,1,0,
* 0,1,1,1,
* ];
*
* // flood fill connected region from point (2,1)
* [...floodFill((x, y) => img[y * 4 + x] === 0, 2, 1, 4, 4)]
* // [
* // [2, 1], [1, 1], [0, 1], [3, 1], [3, 2],
* // [3, 0], [0, 2], [0, 3], [1, 0]
* // ]
* ```
*
* @param pred -
* @param x -
* @param y -
* @param width -
* @param height -
*/
export function* floodFill(
pred: Predicate2<number>,
x: number,
y: number,
width: number,
height: number
) {
x |= 0;
y |= 0;
if (!pred(x, y)) return;
const queue: number[][] = [[x, y]];
const visited = defBitField(width * height);
height--;
while (queue.length) {
[x, y] = queue.pop()!;
yield* partialRow(pred, queue, visited, x, y, width, height, -1);
yield* partialRow(pred, queue, visited, x + 1, y, width, height, 1);
}
}
/** @internal */
function* partialRow(
pred: Predicate2<number>,
queue: number[][],
visited: BitField,
x: number,
y: number,
width: number,
height1: number,
step: number
) {
let idx = y * width + x;
if (visited.at(idx)) return;
let scanUp = false;
let scanDown = false;
while (x >= 0 && x < width && pred(x, y)) {
visited.setAt(idx);
yield [x, y];
if (y > 0) {
if (pred(x, y - 1) && !scanUp) {
queue.push([x, y - 1]);
scanUp = true;
} else {
scanUp = false;
}
}
if (y < height1) {
if (pred(x, y + 1) && !scanDown) {
queue.push([x, y + 1]);
scanDown = true;
} else {
scanDown = false;
}
}
x += step;
idx += step;
}
}