// fills fully transparent pixels in the image with // the color of the (roughly) closest non-transparent neighbor pixel sclass ImageInfiller { settable BufferedImage inputImage; // how far to infill (pixels) settable int maxDepth = 2; // whether to make the infilled pixels (semi-) transparent settable int infillOpacity = 255; settable bool verbose; int w, h; int[] pixels; IntBuffer queue; // pixel indices for current round IntBuffer nextQueue; // pixel indices for next round BufferedImage outputImage; gettable int rounds; gettable int filledPixels; *() {} *(BufferedImage *inputImage) {} *(BufferedImage *inputImage, int *maxDepth) {} run { prepare(); while (!done()) oneRound(); } bool prepared() { ret pixels != null; } bool done() { ret nextQueue != null && nextQueue.isEmpty() || rounds >= maxDepth; } BufferedImage get() { run(); ret pixelsToBufferedImage(pixels, w); } void prepare { if (pixels != null) ret; pixels = pixelsFromBufferedImage(inputImage); w = inputImage.getWidth(); h = inputImage.getHeight(); } void oneRound { ++rounds; queue = nextQueue; nextQueue = new IntBuffer; new BitSet queued; assertBetween("infillOpacity", 1, 255, infillOpacity); new IntBuffer toFill; // pixel index, color, pixel index, color, ... for (pos : queue == null ? countIterator(w*h) : queue) { int rgba = pixels[pos]; if (!isFullyTransparent(rgba)) continue; int x = pos % w, y = pos/w; new MultiSet surroundingColors; for (int y2 = max(0, y-1); y2 < min(h, y+2); y2++) for (int x2 = max(0, x-1); x2 < min(w, x+2); x2++) { rgba = pixels[y2*w+x2]; if (!isFullyTransparent(rgba)) surroundingColors.add(withOpacity(infillOpacity, rgba)); } if (!surroundingColors.isEmpty()) { // Surrounding color found, fill pixel toFill.add(y*w+x); toFill.add(surroundingColors.mostPopular()); filledPixels++; // Schedule surrounding transparent pixels for (int y2 = max(0, y-1); y2 < min(h, y+2); y2++) for (int x2 = max(0, x-1); x2 < min(w, x+2); x2++) { int pos2 = y2*w+x2; if (!queued.get(pos2) && isFullyTransparent(pixels[pos2])) { queued.set(pos2); nextQueue.add(pos2); } } } } queue = null; int n = l(toFill); for (int i = 0; i < n; i += 2) pixels[toFill.get(i)] = toFill.get(i+1); if (verbose) print("Round " + rounds + " pixels filled: " + n2(filledPixels) + ", nextQueue: " + l(nextQueue)); } selfType unlimited() { maxDepth(Int.MAX_VALUE); this; } void eraseOriginalPixels { run(); if (infillOpacity >= 255) fail("Need semi-transparency to erase original pixels"); int[] pixels = this.pixels; for i over pixels: if (!isTransparent(pixels[i])) pixels[i] = 0; } // works only after at least one round was run int nRemainingTransparentPixels() { ret nextQueue.size(); } }