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

225
LINES

< > BotCompany Repo | #1035023 // G22RegionThinner_v2 - thin a region to 1 pixel wide "skeleton" [working backup]

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

Libraryless. Click here for Pure Java version (12198L/70K).

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  
  // 0 = outside of region
12  
  // 1 = inner pixel
13  
  // 2 = border pixel
14  
  // index is in bounds coordinates
15  
  byte[] pixels;
16  
  
17  
  int idx(int x, int y) {
18  
    ret (y-bounds.y)*bounds.w+x-bounds.x;
19  
  }
20  
  int idx(Pt p) { ret idx(p.x, p.y); }
21  
  
22  
  Pt idxToPt(int idx) {
23  
    ret pt(bounds.x+(idx % bounds.w), bounds.y+idx/bounds.w);
24  
  }
25  
  
26  
  byte getPixel(Pt p) {
27  
    ret !containsPt(bounds, p) ? 0 : pixels[idx(p)];
28  
  }
29  
  
30  
  byte getPixel(int x, int y) {
31  
    ret !containsPt(bounds, x, y) ? 0 : pixels[idx(x, y)];
32  
  }
33  
  
34  
  bool contains(int x, int y) {
35  
    ret getPixel(x, y) != 0;
36  
  }
37  
  
38  
  bool clearPixel(int x, int y) {
39  
    if (!region.contains(x, y)) false;
40  
    pixels[idx(x, y)] = 0;
41  
    true;
42  
  }
43  
  
44  
  void init {
45  
    if (bounds != null) ret;
46  
    bounds = originalRegion.bounds();
47  
    pixels = new byte[area(bounds)];
48  
    for (Pt p : originalRegion.pixelIterator())
49  
      pixels[idx(p.x, p.y)] = 1;
50  
      
51  
    region = new ThinnedRegion;
52  
  }
53  
  
54  
  class ThinnedRegion is IImageRegion<Img> {
55  
    public Img image() { ret originalRegion.image(); }
56  
    public Rect bounds() { ret bounds; }
57  
  
58  
    public bool contains(int x, int y) {
59  
      ret containsPt(bounds, x, y) && pixels[idx(x, y)] > 0;
60  
    }
61  
    
62  
    public ItIt<Pt> pixelIterator() {
63  
      ret iff_null(new IF0<Pt> {
64  
        int idx = 0;
65  
        
66  
        public Pt get() {
67  
          for (; idx < pixels.length; idx++)
68  
            if (pixels[idx] > 0)
69  
              ret idxToPt(idx++);
70  
          null;
71  
        }
72  
      });
73  
    }
74  
  }
75  
  
76  
  public bool step() {
77  
    init();
78  
    
79  
    if (phase1done)
80  
      ret finalStep();
81  
    
82  
    L<PtBuffer> traces = g22_allBorderTraces_withDiagonals(region);
83  
    for (points : traces)
84  
      for (p : points)
85  
        pixels[idx(p)] = 2;
86  
        
87  
    new PtBuffer toDelete;
88  
89  
    for (points : traces) {
90  
      int nPoints = l(points);
91  
      BitSet deletable = emptyBitSet(nPoints);
92  
      for i to nPoints: {
93  
        ping();
94  
        if (deletableBorderPoint(points, i))
95  
          deletable.set(i);
96  
      }
97  
98  
      // handle special cases      
99  
      for i to nPoints:
100  
        if (cyclicGet(deletable, nPoints, i-1)
101  
          && !deletable.get(i)
102  
          && cyclicGet(deletable, nPoints, i+1)) {
103  
          Pt p = points.get(i);
104  
          Pt prev = ptMinus(cyclicGet(points, i-1), p);
105  
          Pt next = ptMinus(cyclicGet(points, i+1), p);
106  
          int dir1 = onePathLookupDirection(prev);
107  
          int dir2 = onePathLookupDirection(next);
108  
          int diff = mod(dir2-dir1, 8);
109  
          if (debug) printVars("special case", +p, +prev, +next, +dir1, +dir2, +diff);
110  
          if (diff == 1 || diff == 7)
111  
            deletable.set(i);
112  
        }
113  
114  
      for i to nPoints:
115  
        if (deletable.get(i))
116  
          toDelete.add(points.get(i));
117  
    }
118  
119  
    for (p : toDelete)
120  
      pixels[idx(p)] = 0;
121  
122  
    if (empty(toDelete))
123  
      set phase1done;
124  
    true;
125  
  }
126  
  
127  
  bool deletableBorderPoint(PtBuffer points, int i) {
128  
    L<Pt> range = cyclicSubList_incl(points, i-3, i+3);
129  
    Pt p = points.get(i);
130  
    
131  
    // special case
132  
    if (eq(range.get(1), p) || eq(range.get(5), p)) false;
133  
    
134  
    int surroundingBorderPixels = 0, surroundingInnerPixels = 0;
135  
    for (int dir = 1; dir <= 8; dir++) {
136  
      Pt p2 = ptPlus(p, onePathDirection(dir));
137  
      byte value = getPixel(p2);
138  
      if (value == 2 && !range.contains(p2))
139  
        surroundingBorderPixels++;
140  
      else if (value == 1)
141  
        surroundingInnerPixels++;
142  
    }
143  
    
144  
    bool deletable = surroundingInnerPixels > 0 && surroundingBorderPixels == 0;
145  
      
146  
    printVars ifdef G22RegionThinner_debug(+p, +surroundingInnerPixels, +surroundingBorderPixels, +deletable, +range);
147  
    ret deletable;
148  
  }
149  
  
150  
  IImageRegion region aka get() { ret or(region, originalRegion); }
151  
  
152  
  // go from 2 pixels wide to 1 pixel wide (TODO)
153  
  bool finalStep() {
154  
    if (!doFinalStep) false;
155  
    
156  
    bool change;
157  
    int x1 = bounds.x, x2 = bounds.x2();
158  
    int y1 = bounds.y, y2 = bounds.y2();
159  
    printVars ifdef G22RegionThinner_lastPhase_debug("finalStep", +x1, +y1, +x2, +y2);
160  
    
161  
    for (int x = x1; x < x2; x++)
162  
      for (int y = y1; y < y2; y++) {
163  
        // need a set pixel
164  
        if (!contains(x, y)) continue;
165  
166  
        // check if this pixel is essential to hold the structure
167  
        // together by doing a floodfill in the 3x3 neighborhood
168  
        // (simulating the pixel being cleared already)
169  
        
170  
        reMutable x; reMutable y;
171  
        var bwImage = bwImageFromFunction(3, (xx, yy) ->
172  
          (xx != 1 || yy != 1) && contains(x+xx-1, y+yy-1) ? 0 : 1);
173  
        FastRegions_BWImage regionMaker = new(bwImage);
174  
        regionMaker.withDiagonals(true);
175  
        regionMaker.run();
176  
        
177  
        // get all the black regions out of the 3x3 image
178  
        var regions = regionMaker.regions();
179  
        regions = filter(regions, r -> r.brightness() == 0);
180  
        
181  
        bool delete = false;
182  
        int pixels = 0;
183  
        
184  
        // no regions? it's a lonely pixel - keep it
185  
        if (empty(regions)) {}
186  
        else if (l(regions) == 1) {
187  
          // one region
188  
          
189  
          pixels = first(regions).numberOfPixels();
190  
          // if it's only one or two pixels, we are at the end of a line - keep
191  
          if (pixels <= lastPhaseThreshold1)
192  
            delete = false;
193  
          else {
194  
            // delete pixel if the region is small, but not too small
195  
            // (lastPhaseThreshold is 5 by default)
196  
            if (pixels < lastPhaseThreshold)
197  
              delete = true;
198  
            else if (pixels == lastPhaseThreshold) {
199  
              // if it's exactly 5 pixels, check out how far
200  
              // the region "surrounds" the pixel (does it touch 2 or 3 sides?)
201  
              // delete pixel if it is not surrounded
202  
              
203  
              int sides = 4-(int)
204  
                (bwImage.getFloatPixel(1, 0)
205  
                + bwImage.getFloatPixel(0, 1)
206  
                + bwImage.getFloatPixel(2, 1)
207  
                + bwImage.getFloatPixel(1, 2));
208  
              delete = sides < 3;
209  
            }
210  
          }
211  
        } else
212  
          // we have more than one region - pixel was
213  
          // structurally important, keep it
214  
          {}
215  
        
216  
        printVars ifdef G22RegionThinner_lastPhase_debug(
217  
          +x, +y, bwImage := escapeNewLines(bwImageToString(bwImage)),
218  
          regions := l(regions), +pixels, +delete);
219  
        
220  
        if (delete)
221  
          change |= clearPixel(x, y);
222  
      }
223  
    ret change;
224  
  }
225  
}

Author comment

Began life as a copy of #1034983

download  show line numbers  debug dex  old transpilations   

Travelled to 3 computer(s): bhatertpkbcr, mowyntqkapby, mqqgnosmbjvj

No comments. add comment

Snippet ID: #1035023
Snippet name: G22RegionThinner_v2 - thin a region to 1 pixel wide "skeleton" [working backup]
Eternal ID of this version: #1035023/3
Text MD5: 8ded964987c73e3f228bfec5f166c024
Transpilation MD5: 5355b296a02f3f66e13741943beb474d
Author: stefan
Category: javax
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2022-03-23 02:37:13
Source code size: 6941 bytes / 225 lines
Pitched / IR pitched: No / No
Views / Downloads: 65 / 98
Version history: 2 change(s)
Referenced in: [show references]