From dc1969ec849c0185af51b097e6142913a6833d3b Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Mon, 25 Feb 2002 07:14:56 +0000 Subject: [PATCH] Added a method for recoloring images. It requires that the images be indexed color and it converts colors that are close to a target color in the HSV color space (with specifiable bounds on difference in hue, saturation and value) to new colors (with specifiable offsets for hue, saturation and value). Initial testing shows great promise for articles of clothing and even reasonable results on skin tones. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1069 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- .../com/threerings/media/image/ImageUtil.java | 128 +++++++++++++++++- 1 file changed, 127 insertions(+), 1 deletion(-) 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 {