Files
narya/src/java/com/threerings/media/image/ImageUtil.java
T
Michael Bayne 87c4c756e4 Pass a color rather than an integer to the recoloration routine.
git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1100 542714f4-19e9-0310-aa3c-eee0fc999fb1
2002-03-08 01:38:12 +00:00

222 lines
7.5 KiB
Java

//
// $Id: ImageUtil.java,v 1.6 2002/03/08 01:38:12 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.
*/
public class ImageUtil
{
/**
* Extracts a subimage from the supplied image with the specified
* dimensions. If the supplied image is an instance of {@link
* BufferedImage}, then the subimage will simply reference the main
* image. If it is not, the subimage will be created and the data will
* be rendered into the newly created image.
*
* @param source the source image.
* @param x the left coordinate of the sub-image.
* @param y the top coordinate of the sub-image.
* @param width the sub-image width.
* @param height the sub-image height.
*
* @return the desired subimage.
*/
public static Image getSubimage (
Image source, int x, int y, int width, int height)
{
if (source instanceof BufferedImage) {
return ((BufferedImage)source).getSubimage(x, y, width, height);
} else {
BufferedImage target = createImage(width, height);
Graphics g = target.getGraphics();
g.drawImage(source, 0, 0, width, height,
x, y, x+width, y+height, null);
g.dispose();
return target;
}
}
/**
* Creates a new blank image with the given dimensions and
* transparency set to {@link Transparency#BITMASK}. The format of
* the created image is compatible with the graphics configuration of
* the default screen device, such that no format conversion will be
* necessary when rendering the image to that device.
*
* @param width the desired image width.
* @param height the desired image height.
*
* @return the blank image.
*/
public static BufferedImage createImage (int width, int height)
{
return createImage(width, height, Transparency.BITMASK);
}
/**
* Creates a new blank image with the given dimensions and
* transparency. The format of the created image is compatible with
* the graphics configuration of the default screen device, such that
* no format conversion will be necessary when rendering the image to
* that device.
*
* @param width the desired image width.
* @param height the desired image height.
* @param transparency the desired image transparency; one of the
* constants in {@link java.awt.Transparency}.
*
* @return the blank image.
*/
public static BufferedImage createImage (
int width, int height, int transparency)
{
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, Color 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
float[] rHSV = Color.RGBtoHSB(rootColor.getRed(), rootColor.getGreen(),
rootColor.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 {
// obtain information on our graphics environment
GraphicsEnvironment env =
GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = env.getDefaultScreenDevice();
_gc = gd.getDefaultConfiguration();
};
}