// Abstract base class for tiling an image (G22Tiling) abstract srecord noeq G22AbstractTiler(Img image) is Runnable, Steppable { delegate Tile to G22Tiling. int w, h, runner; gettable int size; // =w*h event tileStarted(GrowingTile tile); event tileDone(GrowingTile tile); // iterator that provides points to be looked at // (e.g. a WeightlessShuffledIterator) IndexIterator pointIterator; // Temporary stack of points to be looked at // (e.g. neighbors of a tile just discovered). // May or may not be used. new IntBuffer stack; G22Tiling tiling; // the tiling we are making int tileCounter; GrowingTile growingTile; // tile currently being grown bool verbose; // The one method subclasses need to override. // The exact meaning of a "color" is not defined, // the tiler just treats different int value as different colors. abstract int getColor(int pos); // calculations for positions (pixel index) int x(int pos) { ret pos % w; } int y(int pos) { ret pos / w; } int pos(int x, int y) { ret y*w+x; } Pt pt(int pos) { ret Pt(x(pos), y(pos)); } bool validPos(int x, int y) { ret x >= 0 && y >= 0 && x < w && y < h; } int getColor(int x, int y) { ret getColor(pos(x, y)); } class GrowingTile is Steppable { Tile tile; int color; // Still growing in any of the 4 directions? bool growingN = true; bool growingE = true; bool growingS = true; bool growingW = true; *(int pos) { int x = x(pos), y = y(pos); color = getColor(x, y); int iTile = tileCounter++; tile = tiling.new Tile(iTile, color, rect(x, y, 1, 1)); tiling.tiles.add(tile); } // put tile into tileMatrix void finish { Rect r = tile.position(); int rx = r.x, ry = r.y, rw = r.w, rh = r.h; int[] matrix = tiling.tileMatrix; int iTile = tile.index; for y to rh: for x to rw: { int pos = (ry+y)*w+(rx+x); if (matrix[pos] != 0) fail("Overlapping tiles!"); matrix[pos] = iTile+1; } tiling.pixelsCovered += rw*rh; } public bool step() { Rect r = nextRect(); if (r == null) { finish(); false; } tile.position(r); true; } // Is this pixel in the image and the tile's color // and not in a tile? bool isContinuation(int x, int y) { ret validPos(x, y) && isContinuation_impl(x, y); } bool isContinuation_impl(int x, int y) { ret tiling.tileMatrix[pos(x, y)] == 0 && getColor(x, y) == color; } // Is this rectangle completely in the image and the tile's color // and not in a tile? bool isContinuation(int x1, int y1, int w, int h) { if (!validPos(x1, y1) || !validPos(x1+w-1, y1+h-1)) false; for y to h: for x to w: if (!isContinuation_impl(x1+x, y1+y)) false; true; } Rect nextRect() { Rect r = tile.position(); // Check the 4 sides for possible growth growingN = growingN && isContinuation(r.x, r.y-1, r.w, 1); growingS = growingS && isContinuation(r.x, r.y2(), r.w, 1); growingW = growingW && isContinuation(r.x-1, r.y, 1, r.h); growingE = growingE && isContinuation(r.x2(), r.y, 1, r.h); // Check the corners too (including the adjacent sides) bool cornerNW = growingN && growingW && isContinuation(r.x-1, r.y-1); bool cornerNE = growingN && growingE && isContinuation(r.x2(), r.y-1); bool cornerSW = growingS && growingW && isContinuation(r.x-1, r.y2()); bool cornerSE = growingS && growingE && isContinuation(r.x2(), r.y2()); // Corners are the best case, so try those first if (cornerNW) { int x2 = r.x2(), y2 = r.y2(); // Default case: Grow to NE only if (cornerSW && cornerNE && cornerSE) { // Best case - grow in all 4 directions x2++; y2++; } else if (cornerNE) // At least grow to the right too x2++; else if (cornerSW) // At least grow to the south too y2++; ret rectFromPoints(r.x-1, r.y-1, x2, y2); } else if (cornerNE) { // Can't grow NW but will grow NE. So only south growth to decide int y2 = r.y2(); if (cornerSE) // Grow south too y2++; ret rectFromPoints(r.x, r.y-1, r.x2()+1, y2); } else if (cornerSW) { // Can't grow NW or NE but will grow SW. So only east growth to decide int x2 = r.x2(); if (cornerSE) // Grow east too x2++; ret rectFromPoints(r.x-1, r.y, x2, r.y2()+1); } else if (cornerSE) { // Only growable corner is SE, so just do it ret rectFromPoints(r.x, r.y, r.x2()+1, r.y2()+1); } else { // No growable corners. Try growing west/east or north/south if (growingW || growingE) ret rectFromPoints(r.x-(growingW ? 1 : 0), r.y, r.x2()+(growingE ? 1 : 0), r.y2()); else if (growingN || growingS) ret rectFromPoints(r.x, r.y-(growingN ? 1 : 0), r.x2(), r.y2()+(growingS ? 1 : 0)); } // No more growth possible null; } } run { stepAll(this); } public bool step() { init(); // Currently growing a tile? Continue that if (growingTile != null) { if (growingTile.step()) true; tileDone(growingTile); growingTile = null; // Done growing this tile } int pos = pointIterator.nextIndex(); if (pos < 0) false; // Is point already covered by a tile? if (tiling.tileMatrix[pos] != 0) true; // Create new tile growingTile = new GrowingTile(pos); tileStarted(growingTile); true; } void init { if (tiling != null) ret; w = image.getWidth(); h = image.getHeight(); size = w*h; tiling = new G22Tiling(image); tiling.initTileMatrix(); pointIterator = WeightlessShuffledIterator(size); } G22Tiling get() { if (tiling == null) run(); ret tiling; } }