Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

374
LINES

< > BotCompany Repo | #1034520 // AbstractFastRegions

JavaX fragment (include) [tags: use-pretranspiled]

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  
}

Author comment

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]