diff --git a/src/java/com/threerings/media/image/ImageUtil.java b/src/java/com/threerings/media/image/ImageUtil.java index f23a7e498..1699792cc 100644 --- a/src/java/com/threerings/media/image/ImageUtil.java +++ b/src/java/com/threerings/media/image/ImageUtil.java @@ -1,15 +1,22 @@ // -// $Id: ImageUtil.java,v 1.4 2002/02/24 02:20:44 mdb Exp $ +// $Id: ImageUtil.java,v 1.5 2002/02/25 07:14:56 mdb Exp $ package com.threerings.media.util; +import java.awt.Color; import java.awt.Image; import java.awt.Graphics; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Transparency; + import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.IndexColorModel; + +import com.samskivert.util.StringUtil; +import com.threerings.media.Log; /** * Image related utility functions. @@ -84,6 +91,125 @@ public class ImageUtil return _gc.createCompatibleImage(width, height, transparency); } + /** + * Used to recolor images by shifting bands of color (in HSV color + * space) to a new hue. The source images must be 8-bit color mapped + * images, as the recoloring process works by analysing the color map + * and modifying it. + */ + public static BufferedImage recolorImage ( + BufferedImage image, int rootColor, float[] dists, float[] offsets) + { + ColorModel cm = image.getColorModel(); + if (!(cm instanceof IndexColorModel)) { + String errmsg = "Unable to recolor images with non-index color " + + "model [cm=" + cm.getClass() + "]"; + throw new RuntimeException(errmsg); + } + + // first convert the root color to HSV for later comparison + Color rcolor = new Color(rootColor); + float[] rHSV = Color.RGBtoHSB(rcolor.getRed(), rcolor.getGreen(), + rcolor.getBlue(), null); + int[] frHSV = toFixedHSV(rHSV, null); + + // now process the image + IndexColorModel icm = (IndexColorModel)cm; + int size = icm.getMapSize(); + int[] rgbs = new int[size]; + byte[] reds = new byte[size]; + byte[] greens = new byte[size]; + byte[] blues = new byte[size]; + byte[] alphas = new byte[size]; + + // fetch the color data + icm.getRGBs(rgbs); + icm.getReds(reds); + icm.getGreens(greens); + icm.getBlues(blues); + icm.getAlphas(alphas); + + // convert the colors to HSV + float[] hsv = new float[3]; + int[] fhsv = new int[3]; + for (int i = 0; i < size; i++) { + // don't fiddle with alpha pixels + if (alphas[i] != -1) { + continue; + } + + // convert the color to HSV + Color color = new Color(rgbs[i]); + Color.RGBtoHSB(color.getRed(), color.getGreen(), + color.getBlue(), hsv); + + // check to see that this color is sufficiently "close" to the + // root color based on the supplied distance parameters + toFixedHSV(hsv, fhsv); + if (distance(fhsv[0], frHSV[0], Short.MAX_VALUE) >= + dists[0] * Short.MAX_VALUE) { + continue; + } + + // saturation and value don't wrap around like hue + if (Math.abs(rHSV[1] - hsv[1]) >= dists[1] || + Math.abs(rHSV[2] - hsv[2]) >= dists[2]) { + continue; + } + + // massage the HSV bands and update the RGBs array + for (int band = 0; band < offsets.length; band++) { + hsv[band] += offsets[band]; + + // for hue, we wrap around + if (band == 0) { + if (hsv[band] > 1.0) { + hsv[band] -= 1.0; + } + } else { + // otherwise we clip + hsv[band] = Math.max(hsv[band], 0); + hsv[band] = Math.min(hsv[band], 1); + } + } + + int rgb = Color.HSBtoRGB(hsv[0], hsv[1], hsv[2]); + Color ncolor = new Color(rgb); + reds[i] = (byte)ncolor.getRed(); + greens[i] = (byte)ncolor.getGreen(); + blues[i] = (byte)ncolor.getBlue(); + } + + // create a new image with the adjusted color palette + IndexColorModel nicm = new IndexColorModel( + 8, size, reds, greens, blues, alphas); + return new BufferedImage(nicm, image.getRaster(), false, null); + } + + /** + * Converts floating point HSV values to a fixed point integer + * representation. + */ + protected static int[] toFixedHSV (float[] hsv, int[] fhsv) + { + if (fhsv == null) { + fhsv = new int[hsv.length]; + } + for (int i = 0; i < hsv.length; i++) { + // fhsv[i] = (int)(hsv[i]*Integer.MAX_VALUE); + fhsv[i] = (int)(hsv[i]*Short.MAX_VALUE); + } + return fhsv; + } + + /** + * Returns the distance between the supplied to numbers modulo N. + */ + protected static int distance (int a, int b, int N) + { + return (a > b) ? Math.min(a-b, b+N-a) : Math.min(b-a, a+N-b); + } + /** The graphics configuration for the default screen device. */ protected static GraphicsConfiguration _gc; static {