/* * Copyright 2017 Laszlo Balazs-Csiki * * This file is part of Pixelitor. Pixelitor is free software: you * can redistribute it and/or modify it under the terms of the GNU * General Public License, version 3 as published by the Free * Software Foundation. * * Pixelitor is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Pixelitor. If not, see <http://www.gnu.org/licenses/>. */ package pixelitor.filters.impl; import com.jhlabs.image.PointFilter; import pixelitor.utils.ImageUtils; import pixelitor.utils.Metric; import pixelitor.utils.ReseedSupport; import java.awt.Color; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.geom.Ellipse2D; import java.awt.image.BufferedImage; import java.util.Random; public class VoronoiFilter extends PointFilter { private int numPoints = 10; private int[] xCoords; private int[] yCoords; private int[] colors; private Metric metric; private boolean useImageColors; private int aaRes = 2; private int aaRes2 = aaRes * aaRes; public VoronoiFilter(String filterName) { super(filterName); } public void setNumPoints(int numPoints) { this.numPoints = numPoints; } public void setMetric(Metric metric) { this.metric = metric; } public void setUseImageColors(boolean useImageColors) { this.useImageColors = useImageColors; } public void setAaRes(int aaRes) { this.aaRes = aaRes; this.aaRes2 = aaRes * aaRes; } @Override public BufferedImage filter(BufferedImage src, BufferedImage dst) { xCoords = new int[numPoints]; yCoords = new int[numPoints]; colors = new int[numPoints]; ReseedSupport.reInitialize(); Random rand = ReseedSupport.getRand(); for (int i = 0; i < numPoints; i++) { xCoords[i] = rand.nextInt(src.getWidth()); yCoords[i] = rand.nextInt(src.getHeight()); if (useImageColors) { colors[i] = src.getRGB(xCoords[i], yCoords[i]); } else { colors[i] = 0xFF_00_00_00 | rand.nextInt(0xFF_FF_FF); } } return super.filter(src, dst); } public void showPoints(BufferedImage img) { double radius = (img.getWidth() + img.getHeight()) / 300.0; if(radius < 1) { radius = 1; } double diameter = 2 * radius; Graphics2D g = img.createGraphics(); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setColor(Color.BLACK); if (useImageColors) { g.setXORMode(Color.WHITE); } for (int i = 0; i < numPoints; i++) { g.fill(new Ellipse2D.Double(xCoords[i] - radius, yCoords[i] - radius, diameter, diameter)); } g.dispose(); } @Override public int filterRGB(int x, int y, int rgb) { int closestPointIndex = 0; double fromHereToClosestSoFar = metric.distanceInt(xCoords[closestPointIndex], x, yCoords[closestPointIndex], y); for (int i = 0; i < numPoints; i++) { double fromHereToPointI = metric.distanceInt(xCoords[i], x, yCoords[i], y); if (fromHereToPointI < fromHereToClosestSoFar) { closestPointIndex = i; fromHereToClosestSoFar = fromHereToPointI; } } return colors[closestPointIndex]; } /** * Finds the nearest point with double precision. Used for AA */ private int nearestSiteDouble(double x, double y) { int closestPointIndex = 0; double fromHereToClosestSoFar = metric.distanceDouble(xCoords[closestPointIndex], x, yCoords[closestPointIndex], y); for (int i = 0; i < numPoints; i++) { double fromHereToPointI = metric.distanceDouble(xCoords[i], x, yCoords[i], y); if (fromHereToPointI < fromHereToClosestSoFar) { closestPointIndex = i; fromHereToClosestSoFar = fromHereToPointI; } } return closestPointIndex; } /** * Check whether the pixel is different from its neighbours. * It is enough to check in the horizontal direction */ private static boolean isEdge(int[] allPixels, int pixelIndex, int width) { int color = allPixels[pixelIndex]; int colorLeft = allPixels[pixelIndex - 1]; int colorRight = allPixels[pixelIndex + 1]; if ((color != colorLeft) || (color != colorRight)) { return true; } int colorUp = allPixels[pixelIndex - width]; int colorDown = allPixels[pixelIndex + width]; if ((color != colorUp) || (color != colorDown)) { return true; } return false; } private int calcSuperSampledColor(int pixelIndex, int width) { int x = pixelIndex % width; int y = pixelIndex / width; int r = 0; int g = 0; int b = 0; for (int i = 0; i < aaRes; i++) { double yy = y + 1.0 / aaRes * i - 0.5; for (int j = 0; j < aaRes; j++) { double xx = x + 1.0 / aaRes * j - 0.5; int closestPointIndex = nearestSiteDouble(xx, yy); int color = colors[closestPointIndex]; r += (color >>> 16) & 0xFF; g += (color >>> 8) & 0xFF; b += color & 0xFF; } } r /= aaRes2; g /= aaRes2; b /= aaRes2; return (0xFF_00_00_00 | (r << 16) | (g << 8) | b); } public void antiAlias(BufferedImage imgSoFar) { assert aaRes != 0; int width = imgSoFar.getWidth(); int[] pixels = ImageUtils.getPixelsAsArray(imgSoFar); // make a copy so that the original is inspected for edges // while the array is changed int[] pixelsCopy = new int[pixels.length]; System.arraycopy(pixels, 0, pixelsCopy, 0, pixels.length); for (int i = 0; i < pixels.length; i++) { // only pixels at the edges are supersampled boolean edge; try { edge = isEdge(pixelsCopy, i, width); } catch (ArrayIndexOutOfBoundsException e) { edge = false; } if(edge) { pixels[i] = calcSuperSampledColor(i, width); } } } }