srecord noeq BWImage_FastRegions(BWImage image) is Runnable { int w, h, size, runner; new IntBuffer stack; // locations as y*w+x int[] regionMatrix; // for each pixel: region index (starting at 1) new IntStack regionFirstPixel; // for each region: index of first pixel found int regionCounter; bool verbose; double regionStep = .1; // for rendering in regionsImage int x(int pos) { ret pos % w; } int y(int pos) { ret pos / w; } int pos(int x, int y) { ret y*w+x; } int getColor(int pos) { ret image.getInt(x(pos), y(pos)); } run { w = image.getWidth(); h = image.getHeight(); size = w*h; regionMatrix = new int[size]; regionFirstPixel.add(0); // unused while (runner < size) { if (regionMatrix[runner] == 0) { // make a new region, get color int region = ++regionCounter; regionFirstPixel[region] = runner; stack.add(runner); int color = getColor(runner); // flood-fill region while (nempty(stack)) { int pos = stack.popLast(); if (regionMatrix[pos] != 0) continue; // touching myself (or someone else) if (getColor(pos) != color) continue; // wrong color // new pixel found, mark as ours regionMatrix[pos] = region; // explore neighborhood int x = x(pos), y = y(pos); if (x > 0) stack.add(pos-1); if (x < w-1) stack.add(pos+1); if (y > 0) stack.add(pos-w); if (y < h-1) stack.add(pos+w); } } ++runner; } } IBWImage regionsImage() { ret iBWImageFromFunction((x, y) -> { var region = regionMatrix[pos(x, y)]; ret ((region-1)*regionStep) % (1.0+regionStep-0.0001); }, w, h); } int regionCount() { ret regionCounter; } }