srecord noeq G22RegionThinner_v2(IImageRegion originalRegion) is Steppable {
Rect bounds;
IImageRegion region;
bool phase1done;
settable bool debug;
settable bool doFinalStep = true;
settable int lastPhaseThreshold1 = 2;
settable int lastPhaseThreshold = 5;
settable byte[] lookupTable;
// 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(long p) {
ret getPixel(firstIntFromLong(p), secondIntFromLong(p));
}
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 {
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 pixelIterator() {
ret iff_null(new IF0 {
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 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: {
bool del = deletableBorderPoint(points, i);
/*bool del2 = deletableBorderPoint_old(points, i);
if (del != del2)
failWithVars("deletableBorderPoint", +i, +nPoints, +del);*/
if (del)
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 range = cyclicSubList_incl(points, i-3, i+3);
LongBuffer pointsBuf = points.buf;
long p_long = pointsBuf.get(i);
// special case
if (p_long == cyclicGet(pointsBuf, i-2)
|| p_long == cyclicGet(pointsBuf, i+2)) {
printVars ifdef G22RegionThinner_debug("special case", p := ptFromLong(p_long));
false;
}
int surroundingBorderPixels = 0, surroundingInnerPixels = 0;
for (int dir = 1; dir <= 8; dir++) {
long p2_long = onePathDirection_long(p_long, dir);
byte value = getPixel(p2_long);
if (value == 2 && !rangeContains(pointsBuf, i, p2_long)) {
printVars ifdef G22RegionThinner_debug("surroundingBorderPixel", p2 := ptFromLong(p2_long));
//surroundingBorderPixels++;
false;
} else if (value == 1) {
printVars ifdef G22RegionThinner_debug("surroundingInnerPixel", p2 := ptFromLong(p2_long));
surroundingInnerPixels++;
}
}
ret surroundingInnerPixels > 0;
//bool deletable = surroundingInnerPixels > 0 && surroundingBorderPixels == 0;
//printVars ifdef G22RegionThinner_debug(+p, +surroundingInnerPixels, +surroundingBorderPixels, +deletable, +range);
//ret deletable;
}
bool rangeContains(LongBuffer pointsBuf, int i, long p2_long) {
for (int j = i-3; j <= i+3; j++)
if (cyclicGet(pointsBuf, j) == p2_long)
true;
false;
}
bool deletableBorderPoint_old(PtBuffer points, int i) {
L 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;
if (lookupTable == null)
lookupTable = new G22RegionThinner_LookupTable()
.lastPhaseThreshold(lastPhaseThreshold)
.lastPhaseThreshold1(lastPhaseThreshold1)!;
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);
var pingSource = pingSource();
for (int y = y1; y < y2; y++) {
ping(pingSource);
for (int x = x1; x < x2; x++) {
// 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)
var imgPattern = ubyteFromBits(
contains(x-1, y-1),
contains(x, y-1),
contains(x+1, y-1),
contains(x-1, y),
contains(x+1, y),
contains(x-1, y+1),
contains(x, y+1),
contains(x+1, y+1));
bool delete = getBit(lookupTable, imgPattern);
printVars ifdef G22RegionThinner_lastPhase_debug(
+x, +y, +imgPattern, +pixels, +delete);
if (delete)
change |= clearPixel(x, y);
}
}
ret change;
}
}