Libraryless. Click here for Pure Java version (15580L/89K).
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; // Image size |
6 | int runner; // Position in image currently being scanned |
7 | gettable int size; // =w*h |
8 | |
9 | new IntBuffer stack; // locations as y*w+x |
10 | gettable int[] regionMatrix; // for each pixel: region index (starting at 1) |
11 | new IntBuffer regionPixels; // collect all pixels for regions |
12 | |
13 | settable bool withDiagonals; // also walk diagonally? |
14 | |
15 | // initialize these to use them |
16 | |
17 | new IntBuffer regionFirstPixel; // for each region: index of first pixel found in regionPixels |
18 | new IntBuffer regionSize; // for each region: number of pixels |
19 | new IntBuffer regionBounds; // for each region: bounds (x1, y1, x2, y2) |
20 | |
21 | // index = dual log of region size, value = region index |
22 | new L<IntBuffer> regionsBySize; |
23 | |
24 | int regionCounter; |
25 | bool verbose; |
26 | |
27 | // temporary |
28 | int currentColor; |
29 | |
30 | double regionStep = .1; // for rendering in regionsImage |
31 | |
32 | int x(int pos) { ret pos % w; } |
33 | int y(int pos) { ret pos / w; } |
34 | int pos(int x, int y) { ret y*w+x; } |
35 | Pt pt(int pos) { ret Pt(x(pos), y(pos)); } |
36 | bool validPos(int x, int y) { ret x >= 0 && y >= 0 && x < w && y < h; } |
37 | |
38 | abstract int getColor(int pos); |
39 | |
40 | bool initialized() { ret regionMatrix != null; } |
41 | |
42 | void init { |
43 | if (initialized()) ret; |
44 | |
45 | w = image.getWidth(); h = image.getHeight(); |
46 | size = w*h; |
47 | regionMatrix = new int[size]; |
48 | |
49 | // 0 entries are unused |
50 | regionFirstPixel?.add(0); |
51 | regionSize?.add(0); |
52 | |
53 | regionPixels?.setSize(size); |
54 | } |
55 | |
56 | // Search for all regions in scanline order |
57 | run { |
58 | if (initialized()) ret; |
59 | init(); |
60 | |
61 | while (runner < size) { |
62 | if (regionMatrix[runner] == 0) |
63 | makeRegion(); |
64 | ++runner; |
65 | } |
66 | } |
67 | |
68 | // returns index of new region |
69 | int makeRegion() { |
70 | // make a new region, get color |
71 | int region = ++regionCounter; |
72 | regionFirstPixel?.add(regionPixels != null ? l(regionPixels) : runner); |
73 | currentColor = getColor(runner); |
74 | stack.add(runner); |
75 | int rsize = 0, x1 = w, y1 = h, x2 = 0, y2 = 0; |
76 | ifdef FastRegions_debug |
77 | printVars FastRegions(+region, +runner); |
78 | endifdef |
79 | |
80 | // flood-fill region |
81 | while (nempty(stack)) { |
82 | int pos = stack.popLast(); |
83 | if (regionMatrix[pos] != 0) continue; // check again if set in the meantime (color has been checked before) |
84 | |
85 | // new pixel found! |
86 | int x = x(pos), y = y(pos); |
87 | |
88 | // march to the left |
89 | int lineStart = pos-x; |
90 | int xLeft = x; |
91 | while (xLeft > 0 && addable(lineStart+xLeft-1)) |
92 | --xLeft; |
93 | |
94 | // march to the right |
95 | int xRight = x+1; |
96 | while (xRight < w && addable(lineStart+xRight)) |
97 | ++xRight; |
98 | |
99 | // Now we know [xLeft; xRight) are our pixels |
100 | // mark them in matrix |
101 | for (x = xLeft; x < xRight; x++) { |
102 | regionMatrix[lineStart+x] = region; |
103 | regionPixels?.add(lineStart+x); |
104 | } |
105 | |
106 | // increase region size |
107 | rsize += xRight-xLeft; |
108 | |
109 | ifdef FastRegions_debug |
110 | printVars FastRegions(+region, +rsize, +y, +xLeft, +xRight); |
111 | endifdef |
112 | |
113 | // update bounds |
114 | if (xLeft < x1) x1 = xLeft; |
115 | if (xRight-1 > x2) x2 = xRight-1; |
116 | if (y < y1) y1 = y; |
117 | if (y > y2) y2 = y; |
118 | |
119 | // explore above & below |
120 | int xLeft2 = withDiagonals ? max(0, xLeft-1) : xLeft; |
121 | int xRight2 = withDiagonals ? min(w, xRight+1) : xRight; |
122 | if (y > 0) |
123 | addStreaks(lineStart-w, xLeft2, xRight2); |
124 | if (y < h-1) |
125 | addStreaks(lineStart+w, xLeft2, xRight2); |
126 | } |
127 | |
128 | regionSize?.add(rsize); |
129 | regionBounds?.addAll(x1, y1, x2+1, y2+1); |
130 | if (regionsBySize != null) { |
131 | int iBucket = dualLog(rsize); |
132 | var buffer = listGetOrCreate(regionsBySize, iBucket, -> new IntBuffer); |
133 | buffer.add(region); |
134 | } |
135 | |
136 | ret region; |
137 | } |
138 | |
139 | bool addable(int pos) { |
140 | if (regionMatrix[pos] != 0) false; // touching myself (or someone else) |
141 | if (getColor(pos) != currentColor) false; // wrong color |
142 | true; |
143 | } |
144 | |
145 | bool addToStack(int pos) { |
146 | if (!addable(pos)) false; |
147 | stack.add(pos); |
148 | true; |
149 | } |
150 | |
151 | void addStreaks(int lineStart, int xLeft, int xRight) { |
152 | int x = xLeft; |
153 | while (x < xRight) { |
154 | if (addToStack(lineStart+x)) |
155 | while (x+1 < xRight && addable(lineStart+x+1)) |
156 | ++x; |
157 | ++x; |
158 | } |
159 | } |
160 | |
161 | IBWImage regionsImage() { |
162 | ret iBWImageFromFunction((x, y) -> { |
163 | var region = regionMatrix[pos(x, y)]; |
164 | ret ((region-1)*regionStep) % (1.0+regionStep-0.0001); |
165 | }, w, h); |
166 | } |
167 | |
168 | public int regionCount aka nRegions() { ret regionCounter; } |
169 | |
170 | abstract class RegionIterator { |
171 | int pos; |
172 | |
173 | abstract bool next(); |
174 | |
175 | int pos() { ret pos; } |
176 | int x() { ret AbstractFastRegions.this.x(pos); } |
177 | int y() { ret AbstractFastRegions.this.y(pos); } |
178 | |
179 | int[] pixelsAsIntArray() { throw todo(); } |
180 | } |
181 | |
182 | // returns points in no particular order |
183 | class FloodRegionIterator > RegionIterator { |
184 | int region; |
185 | new IntBuffer stack; // locations as y*w+x |
186 | BitSet seen = new(size); |
187 | |
188 | *(int *region) { |
189 | int pos = regionFirstPixel.get(region); |
190 | printVars(+region, +pos); |
191 | seen.set(pos); |
192 | stack.add(pos); |
193 | } |
194 | |
195 | // flood-fill region |
196 | bool next() { |
197 | if (empty(stack)) false; |
198 | |
199 | pos = stack.popLast(); |
200 | |
201 | // explore neighborhood |
202 | int x = x(), y = y(); |
203 | if (x > 0) tryPosition(pos-1); |
204 | if (x < w-1) tryPosition(pos+1); |
205 | if (y > 0) tryPosition(pos-w); |
206 | if (y < h-1) tryPosition(pos+w); |
207 | |
208 | true; |
209 | } |
210 | |
211 | private void tryPosition(int p) { |
212 | if (!seen.get(p) && regionMatrix[p] == region) { |
213 | seen.set(p); |
214 | stack.add(p); |
215 | } |
216 | } |
217 | } |
218 | |
219 | class CachedRegionIterator > RegionIterator { |
220 | int i, to; |
221 | |
222 | *(int region) { |
223 | i = regionFirstPixel.get(region); |
224 | to = region+1 < l(regionFirstPixel) ? regionFirstPixel.get(region+1) : l(regionPixels); |
225 | |
226 | ifdef FastRegions_debug |
227 | printVars CachedRegionIterator(+region, +i, +to); |
228 | endifdef |
229 | } |
230 | |
231 | bool next() { |
232 | if (i >= to) false; |
233 | pos = regionPixels.get(i++); |
234 | true; |
235 | } |
236 | |
237 | int[] pixelsAsIntArray() { |
238 | ret regionPixels.subArray(i, to); |
239 | } |
240 | } |
241 | |
242 | int regionSize(int iRegion) { ret regionSize.get(iRegion); } |
243 | |
244 | Pt samplePixel aka firstPixel(int iRegion) { |
245 | ret pt(firstPixelPos(iRegion)); |
246 | } |
247 | |
248 | int firstPixelPos(int iRegion) { |
249 | int i = regionFirstPixel.get(iRegion); |
250 | ret regionPixels != null ? regionPixels.get(i) : i; |
251 | } |
252 | |
253 | bool inRegion(int iRegion, int x, int y) { |
254 | ret validPos(x, y) && regionMatrix[pos(x, y)] == iRegion; |
255 | } |
256 | Rect regionBounds(int iRegion) { ret rectFromPoints( |
257 | regionBounds.get((iRegion-1)*4), |
258 | regionBounds.get((iRegion-1)*4+1), |
259 | regionBounds.get((iRegion-1)*4+2), |
260 | regionBounds.get((iRegion-1)*4+3), |
261 | ); } |
262 | |
263 | int regionAt(Pt p) { ret regionAt(p.x, p.y); } |
264 | |
265 | int regionAt(int x, int y) { |
266 | ret !validPos(x, y) ? 0 : regionMatrix[pos(x, y)]; |
267 | } |
268 | |
269 | int regionAt(int pos) { |
270 | ret regionMatrix[pos]; |
271 | } |
272 | |
273 | RegionIterator regionIterator(int iRegion) { |
274 | ret regionPixels != null |
275 | ? new CachedRegionIterator(iRegion) |
276 | : new FloodRegionIterator(iRegion); |
277 | } |
278 | |
279 | L<Pt> regionPixels(int iRegion) { |
280 | var it = regionIterator(iRegion); |
281 | new L<Pt> l; |
282 | while (it.next()) |
283 | l.add(Pt(it.x(), it.y())); |
284 | ret l; |
285 | } |
286 | |
287 | // select extra features before regions are made |
288 | // (not necessary anymore) |
289 | |
290 | void collectFirstPixels { /*regionFirstPixel = new IntBuffer;*/ } |
291 | void collectBounds { /*regionBounds = new IntBuffer;*/ } |
292 | |
293 | BitMatrix regionBitMatrix(int iRegion) { |
294 | ret new AbstractBitMatrix(w, h) { |
295 | public Bool get(int x, int y) { |
296 | ret inRegion(iRegion, x, y); |
297 | } |
298 | }; |
299 | } |
300 | |
301 | void markRegionInPixelArray(int[] pixels, int iRegion, int rgba) { |
302 | if (iRegion <= 0) ret; |
303 | for i over pixels: |
304 | if (regionAt(i) == iRegion) |
305 | pixels[i] = rgba; |
306 | } |
307 | |
308 | L<Int> regionIndices() { ret virtualCountList(1, nRegions()+1); } |
309 | |
310 | ItIt<Int> regionsRoughlyByDecreasingSize() { |
311 | ret nestedIterator(countIterator_inclusive_backwards(regionsBySize.size()-1, 0), |
312 | iBucket -> iterator(regionsBySize.get(iBucket))); |
313 | } |
314 | |
315 | public IImageRegion<Img> getRegion aka get(int iRegion) { |
316 | ret new ImageRegion(iRegion); |
317 | } |
318 | |
319 | public L<IImageRegion<Img>> regions aka get() { |
320 | run(); |
321 | ret listFromFunction(i -> getRegion(i+1), nRegions()); |
322 | } |
323 | |
324 | record noeq ImageRegion(int iRegion) is IImageRegion<Img> { |
325 | public int hashCode() { ret iRegion; } |
326 | public bool equals(O o) { |
327 | if (!o instanceof AbstractFastRegions.ImageRegion) false; |
328 | ret ((AbstractFastRegions.ImageRegion) o).creator() == creator() |
329 | && ((AbstractFastRegions.ImageRegion) o).iRegion == iRegion; |
330 | } |
331 | |
332 | public Img image() { ret image; } |
333 | public O creator() { ret AbstractFastRegions.this; } |
334 | public int indexInCreator() { ret iRegion; } |
335 | public Bool createdWithDiagonals() { ret withDiagonals; } |
336 | |
337 | public Rect bounds() { ret regionBounds(iRegion); } |
338 | |
339 | public int numberOfPixels() { ret regionSize(iRegion); } |
340 | |
341 | public Pt firstPixel() { ret pt(firstPixelPos()); } |
342 | public int firstPixelPos() { ret AbstractFastRegions.this.firstPixelPos(iRegion); } |
343 | |
344 | public ItIt<Pt> pixelIterator() { |
345 | var it = regionIterator(iRegion); |
346 | ret iteratorFromFunction(-> it.next() ? pt(it.pos()) : null); |
347 | } |
348 | |
349 | public int[] pixelsAsIntArray() { |
350 | ret regionIterator(iRegion).pixelsAsIntArray(); |
351 | } |
352 | |
353 | public bool contains(int x, int y) { ret inRegion(iRegion, x, y); } |
354 | |
355 | public Color color() { |
356 | ret toColor(rgbForRegion(this)); |
357 | } |
358 | |
359 | public int brightness aka firstPixelLogicalColor() { |
360 | ret getColor(firstPixelPos()); |
361 | } |
362 | |
363 | toString { |
364 | ret renderRecordVars("Region", +brightness(), +color(), pixels := numberOfPixels(), +bounds()); |
365 | } |
366 | } |
367 | |
368 | abstract RGB rgbForRegion(ImageRegion r); |
369 | |
370 | public Img image() { ret image; } |
371 | |
372 | // called after scan was performed externally |
373 | void markDone { runner = size; } |
374 | } |
Began life as a copy of #1033761
download show line numbers debug dex old transpilations
Travelled to 4 computer(s): bhatertpkbcr, ekrmjmnbrukm, mowyntqkapby, mqqgnosmbjvj
No comments. add comment
Snippet ID: | #1034520 |
Snippet name: | AbstractFastRegions |
Eternal ID of this version: | #1034520/52 |
Text MD5: | 5c9487645191545f9cf345e44d010d08 |
Transpilation MD5: | 3b244090a24a6e429e40ec10a0a3c2f5 |
Author: | stefan |
Category: | javax / imaging |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2023-02-12 15:17:57 |
Source code size: | 10692 bytes / 374 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 333 / 837 |
Version history: | 51 change(s) |
Referenced in: | [show references] |