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