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).

srecord noeq G22RegionThinner_v2<Img extends WidthAndHeight>(IImageRegion<Img> originalRegion) is Steppable {
  Rect bounds;
  IImageRegion<Img> region;
  bool phase1done;
  
  settable bool debug;
  settable bool doFinalStep = true;
  settable int lastPhaseThreshold1 = 2;
  settable int lastPhaseThreshold = 5;
  
  // 0 = outside of region
  // 1 = inner pixel
  // 2 = border pixel
  // index is in bounds coordinates
  byte[] pixels;
  
  int idx(int x, int y) {
    ret (y-bounds.y)*bounds.w+x-bounds.x;
  }
  int idx(Pt p) { ret idx(p.x, p.y); }
  
  Pt idxToPt(int idx) {
    ret pt(bounds.x+(idx % bounds.w), bounds.y+idx/bounds.w);
  }
  
  byte getPixel(Pt p) {
    ret !containsPt(bounds, p) ? 0 : pixels[idx(p)];
  }
  
  byte getPixel(int x, int y) {
    ret !containsPt(bounds, x, y) ? 0 : pixels[idx(x, y)];
  }
  
  bool contains(int x, int y) {
    ret getPixel(x, y) != 0;
  }
  
  bool clearPixel(int x, int y) {
    if (!region.contains(x, y)) false;
    pixels[idx(x, y)] = 0;
    true;
  }
  
  void init {
    if (bounds != null) ret;
    bounds = originalRegion.bounds();
    pixels = new byte[area(bounds)];
    for (Pt p : originalRegion.pixelIterator())
      pixels[idx(p.x, p.y)] = 1;
      
    region = new ThinnedRegion;
  }
  
  class ThinnedRegion is IImageRegion<Img> {
    public Img image() { ret originalRegion.image(); }
    public Rect bounds() { ret bounds; }
  
    public bool contains(int x, int y) {
      ret containsPt(bounds, x, y) && pixels[idx(x, y)] > 0;
    }
    
    public ItIt<Pt> pixelIterator() {
      ret iff_null(new IF0<Pt> {
        int idx = 0;
        
        public Pt get() {
          for (; idx < pixels.length; idx++)
            if (pixels[idx] > 0)
              ret idxToPt(idx++);
          null;
        }
      });
    }
  }
  
  public bool step() {
    init();
    
    if (phase1done)
      ret finalStep();
    
    L<PtBuffer> traces = g22_allBorderTraces_withDiagonals(region);
    for (points : traces)
      for (p : points)
        pixels[idx(p)] = 2;
        
    new PtBuffer toDelete;

    for (points : traces) {
      int nPoints = l(points);
      BitSet deletable = emptyBitSet(nPoints);
      for i to nPoints: {
        ping();
        if (deletableBorderPoint(points, i))
          deletable.set(i);
      }

      // handle special cases      
      for i to nPoints:
        if (cyclicGet(deletable, nPoints, i-1)
          && !deletable.get(i)
          && cyclicGet(deletable, nPoints, i+1)) {
          Pt p = points.get(i);
          Pt prev = ptMinus(cyclicGet(points, i-1), p);
          Pt next = ptMinus(cyclicGet(points, i+1), p);
          int dir1 = onePathLookupDirection(prev);
          int dir2 = onePathLookupDirection(next);
          int diff = mod(dir2-dir1, 8);
          if (debug) printVars("special case", +p, +prev, +next, +dir1, +dir2, +diff);
          if (diff == 1 || diff == 7)
            deletable.set(i);
        }

      for i to nPoints:
        if (deletable.get(i))
          toDelete.add(points.get(i));
    }

    for (p : toDelete)
      pixels[idx(p)] = 0;

    if (empty(toDelete))
      set phase1done;
    true;
  }
  
  bool deletableBorderPoint(PtBuffer points, int i) {
    L<Pt> range = cyclicSubList_incl(points, i-3, i+3);
    Pt p = points.get(i);
    
    // special case
    if (eq(range.get(1), p) || eq(range.get(5), p)) false;
    
    int surroundingBorderPixels = 0, surroundingInnerPixels = 0;
    for (int dir = 1; dir <= 8; dir++) {
      Pt p2 = ptPlus(p, onePathDirection(dir));
      byte value = getPixel(p2);
      if (value == 2 && !range.contains(p2))
        surroundingBorderPixels++;
      else if (value == 1)
        surroundingInnerPixels++;
    }
    
    bool deletable = surroundingInnerPixels > 0 && surroundingBorderPixels == 0;
      
    printVars ifdef G22RegionThinner_debug(+p, +surroundingInnerPixels, +surroundingBorderPixels, +deletable, +range);
    ret deletable;
  }
  
  IImageRegion region aka get() { ret or(region, originalRegion); }
  
  // go from 2 pixels wide to 1 pixel wide (TODO)
  bool finalStep() {
    if (!doFinalStep) false;
    
    bool change;
    int x1 = bounds.x, x2 = bounds.x2();
    int y1 = bounds.y, y2 = bounds.y2();
    printVars ifdef G22RegionThinner_lastPhase_debug("finalStep", +x1, +y1, +x2, +y2);
    
    for (int x = x1; x < x2; x++)
      for (int y = y1; y < y2; y++) {
        // need a set pixel
        if (!contains(x, y)) continue;

        // check if this pixel is essential to hold the structure
        // together by doing a floodfill in the 3x3 neighborhood
        // (simulating the pixel being cleared already)
        
        reMutable x; reMutable y;
        var bwImage = bwImageFromFunction(3, (xx, yy) ->
          (xx != 1 || yy != 1) && contains(x+xx-1, y+yy-1) ? 0 : 1);
        FastRegions_BWImage regionMaker = new(bwImage);
        regionMaker.withDiagonals(true);
        regionMaker.run();
        
        // get all the black regions out of the 3x3 image
        var regions = regionMaker.regions();
        regions = filter(regions, r -> r.brightness() == 0);
        
        bool delete = false;
        int pixels = 0;
        
        // no regions? it's a lonely pixel - keep it
        if (empty(regions)) {}
        else if (l(regions) == 1) {
          // one region
          
          pixels = first(regions).numberOfPixels();
          // if it's only one or two pixels, we are at the end of a line - keep
          if (pixels <= lastPhaseThreshold1)
            delete = false;
          else {
            // delete pixel if the region is small, but not too small
            // (lastPhaseThreshold is 5 by default)
            if (pixels < lastPhaseThreshold)
              delete = true;
            else if (pixels == lastPhaseThreshold) {
              // if it's exactly 5 pixels, check out how far
              // the region "surrounds" the pixel (does it touch 2 or 3 sides?)
              // delete pixel if it is not surrounded
              
              int sides = 4-(int)
                (bwImage.getFloatPixel(1, 0)
                + bwImage.getFloatPixel(0, 1)
                + bwImage.getFloatPixel(2, 1)
                + bwImage.getFloatPixel(1, 2));
              delete = sides < 3;
            }
          }
        } else
          // we have more than one region - pixel was
          // structurally important, keep it
          {}
        
        printVars ifdef G22RegionThinner_lastPhase_debug(
          +x, +y, bwImage := escapeNewLines(bwImageToString(bwImage)),
          regions := l(regions), +pixels, +delete);
        
        if (delete)
          change |= clearPixel(x, y);
      }
    ret change;
  }
}

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: 133 / 184
Version history: 2 change(s)
Referenced in: [show references]