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
This commit is contained in:
Michael Bayne
2002-02-25 07:14:56 +00:00
parent 9e5937ffea
commit dc1969ec84
@@ -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 {