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:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user