package com.badlogic.gdx.tools.imagepacker; import com.badlogic.gdx.tools.imagepacker.ColorBleedEffect.Mask.MaskIterator; import java.awt.image.BufferedImage; import java.util.NoSuchElementException; /** @author Ruben Garat * @author Ariel Coppes * @author Nathan Sweet */ public class ColorBleedEffect { static int TO_PROCESS = 0; static int IN_PROCESS = 1; static int REALDATA = 2; static int[][] offsets = { {-1, -1}, {0, -1}, {1, -1}, {-1, 0}, {1, 0}, {-1, 1}, {0, 1}, {1, 1}}; ARGBColor color = new ARGBColor(); public BufferedImage processImage (BufferedImage image, int maxIterations) { int width = image.getWidth(); int height = image.getHeight(); BufferedImage processedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); int[] rgb = image.getRGB(0, 0, width, height, null, 0, width); Mask mask = new Mask(rgb); int iterations = 0; int lastPending = -1; while (mask.pendingSize > 0 && mask.pendingSize != lastPending && iterations < maxIterations) { lastPending = mask.pendingSize; executeIteration(rgb, mask, width, height); iterations++; } processedImage.setRGB(0, 0, width, height, rgb, 0, width); return processedImage; } private void executeIteration (int[] rgb, Mask mask, int width, int height) { MaskIterator iterator = mask.new MaskIterator(); while (iterator.hasNext()) { int pixelIndex = iterator.next(); int x = pixelIndex % width; int y = pixelIndex / width; int r = 0, g = 0, b = 0; int count = 0; for (int i = 0, n = offsets.length; i < n; i++) { int[] offset = offsets[i]; int column = x + offset[0]; int row = y + offset[1]; if (column < 0 || column >= width || row < 0 || row >= height) continue; int currentPixelIndex = getPixelIndex(width, column, row); if (mask.getMask(currentPixelIndex) == REALDATA) { color.argb = rgb[currentPixelIndex]; r += color.red(); g += color.green(); b += color.blue(); count++; } } if (count != 0) { color.setARGBA(0, r / count, g / count, b / count); rgb[pixelIndex] = color.argb; iterator.markAsInProgress(); } } iterator.reset(); } private int getPixelIndex (int width, int x, int y) { return y * width + x; } static class Mask { int[] data, pending, changing; int pendingSize, changingSize; Mask (int[] rgb) { data = new int[rgb.length]; pending = new int[rgb.length]; changing = new int[rgb.length]; ARGBColor color = new ARGBColor(); for (int i = 0; i < rgb.length; i++) { color.argb = rgb[i]; if (color.alpha() == 0) { data[i] = TO_PROCESS; pending[pendingSize] = i; pendingSize++; } else data[i] = REALDATA; } } int getMask (int index) { return data[index]; } int removeIndex (int index) { if (index >= pendingSize) throw new IndexOutOfBoundsException(String.valueOf(index)); int value = pending[index]; pendingSize--; pending[index] = pending[pendingSize]; return value; } class MaskIterator { private int index; boolean hasNext () { return index < pendingSize; } int next () { if (index >= pendingSize) throw new NoSuchElementException(String.valueOf(index)); return pending[index++]; } void markAsInProgress () { index--; changing[changingSize] = removeIndex(index); changingSize++; } void reset () { index = 0; for (int i = 0; i < changingSize; i++) { int index = changing[i]; data[index] = REALDATA; } changingSize = 0; } } } static class ARGBColor { int argb = 0xff000000; public int red () { return (argb >> 16) & 0xFF; } public int green () { return (argb >> 8) & 0xFF; } public int blue () { return (argb >> 0) & 0xFF; } public int alpha () { return (argb >> 24) & 0xff; } public void setARGBA (int a, int r, int g, int b) { if (a < 0 || a > 255 || r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255) throw new IllegalArgumentException("Invalid RGBA: " + r + ", " + g + "," + b + "," + a); argb = ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | ((b & 0xFF) << 0); } } }