1 | // Abstract base class for finding all connected regions of same color |
2 | // in an image using flood filling |
3 | |
4 | abstract srecord noeq AbstractFastRegions<Img extends WidthAndHeight>(Img image) is Runnable, IImageRegions<Img> { |
5 | int w, h, runner; |
6 | gettable int size; // =w*h |
7 | |
8 | new IntBuffer stack; // locations as y*w+x |
9 | int[] regionMatrix; // for each pixel: region index (starting at 1) |
10 | new IntBuffer regionPixels; // collect all pixels for regions |
11 | |
12 | settable bool withDiagonals; // also walk diagonally? |
13 | |
14 | // initialize these to use them |
15 | |
16 | new IntBuffer regionFirstPixel; // for each region: index of first pixel found in regionPixels |
17 | new IntBuffer regionSize; // for each region: number of pixels |
18 | new IntBuffer regionBounds; // for each region: bounds (x1, y1, x2, y2) |
19 | |
20 | // index = dual log of region size, value = region index |
21 | new L<IntBuffer> regionsBySize; |
22 | |
23 | int regionCounter; |
24 | bool verbose; |
25 | |
26 | double regionStep = .1; // for rendering in regionsImage |
27 | |
28 | int x(int pos) { ret pos % w; } |
29 | int y(int pos) { ret pos / w; } |
30 | int pos(int x, int y) { ret y*w+x; } |
31 | Pt pt(int pos) { ret Pt(x(pos), y(pos)); } |
32 | bool validPos(int x, int y) { ret x >= 0 && y >= 0 && x < w && y < h; } |
33 | |
34 | abstract int getColor(int pos); |
35 | |
36 | run { |
37 | if (regionMatrix != null) ret; |
38 | |
39 | w = image.getWidth(); h = image.getHeight(); |
40 | size = w*h; |
41 | regionMatrix = new int[size]; |
42 | |
43 | // 0 entries are unused |
44 | regionFirstPixel?.add(0); |
45 | regionSize?.add(0); |
46 | |
47 | regionPixels?.setSize(size); |
48 | |
49 | while (runner < size) { |
50 | if (regionMatrix[runner] == 0) { |
51 | // make a new region, get color |
52 | int region = ++regionCounter; |
53 | regionFirstPixel?.add(regionPixels != null ? l(regionPixels) : runner); |
54 | stack.add(runner); |
55 | int color = getColor(runner); |
56 | int rsize = 0, x1 = w, y1 = h, x2 = 0, y2 = 0; |
57 | ifdef FastRegions_debug |
58 | printVars FastRegions(+region, +runner); |
59 | endifdef |
60 | |
61 | // flood-fill region |
62 | while (nempty(stack)) { |
63 | int pos = stack.popLast(); |
64 | if (regionMatrix[pos] != 0) continue; // touching myself (or someone else) |
65 | if (getColor(pos) != color) continue; // wrong color |
66 | |
67 | // new pixel found, mark as ours |
68 | regionMatrix[pos] = region; |
69 | ++rsize; |
70 | regionPixels?.add(pos); |
71 | int x = x(pos), y = y(pos); |
72 | |
73 | ifdef FastRegions_debug |
74 | printVars FastRegions(+x, +y); |
75 | endifdef |
76 | |
77 | if (x < x1) x1 = x; |
78 | if (x > x2) x2 = x; |
79 | if (y < y1) y1 = y; |
80 | if (y > y2) y2 = y; |
81 | |
82 | // explore neighborhood |
83 | if (x > 0) stack.add(pos-1); |
84 | if (x < w-1) stack.add(pos+1); |
85 | if (y > 0) stack.add(pos-w); |
86 | if (y < h-1) stack.add(pos+w); |
87 | |
88 | if (withDiagonals) { |
89 | if (y > 0) { |
90 | if (x > 0) stack.add(pos-w-1); |
91 | if (x < w-1) stack.add(pos-w+1); |
92 | } |
93 | if (y < h-1) { |
94 | if (x > 0) stack.add(pos+w-1); |
95 | if (x < w-1) stack.add(pos+w+1); |
96 | } |
97 | } |
98 | } |
99 | |
100 | regionSize?.add(rsize); |
101 | regionBounds?.addAll(x1, y1, x2+1, y2+1); |
102 | if (regionsBySize != null) { |
103 | int iBucket = dualLog(rsize); |
104 | var buffer = listGetOrCreate(regionsBySize, iBucket, -> new IntBuffer); |
105 | buffer.add(region); |
106 | } |
107 | } |
108 | |
109 | ++runner; |
110 | } |
111 | } |
112 | |
113 | IBWImage regionsImage() { |
114 | ret iBWImageFromFunction((x, y) -> { |
115 | var region = regionMatrix[pos(x, y)]; |
116 | ret ((region-1)*regionStep) % (1.0+regionStep-0.0001); |
117 | }, w, h); |
118 | } |
119 | |
120 | int regionCount aka nRegions() { ret regionCounter; } |
121 | |
122 | abstract class RegionIterator { |
123 | int pos; |
124 | |
125 | abstract bool next(); |
126 | |
127 | int pos() { ret pos; } |
128 | int x() { ret AbstractFastRegions.this.x(pos); } |
129 | int y() { ret AbstractFastRegions.this.y(pos); } |
130 | } |
131 | |
132 | // returns points in no particular order |
133 | class FloodRegionIterator > RegionIterator { |
134 | int region; |
135 | new IntBuffer stack; // locations as y*w+x |
136 | BitSet seen = new(size); |
137 | |
138 | *(int *region) { |
139 | int pos = regionFirstPixel.get(region); |
140 | printVars(+region, +pos); |
141 | seen.set(pos); |
142 | stack.add(pos); |
143 | } |
144 | |
145 | // flood-fill region |
146 | bool next() { |
147 | if (empty(stack)) false; |
148 | |
149 | pos = stack.popLast(); |
150 | |
151 | // explore neighborhood |
152 | int x = x(), y = y(); |
153 | if (x > 0) tryPosition(pos-1); |
154 | if (x < w-1) tryPosition(pos+1); |
155 | if (y > 0) tryPosition(pos-w); |
156 | if (y < h-1) tryPosition(pos+w); |
157 | |
158 | true; |
159 | } |
160 | |
161 | private void tryPosition(int p) { |
162 | if (!seen.get(p) && regionMatrix[p] == region) { |
163 | seen.set(p); |
164 | stack.add(p); |
165 | } |
166 | } |
167 | } |
168 | |
169 | class CachedRegionIterator > RegionIterator { |
170 | int i, to; |
171 | |
172 | *(int region) { |
173 | i = regionFirstPixel.get(region); |
174 | to = region+1 < l(regionFirstPixel) ? regionFirstPixel.get(region+1) : l(regionPixels); |
175 | |
176 | ifdef FastRegions_debug |
177 | printVars CachedRegionIterator(+region, +i, +to); |
178 | endifdef |
179 | } |
180 | |
181 | bool next() { |
182 | if (i >= to) false; |
183 | pos = regionPixels.get(i++); |
184 | true; |
185 | } |
186 | } |
187 | |
188 | int regionSize(int iRegion) { ret regionSize.get(iRegion); } |
189 | |
190 | Pt samplePixel aka firstPixel(int iRegion) { |
191 | ret pt(firstPixelPos(iRegion)); |
192 | } |
193 | |
194 | int firstPixelPos(int iRegion) { |
195 | int i = regionFirstPixel.get(iRegion); |
196 | ret regionPixels != null ? regionPixels.get(i) : i; |
197 | } |
198 | |
199 | bool inRegion(int iRegion, int x, int y) { |
200 | ret validPos(x, y) && regionMatrix[pos(x, y)] == iRegion; |
201 | } |
202 | Rect regionBounds(int iRegion) { ret rectFromPoints( |
203 | regionBounds.get((iRegion-1)*4), |
204 | regionBounds.get((iRegion-1)*4+1), |
205 | regionBounds.get((iRegion-1)*4+2), |
206 | regionBounds.get((iRegion-1)*4+3), |
207 | ); } |
208 | |
209 | int regionAt(Pt p) { ret regionAt(p.x, p.y); } |
210 | |
211 | int regionAt(int x, int y) { |
212 | ret !validPos(x, y) ? 0 : regionMatrix[pos(x, y)]; |
213 | } |
214 | |
215 | int regionAt(int pos) { |
216 | ret regionMatrix[pos]; |
217 | } |
218 | |
219 | RegionIterator regionIterator(int iRegion) { |
220 | ret regionPixels != null |
221 | ? new CachedRegionIterator(iRegion) |
222 | : new FloodRegionIterator(iRegion); |
223 | } |
224 | |
225 | L<Pt> regionPixels(int iRegion) { |
226 | var it = regionIterator(iRegion); |
227 | new L<Pt> l; |
228 | while (it.next()) |
229 | l.add(Pt(it.x(), it.y())); |
230 | ret l; |
231 | } |
232 | |
233 | // select extra features before regions are made |
234 | // (not necessary anymore) |
235 | |
236 | void collectFirstPixels { /*regionFirstPixel = new IntBuffer;*/ } |
237 | void collectBounds { /*regionBounds = new IntBuffer;*/ } |
238 | |
239 | BitMatrix regionBitMatrix(int iRegion) { |
240 | ret new AbstractBitMatrix(w, h) { |
241 | public Bool get(int x, int y) { |
242 | ret inRegion(iRegion, x, y); |
243 | } |
244 | }; |
245 | } |
246 | |
247 | void markRegionInPixelArray(int[] pixels, int iRegion, int rgba) { |
248 | if (iRegion <= 0) ret; |
249 | for i over pixels: |
250 | if (regionAt(i) == iRegion) |
251 | pixels[i] = rgba; |
252 | } |
253 | |
254 | L<Int> regionIndices() { ret virtualCountList(1, nRegions()+1); } |
255 | |
256 | ItIt<Int> regionsRoughlyByDecreasingSize() { |
257 | ret nestedIterator(countIterator_inclusive_backwards(regionsBySize.size()-1, 0), |
258 | iBucket -> iterator(regionsBySize.get(iBucket))); |
259 | } |
260 | |
261 | IImageRegion<Img> getRegion aka get(int iRegion) { |
262 | ret new ImageRegion(iRegion); |
263 | } |
264 | |
265 | public L<IImageRegion<Img>> regions aka get() { |
266 | run(); |
267 | ret listFromFunction(i -> getRegion(i+1), nRegions()); |
268 | } |
269 | |
270 | record noeq ImageRegion(int iRegion) is IImageRegion<Img> { |
271 | public int hashCode() { ret iRegion; } |
272 | public bool equals(O o) { |
273 | if (!o instanceof AbstractFastRegions.ImageRegion) false; |
274 | ret ((AbstractFastRegions.ImageRegion) o).creator() == creator() |
275 | && ((AbstractFastRegions.ImageRegion) o).iRegion == iRegion; |
276 | } |
277 | |
278 | public Img image() { ret image; } |
279 | public O creator() { ret AbstractFastRegions.this; } |
280 | public int indexInCreator() { ret iRegion; } |
281 | public Bool createdWithDiagonals() { ret withDiagonals; } |
282 | |
283 | public Rect bounds() { ret regionBounds(iRegion); } |
284 | |
285 | public int numberOfPixels() { ret regionSize(iRegion); } |
286 | |
287 | public Pt firstPixel() { ret pt(firstPixelPos()); } |
288 | public int firstPixelPos() { ret AbstractFastRegions.this.firstPixelPos(iRegion); } |
289 | |
290 | public ItIt<Pt> pixelIterator() { |
291 | var it = regionIterator(iRegion); |
292 | ret iteratorFromFunction(-> it.next() ? pt(it.pos()) : null); |
293 | } |
294 | |
295 | public bool contains(int x, int y) { ret inRegion(iRegion, x, y); } |
296 | |
297 | public Color color() { |
298 | ret toColor(rgbForRegion(this)); |
299 | } |
300 | |
301 | public int brightness aka firstPixelLogicalColor() { |
302 | ret getColor(firstPixelPos()); |
303 | } |
304 | |
305 | toString { |
306 | ret renderRecordVars("Region", +brightness(), +color(), pixels := numberOfPixels(), +bounds()); |
307 | } |
308 | } |
309 | |
310 | abstract RGB rgbForRegion(ImageRegion r); |
311 | } |
Began life as a copy of #1034520
download show line numbers debug dex old transpilations
Travelled to 2 computer(s): mqqgnosmbjvj, wnsclhtenguj
No comments. add comment
Snippet ID: | #1035890 |
Snippet name: | AbstractFastRegions (backup before optimization) |
Eternal ID of this version: | #1035890/1 |
Text MD5: | 9361a1f6ee5ef5241097c01f69510d56 |
Author: | stefan |
Category: | javax / imaging |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2022-08-07 20:06:38 |
Source code size: | 9238 bytes / 311 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 126 / 131 |
Referenced in: | [show references] |