package org.newdawn.slick.tools.hiero.distancemap; import java.awt.Color; import java.awt.image.BufferedImage; import org.newdawn.slick.tools.hiero.ProgressListener; /** * A filter to create a distance field from a source image * * @author Orangy */ public class DistanceFieldFilter { /** The progress indicator */ private static int progress; /** * Caclulate the distance between two points * * @param x1 The x coordinate of the first point * @param y1 The y coordiante of the first point * @param x2 The x coordinate of the second point * @param y2 The y coordinate of the second point * @return The distance between two point */ private static float separation(final float x1, final float y1, final float x2, final float y2) { final float dx = x1 - x2; final float dy = y1 - y2; return (float)Math.sqrt(dx*dx + dy*dy); } /** * Process the image into a distance field * * @param inImage The image to process * @param outWidth The width of the output field * @param outHeight The height of the output field * @param scanSize The scan size, controls the quality * @param listener The lisetener to report progress to * @return The distance field image */ public static BufferedImage process(BufferedImage inImage, int outWidth, int outHeight, int scanSize, ProgressListener listener) { System.out.println("DistanceFieldFilter.process"); BufferedImage outImage = new BufferedImage(outWidth, outHeight, BufferedImage.TYPE_4BYTE_ABGR); float[][] distances = new float[outImage.getWidth()][outImage.getHeight()]; final int blockWidth = inImage.getWidth() / outImage.getWidth(); final int blockHeight = inImage.getHeight() / outImage.getHeight(); System.out.println("Block size is "+blockWidth+","+blockHeight); for (int x=0; x<outImage.getWidth(); x++) { listener.reportProgress("Finding signed distance", x, outImage.getWidth()); for (int y=0; y<outImage.getHeight(); y++) { distances[x][y] = findSignedDistance( (x * blockWidth) + (blockWidth / 2), (y * blockHeight) + (blockHeight / 2), inImage, scanSize, scanSize); } } float max = 0; for (int x=0; x<distances.length; x++) { for (int y=0; y<distances[0].length; y++) { final float d = distances[x][y]; if (d != Float.MAX_VALUE && d > max) max = d; } } float min = 0; for (int x=0; x<distances.length; x++) { for (int y=0; y<distances[0].length; y++) { final float d = distances[x][y]; if (d != Float.MIN_VALUE && d < min) min = d; } } final float range = max - min; final float scale = Math.max( Math.abs(min), Math.abs(max) ); System.out.println("Max: "+max+", Min:"+min+", Range:"+range); for (int x=0; x<distances.length; x++) { for (int y=0; y<distances[0].length; y++) { float d = distances[x][y]; if (d == Float.MAX_VALUE) d = 1.0f; else if (d == Float.MIN_VALUE) d = 0.0f; else { d /= scale; d /= 2; d += 0.5f; } distances[x][y] = d; } } for (int x=0; x<distances.length; x++) { listener.reportProgress("Setting Image", x, outImage.getWidth()); for (int y=0; y<distances[0].length; y++) { float d = distances[x][y]; if (d == Float.NaN) d = 0; // As greyscale // outImage.setRGB(x, y, new Color(d, d, d, 1.0f).getRGB()); // As alpha outImage.setRGB(x, y, new Color(1.0f, 1.0f, 1.0f, d).getRGB()); // As both // outImage.setRGB(x, y, new Color(d, d, d, d).getRGB()); } } return outImage; } /** * Get the progress indicator * * @return The progress indicator */ public static int progress() { return progress; } /** * Find the signed distance for a given point * * @param pointX The x coordinate of the point * @param pointY The y coordinate of the point * @param inImage The image on which the point exists * @param scanWidth The scan line of the image * @param scanHeight The scan height of the image * @return The signed distance */ private static float findSignedDistance(final int pointX, final int pointY, BufferedImage inImage, final int scanWidth, final int scanHeight) { Color baseColour = new Color(inImage.getRGB(pointX, pointY) ); final boolean baseIsSolid = baseColour.getRed() > 0; float closestDistance = Float.MAX_VALUE; boolean closestValid = false; final int startX = pointX - (scanWidth / 2); final int endX = startX + scanWidth; final int startY = pointY - (scanHeight / 2); final int endY = startY + scanHeight; for (int x=startX; x<endX; x++) { if (x < 0 || x >= inImage.getWidth()) continue; for (int y=startY; y<endY; y++) { if (y < 0 || y >= inImage.getWidth()) continue; Color c = new Color(inImage.getRGB(x, y)); if (baseIsSolid) { if (c.getRed() == 0) { final float dist = separation(pointX, pointY, x, y); if (dist < closestDistance) { closestDistance = dist; closestValid = true; } } } else { if (c.getRed() > 0) { final float dist = separation(pointX, pointY, x, y); if (dist < closestDistance) { closestDistance = dist; closestValid = true; } } } } } if (baseIsSolid) { if (closestValid) return closestDistance; else return Float.MAX_VALUE; } else { if (closestValid) return -closestDistance; else return Float.MIN_VALUE; } } }