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

292
LINES

< > BotCompany Repo | #1034983 // G22RegionThinner_v2 - thin a region to 1 pixel wide [OK]

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

Transpiled version (13958L) is out of date.

1  
srecord noeq G22RegionThinner_v2<Img extends WidthAndHeight>(IImageRegion<Img> originalRegion) is Steppable {
2  
  Rect bounds;
3  
  IImageRegion<Img> region;
4  
  bool phase1done;
5  
  
6  
  settable bool debug;
7  
  settable bool doFinalStep = true;
8  
  settable int lastPhaseThreshold1 = 2;
9  
  settable int lastPhaseThreshold = 5;
10  
  
11  
  settable byte[] lookupTable;
12  
  
13  
  // 0 = outside of region
14  
  // 1 = inner pixel
15  
  // 2 = border pixel
16  
  // index is in bounds coordinates
17  
  byte[] pixels;
18  
  
19  
  int idx(int x, int y) {
20  
    ret (y-bounds.y)*bounds.w+x-bounds.x;
21  
  }
22  
  int idx(Pt p) { ret idx(p.x, p.y); }
23  
  
24  
  Pt idxToPt(int idx) {
25  
    ret pt(bounds.x+(idx % bounds.w), bounds.y+idx/bounds.w);
26  
  }
27  
  
28  
  byte getPixel(long p) {
29  
    ret getPixel(firstIntFromLong(p), secondIntFromLong(p));
30  
  }
31  
  
32  
  byte getPixel(Pt p) {
33  
    ret !containsPt(bounds, p) ? 0 : pixels[idx(p)];
34  
  }
35  
  
36  
  byte getPixel(int x, int y) {
37  
    ret !containsPt(bounds, x, y) ? 0 : pixels[idx(x, y)];
38  
  }
39  
  
40  
  bool contains(int x, int y) {
41  
    ret getPixel(x, y) != 0;
42  
  }
43  
  
44  
  bool clearPixel(int x, int y) {
45  
    if (!region.contains(x, y)) false;
46  
    pixels[idx(x, y)] = 0;
47  
    true;
48  
  }
49  
  
50  
  void init {
51  
    if (bounds != null) ret;
52  
    bounds = originalRegion.bounds();
53  
    pixels = new byte[area(bounds)];
54  
    for (Pt p : originalRegion.pixelIterator())
55  
      pixels[idx(p.x, p.y)] = 1;
56  
      
57  
    region = new ThinnedRegion;
58  
  }
59  
  
60  
  class ThinnedRegion is IImageRegion<Img> {
61  
    public Img image() { ret originalRegion.image(); }
62  
    public Rect bounds() { ret bounds; }
63  
  
64  
    public bool contains(int x, int y) {
65  
      ret containsPt(bounds, x, y) && pixels[idx(x, y)] > 0;
66  
    }
67  
    
68  
    public ItIt<Pt> pixelIterator() {
69  
      ret iff_null(new IF0<Pt> {
70  
        int idx = 0;
71  
        
72  
        public Pt get() {
73  
          for (; idx < pixels.length; idx++)
74  
            if (pixels[idx] > 0)
75  
              ret idxToPt(idx++);
76  
          null;
77  
        }
78  
      });
79  
    }
80  
  }
81  
  
82  
  public bool step() {
83  
    init();
84  
    
85  
    if (phase1done)
86  
      ret finalStep();
87  
    
88  
    L<PtBuffer> traces = g22_allBorderTraces_withDiagonals(region);
89  
    for (points : traces)
90  
      for (p : points)
91  
        pixels[idx(p)] = 2;
92  
        
93  
    new PtBuffer toDelete;
94  
95  
    for (points : traces) {
96  
      int nPoints = l(points);
97  
      BitSet deletable = emptyBitSet(nPoints);
98  
      for i to nPoints: {
99  
        bool del = deletableBorderPoint(points, i);
100  
        /*bool del2 = deletableBorderPoint_old(points, i);
101  
        if (del != del2)
102  
          failWithVars("deletableBorderPoint", +i, +nPoints, +del);*/
103  
        if (del)
104  
          deletable.set(i);
105  
      }
106  
107  
      // handle special cases
108  
      // (preventing single isolated pixels from being left standing)
109  
      
110  
      for i to nPoints:
111  
        if (cyclicGet(deletable, nPoints, i-1)
112  
          && !deletable.get(i)
113  
          && cyclicGet(deletable, nPoints, i+1)) {
114  
          Pt p = points.get(i);
115  
          Pt prev = ptMinus(cyclicGet(points, i-1), p);
116  
          Pt next = ptMinus(cyclicGet(points, i+1), p);
117  
          int dir1 = onePathLookupDirection(prev);
118  
          int dir2 = onePathLookupDirection(next);
119  
          int diff = mod(dir2-dir1, 8);
120  
          if (debug) printVars("special case", +p, +prev, +next, +dir1, +dir2, +diff);
121  
          if (diff == 1 || diff == 7)
122  
            deletable.set(i);
123  
        }
124  
125  
      for i to nPoints:
126  
        if (deletable.get(i))
127  
          toDelete.add(points.get(i));
128  
    }
129  
130  
    for (p : toDelete)
131  
      pixels[idx(p)] = 0;
132  
133  
    if (empty(toDelete))
134  
      set phase1done;
135  
    true;
136  
  }
137  
  
138  
  bool deletableBorderPoint(PtBuffer points, int i) {
139  
    //L<Pt> range = cyclicSubList_incl(points, i-3, i+3);
140  
    LongBuffer pointsBuf = points.buf;
141  
    long p_long = pointsBuf.get(i);
142  
    
143  
    // special case (sharp corner)
144  
    
145  
    if (p_long == cyclicGet(pointsBuf, i-2)
146  
      || p_long == cyclicGet(pointsBuf, i+2)) {
147  
      printVars ifdef G22RegionThinner_debug("special case", p := ptFromLong(p_long));
148  
      false;
149  
    }
150  
    
151  
    // Basic logic:
152  
    // a pixel is deletable if at least one inner pixel is next to it
153  
    // and no "foreign" border pixels are next to it.
154  
    
155  
    // This leaves diagonal lines two pixels wide though
156  
    // which can lead to them eventually shrinking down to nothing
157  
    // (the "k" problem).
158  
    // Idea to fix: Ignore foreign border pixels in certain directions
159  
    
160  
    int /*surroundingBorderPixels = 0,*/ surroundingInnerPixels = 0;
161  
    int foreignBorderPixels = 0;
162  
    for (int dir = 1; dir <= 8; dir++) {
163  
      long p2_long = onePathDirection_long(p_long, dir);
164  
      byte value = getPixel(p2_long);
165  
      if (value == 2 && !rangeContains(pointsBuf, i, p2_long)) {
166  
        // found a "foreign" border pixel
167  
        //surroundingBorderPixels++;
168  
        
169  
        bool fix = dir == 5 || dir == 7;
170  
        
171  
        printVars ifdef G22RegionThinner_debug("surroundingBorderPixel", p := ptFromLong(p_long), +dir, p2 := ptFromLong(p2_long), +fix);
172  
        // FIX (dev.)
173  
        //if (dir < 4) false;
174  
        if (!fix) false;
175  
        ifdef G22RegionThinner_debug
176  
          foreignBorderPixels |= 1 << dir;
177  
        endifdef
178  
      } else if (value == 1) {
179  
        printVars ifdef G22RegionThinner_debug("surroundingInnerPixel", p := ptFromLong(p_long), +dir, p2 := ptFromLong(p2_long));
180  
        surroundingInnerPixels++;
181  
      }
182  
    }
183  
184  
    bool deletable = /*foreignBorderPixels == 0 &&*/ surroundingInnerPixels > 0;
185  
    
186  
    // Fixes for certain problems follow.
187  
    // Generally, if it's a certain pattern, the pixel must not be deleted.
188  
    int imgPattern = neighborhoodPattern(firstIntFromLong(p_long), secondIntFromLong(p_long));
189  
    if (imgPattern == 0b10011100 // "E" problem
190  
     || imgPattern == 0b01000111 // "ae" problem
191  
     || imgPattern == 0b00111001 // "a" problem
192  
     || imgPattern == 0b00111111 // "a" problem 2
193  
     || imgPattern == 0b10111101 // "k" problem
194  
      ) deletable = false;
195  
      
196  
    // These are fixes to delete a pixel.
197  
    else if (imgPattern == 0b11010110 // "&" problem (2x3 block)
198  
      ) deletable = true;
199  
200  
    printVars ifdef G22RegionThinner_debug(p := ptFromLong(p_long), +foreignBorderPixels, +surroundingInnerPixels, +deletable,
201  
      imgPattern := ubyteToBinary(imgPattern));
202  
    // FIX v2
203  
    /*if (foreignBorderPixels == ((1 << 4) | (1 << 5) | (1 << 6)))
204  
      true;*/
205  
      
206  
    ret deletable;
207  
  }
208  
  
209  
  bool rangeContains(LongBuffer pointsBuf, int i, long p2_long) {
210  
    for (int j = i-3; j <= i+3; j++)
211  
      if (cyclicGet(pointsBuf, j) == p2_long)
212  
        true;
213  
    false;
214  
  }
215  
  
216  
  bool deletableBorderPoint_old(PtBuffer points, int i) {
217  
    L<Pt> range = cyclicSubList_incl(points, i-3, i+3);
218  
    Pt p = points.get(i);
219  
    
220  
    // special case
221  
    if (eq(range.get(1), p) || eq(range.get(5), p)) false;
222  
    
223  
    int surroundingBorderPixels = 0, surroundingInnerPixels = 0;
224  
    for (int dir = 1; dir <= 8; dir++) {
225  
      Pt p2 = ptPlus(p, onePathDirection(dir));
226  
      byte value = getPixel(p2);
227  
      if (value == 2 && !range.contains(p2))
228  
        surroundingBorderPixels++;
229  
      else if (value == 1)
230  
        surroundingInnerPixels++;
231  
    }
232  
    
233  
    bool deletable = surroundingInnerPixels > 0 && surroundingBorderPixels == 0;
234  
      
235  
    printVars ifdef G22RegionThinner_debug(+p, +surroundingInnerPixels, +surroundingBorderPixels, +deletable, +range);
236  
    ret deletable;
237  
  }
238  
  
239  
  IImageRegion region aka get() { ret or(region, originalRegion); }
240  
  
241  
  // go from 2 pixels wide to 1 pixel wide (TODO)
242  
  bool finalStep() {
243  
    if (!doFinalStep) false;
244  
    
245  
    if (lookupTable == null)
246  
      lookupTable = new G22RegionThinner_LookupTable()
247  
        .lastPhaseThreshold(lastPhaseThreshold)
248  
        .lastPhaseThreshold1(lastPhaseThreshold1)!;
249  
    
250  
    bool change;
251  
    int x1 = bounds.x, x2 = bounds.x2();
252  
    int y1 = bounds.y, y2 = bounds.y2();
253  
    printVars ifdef G22RegionThinner_lastPhase_debug("finalStep", +x1, +y1, +x2, +y2);
254  
    
255  
    var pingSource = pingSource();
256  
    
257  
    for (int y = y1; y < y2; y++) {
258  
      ping(pingSource);
259  
      for (int x = x1; x < x2; x++) {
260  
        // need a set pixel
261  
        if (!contains(x, y)) continue;
262  
        
263  
        int imgPattern = neighborhoodPattern(x, y);
264  
265  
        // check if this pixel is essential to hold the structure
266  
        // together by doing a floodfill in the 3x3 neighborhood
267  
        // (simulating the pixel being cleared already)
268  
        
269  
        bool delete = getBit(lookupTable, imgPattern);
270  
        
271  
        printVars ifdef G22RegionThinner_lastPhase_debug(
272  
          +x, +y, +imgPattern, +pixels, +delete);
273  
        
274  
        if (delete)
275  
          change |= clearPixel(x, y);
276  
      }
277  
    }
278  
    ret change;
279  
  }
280  
281  
  int neighborhoodPattern(int x, int y) {  
282  
    ret ubyteFromBits(
283  
      contains(x-1, y-1),
284  
      contains(x,   y-1),
285  
      contains(x+1, y-1),
286  
      contains(x-1, y),
287  
      contains(x+1, y),
288  
      contains(x-1, y+1),
289  
      contains(x,   y+1),
290  
      contains(x+1, y+1));
291  
  }
292  
}

Author comment

Began life as a copy of #1034965

download  show line numbers  debug dex  old transpilations   

Travelled to 4 computer(s): bhatertpkbcr, ekrmjmnbrukm, mowyntqkapby, mqqgnosmbjvj

No comments. add comment

Snippet ID: #1034983
Snippet name: G22RegionThinner_v2 - thin a region to 1 pixel wide [OK]
Eternal ID of this version: #1034983/94
Text MD5: 451e5ad93e87b23fd3ed325d33be5823
Author: stefan
Category: javax
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2022-04-30 21:06:05
Source code size: 9152 bytes / 292 lines
Pitched / IR pitched: No / No
Views / Downloads: 230 / 617
Version history: 93 change(s)
Referenced in: [show references]