* Demos of a custom buffered image operation. *
* * @author Romain Guy
* Provides an abstract implementation of the BufferedImageOp
* interface. This class can be used to created new image filters based on
* BufferedImageOp
.
*
* A color tint filter can be used to mix a solid color to an image. The result
* is an image tinted by the specified color. The force of the effect can be
* controlled with the mixValue
, a number between 0.0 and 1.0
* that can be seen as the percentage of the mix (0.0 does not affect the source
* image and 1.0 replaces all the pixels by the solid color).
*
* The color of the pixels in the resulting image is computed as follows: *
* ** cR = cS * (1 - mixValue) + cM * mixValue ** *
* Definition of the parameters: *
*cR
: color of the resulting pixelcS
: color of the source pixelcM
: the solid color to mix with the source imagemixValue
: strength of the mix, a value between 0.0 and
* 1.0
* Creates a new color mixer filter. The specified color will be used to tint
* the source image, with a mixing strength defined by mixValue
.
*
mixColor
is null
*/
public ColorTintFilter(Color mixColor, float mixValue) {
if (mixColor == null) {
throw new IllegalArgumentException("mixColor cannot be null");
}
this.mixColor = mixColor;
if (mixValue < 0.0f) {
mixValue = 0.0f;
} else if (mixValue > 1.0f) {
mixValue = 1.0f;
}
this.mixValue = mixValue;
int mix_r = (int) (mixColor.getRed() * mixValue);
int mix_g = (int) (mixColor.getGreen() * mixValue);
int mix_b = (int) (mixColor.getBlue() * mixValue);
// Since we use only lookup tables to apply the filter, this filter
// could be implemented as a LookupOp.
float factor = 1.0f - mixValue;
preMultipliedRed = new int[256];
preMultipliedGreen = new int[256];
preMultipliedBlue = new int[256];
for (int i = 0; i < 256; i++) {
int value = (int) (i * factor);
preMultipliedRed[i] = value + mix_r;
preMultipliedGreen[i] = value + mix_g;
preMultipliedBlue[i] = value + mix_b;
}
}
/**
* * Returns the mix value of this filter. *
* * @return the mix value, between 0.0 and 1.0 */ public float getMixValue() { return mixValue; } /** ** Returns the solid mix color of this filter. *
* * @return the solid color used for mixing */ public Color getMixColor() { return mixColor; } /** * {@inheritDoc} */ @Override public BufferedImage filter(BufferedImage src, BufferedImage dst) { if (dst == null) { DirectColorModel directCM = new DirectColorModel(32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); dst = createCompatibleDestImage(src, directCM); } int width = src.getWidth(); int height = src.getHeight(); int[] pixels = new int[width * height]; GraphicsUtilities.getPixels(src, 0, 0, width, height, pixels); mixColor(pixels); GraphicsUtilities.setPixels(dst, 0, 0, width, height, pixels); return dst; } private void mixColor(int[] pixels) { for (int i = 0; i < pixels.length; i++) { int argb = pixels[i]; pixels[i] = (argb & 0xFF000000) | preMultipliedRed[(argb >> 16) & 0xFF] << 16 | preMultipliedGreen[(argb >> 8) & 0xFF] << 8 | preMultipliedBlue[argb & 0xFF]; } } } /* * $Id: GraphicsUtilities.java,v 1.1 2006/11/05 15:40:51 gfx Exp $ * * Dual-licensed under LGPL (Sun and Romain Guy) and BSD (Romain Guy). * * Copyright 2005 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * California 95054, U.S.A. All rights reserved. * * Copyright (c) 2006 Romain Guy
* GraphicsUtilities
contains a set of tools to perform common
* graphics operations easily. These operations are divided into several themes,
* listed below.
*
* Compatible images can, and should, be used to increase drawing performance. * This class provides a number of methods to load compatible images directly * from files or to convert existing images to compatibles images. *
** This class provides a number of methods to easily scale down images. Some of * these methods offer a trade-off between speed and result quality and shouuld * be used all the time. They also offer the advantage of producing compatible * images, thus automatically resulting into better runtime performance. *
*
* All these methodes are both faster than
* {@link java.awt.Image#getScaledInstance(int, int, int)} and produce
* better-looking results than the various drawImage()
methods in
* {@link java.awt.Graphics}, which can be used for image scaling.
*
* This class provides two methods to get and set pixels in a buffered image. * These methods try to avoid unmanaging the image in order to keep good * performance. *
* * @author Romain Guy
* Returns a new BufferedImage
using the same color model as
* the image passed as a parameter. The returned image is only compatible with
* the image passed as a parameter. This does not mean the returned image is
* compatible with the hardware.
*
BufferedImage
, compatible with the color
* model of image
*/
public static BufferedImage createColorModelCompatibleImage(
BufferedImage image) {
ColorModel cm = image.getColorModel();
return new BufferedImage(cm, cm.createCompatibleWritableRaster(image
.getWidth(), image.getHeight()), cm.isAlphaPremultiplied(), null);
}
/**
* * Returns a new compatible image with the same width, height and transparency * as the image specified as a parameter. *
* * @see java.awt.Transparency * @see #createCompatibleImage(int, int) * @see #createCompatibleImage(java.awt.image.BufferedImage, int, int) * @see #createTranslucentCompatibleImage(int, int) * @see #loadCompatibleImage(java.net.URL) * @see #toCompatibleImage(java.awt.image.BufferedImage) * @param image * the reference image from which the dimension and the * transparency of the new image are obtained * @return a new compatibleBufferedImage
with the same
* dimension and transparency as image
*/
public static BufferedImage createCompatibleImage(BufferedImage image) {
return createCompatibleImage(image, image.getWidth(), image.getHeight());
}
/**
* * Returns a new compatible image of the specified width and height, and the * same transparency setting as the image specified as a parameter. *
* * @see java.awt.Transparency * @see #createCompatibleImage(java.awt.image.BufferedImage) * @see #createCompatibleImage(int, int) * @see #createTranslucentCompatibleImage(int, int) * @see #loadCompatibleImage(java.net.URL) * @see #toCompatibleImage(java.awt.image.BufferedImage) * @param width * the width of the new image * @param height * the height of the new image * @param image * the reference image from which the transparency of the new image * is obtained * @return a new compatibleBufferedImage
with the same
* transparency as image
and the specified dimension
*/
public static BufferedImage createCompatibleImage(BufferedImage image,
int width, int height) {
return CONFIGURATION.createCompatibleImage(width, height, image
.getTransparency());
}
/**
* * Returns a new opaque compatible image of the specified width and height. *
* * @see #createCompatibleImage(java.awt.image.BufferedImage) * @see #createCompatibleImage(java.awt.image.BufferedImage, int, int) * @see #createTranslucentCompatibleImage(int, int) * @see #loadCompatibleImage(java.net.URL) * @see #toCompatibleImage(java.awt.image.BufferedImage) * @param width * the width of the new image * @param height * the height of the new image * @return a new opaque compatibleBufferedImage
of the
* specified width and height
*/
public static BufferedImage createCompatibleImage(int width, int height) {
return CONFIGURATION.createCompatibleImage(width, height);
}
/**
* * Returns a new translucent compatible image of the specified width and * height. *
* * @see #createCompatibleImage(java.awt.image.BufferedImage) * @see #createCompatibleImage(java.awt.image.BufferedImage, int, int) * @see #createCompatibleImage(int, int) * @see #loadCompatibleImage(java.net.URL) * @see #toCompatibleImage(java.awt.image.BufferedImage) * @param width * the width of the new image * @param height * the height of the new image * @return a new translucent compatibleBufferedImage
of the
* specified width and height
*/
public static BufferedImage createTranslucentCompatibleImage(int width,
int height) {
return CONFIGURATION.createCompatibleImage(width, height,
Transparency.TRANSLUCENT);
}
/**
* * Returns a new compatible image from a URL. The image is loaded from the * specified location and then turned, if necessary into a compatible image. *
* * @see #createCompatibleImage(java.awt.image.BufferedImage) * @see #createCompatibleImage(java.awt.image.BufferedImage, int, int) * @see #createCompatibleImage(int, int) * @see #createTranslucentCompatibleImage(int, int) * @see #toCompatibleImage(java.awt.image.BufferedImage) * @param resource * the URL of the picture to load as a compatible image * @return a new translucent compatibleBufferedImage
of the
* specified width and height
* @throws java.io.IOException
* if the image cannot be read or loaded
*/
public static BufferedImage loadCompatibleImage(URL resource)
throws IOException {
BufferedImage image = ImageIO.read(resource);
return toCompatibleImage(image);
}
/**
* * Return a new compatible image that contains a copy of the specified image. * This method ensures an image is compatible with the hardware, and therefore * optimized for fast blitting operations. *
* * @see #createCompatibleImage(java.awt.image.BufferedImage) * @see #createCompatibleImage(java.awt.image.BufferedImage, int, int) * @see #createCompatibleImage(int, int) * @see #createTranslucentCompatibleImage(int, int) * @see #loadCompatibleImage(java.net.URL) * @param image * the image to copy into a new compatible image * @return a new compatible copy, with the same width and height and * transparency and content, ofimage
*/
public static BufferedImage toCompatibleImage(BufferedImage image) {
if (image.getColorModel().equals(CONFIGURATION.getColorModel())) {
return image;
}
BufferedImage compatibleImage = CONFIGURATION.createCompatibleImage(image
.getWidth(), image.getHeight(), image.getTransparency());
Graphics g = compatibleImage.getGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return compatibleImage;
}
/**
*
* Returns a thumbnail of a source image. newSize
defines the
* length of the longest dimension of the thumbnail. The other dimension is
* then computed according to the dimensions ratio of the original picture.
*
* This method favors speed over quality. When the new size is less than half * the longest dimension of the source image, * {@link #createThumbnail(BufferedImage, int)} or * {@link #createThumbnail(BufferedImage, int, int)} should be used instead to * ensure the quality of the result without sacrificing too much performance. *
* * @see #createThumbnailFast(java.awt.image.BufferedImage, int, int) * @see #createThumbnail(java.awt.image.BufferedImage, int) * @see #createThumbnail(java.awt.image.BufferedImage, int, int) * @param image * the source image * @param newSize * the length of the largest dimension of the thumbnail * @return a new compatibleBufferedImage
containing a
* thumbnail of image
* @throws IllegalArgumentException
* if newSize
is larger than the largest dimension
* of image
or <= 0
*/
public static BufferedImage createThumbnailFast(BufferedImage image,
int newSize) {
float ratio;
int width = image.getWidth();
int height = image.getHeight();
if (width > height) {
if (newSize >= width) {
throw new IllegalArgumentException("newSize must be lower than"
+ " the image width");
} else if (newSize <= 0) {
throw new IllegalArgumentException("newSize must"
+ " be greater than 0");
}
ratio = (float) width / (float) height;
width = newSize;
height = (int) (newSize / ratio);
} else {
if (newSize >= height) {
throw new IllegalArgumentException("newSize must be lower than"
+ " the image height");
} else if (newSize <= 0) {
throw new IllegalArgumentException("newSize must"
+ " be greater than 0");
}
ratio = (float) height / (float) width;
height = newSize;
width = (int) (newSize / ratio);
}
BufferedImage temp = createCompatibleImage(image, width, height);
Graphics2D g2 = temp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(image, 0, 0, temp.getWidth(), temp.getHeight(), null);
g2.dispose();
return temp;
}
/**
* * Returns a thumbnail of a source image. *
** This method favors speed over quality. When the new size is less than half * the longest dimension of the source image, * {@link #createThumbnail(BufferedImage, int)} or * {@link #createThumbnail(BufferedImage, int, int)} should be used instead to * ensure the quality of the result without sacrificing too much performance. *
* * @see #createThumbnailFast(java.awt.image.BufferedImage, int) * @see #createThumbnail(java.awt.image.BufferedImage, int) * @see #createThumbnail(java.awt.image.BufferedImage, int, int) * @param image * the source image * @param newWidth * the width of the thumbnail * @param newHeight * the height of the thumbnail * @return a new compatibleBufferedImage
containing a
* thumbnail of image
* @throws IllegalArgumentException
* if newWidth
is larger than the width of
* image
or if code>newHeight is larger
* than the height of image
or if one of the
* dimensions is <= 0
*/
public static BufferedImage createThumbnailFast(BufferedImage image,
int newWidth, int newHeight) {
if (newWidth >= image.getWidth() || newHeight >= image.getHeight()) {
throw new IllegalArgumentException("newWidth and newHeight cannot"
+ " be greater than the image" + " dimensions");
} else if (newWidth <= 0 || newHeight <= 0) {
throw new IllegalArgumentException("newWidth and newHeight must"
+ " be greater than 0");
}
BufferedImage temp = createCompatibleImage(image, newWidth, newHeight);
Graphics2D g2 = temp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(image, 0, 0, temp.getWidth(), temp.getHeight(), null);
g2.dispose();
return temp;
}
/**
*
* Returns a thumbnail of a source image. newSize
defines the
* length of the longest dimension of the thumbnail. The other dimension is
* then computed according to the dimensions ratio of the original picture.
*
* This method offers a good trade-off between speed and quality. The result * looks better than * {@link #createThumbnailFast(java.awt.image.BufferedImage, int)} when the * new size is less than half the longest dimension of the source image, yet * the rendering speed is almost similar. *
* * @see #createThumbnailFast(java.awt.image.BufferedImage, int, int) * @see #createThumbnailFast(java.awt.image.BufferedImage, int) * @see #createThumbnail(java.awt.image.BufferedImage, int, int) * @param image * the source image * @param newSize * the length of the largest dimension of the thumbnail * @return a new compatibleBufferedImage
containing a
* thumbnail of image
* @throws IllegalArgumentException
* if newSize
is larger than the largest dimension
* of image
or <= 0
*/
public static BufferedImage createThumbnail(BufferedImage image, int newSize) {
int width = image.getWidth();
int height = image.getHeight();
boolean isWidthGreater = width > height;
if (isWidthGreater) {
if (newSize >= width) {
throw new IllegalArgumentException("newSize must be lower than"
+ " the image width");
}
} else if (newSize >= height) {
throw new IllegalArgumentException("newSize must be lower than"
+ " the image height");
}
if (newSize <= 0) {
throw new IllegalArgumentException("newSize must" + " be greater than 0");
}
float ratioWH = (float) width / (float) height;
float ratioHW = (float) height / (float) width;
BufferedImage thumb = image;
do {
if (isWidthGreater) {
width /= 2;
if (width < newSize) {
width = newSize;
}
height = (int) (width / ratioWH);
} else {
height /= 2;
if (height < newSize) {
height = newSize;
}
width = (int) (height / ratioHW);
}
BufferedImage temp = createCompatibleImage(image, width, height);
Graphics2D g2 = temp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(thumb, 0, 0, temp.getWidth(), temp.getHeight(), null);
g2.dispose();
thumb = temp;
} while (newSize != (isWidthGreater ? width : height));
return thumb;
}
/**
* * Returns a thumbnail of a source image. *
** This method offers a good trade-off between speed and quality. The result * looks better than * {@link #createThumbnailFast(java.awt.image.BufferedImage, int)} when the * new size is less than half the longest dimension of the source image, yet * the rendering speed is almost similar. *
* * @see #createThumbnailFast(java.awt.image.BufferedImage, int) * @see #createThumbnailFast(java.awt.image.BufferedImage, int, int) * @see #createThumbnail(java.awt.image.BufferedImage, int) * @param image * the source image * @param newWidth * the width of the thumbnail * @param newHeight * the height of the thumbnail * @return a new compatibleBufferedImage
containing a
* thumbnail of image
* @throws IllegalArgumentException
* if newWidth
is larger than the width of
* image
or if code>newHeight is larger
* than the height of image or if one the dimensions is not
* > 0
*/
public static BufferedImage createThumbnail(BufferedImage image,
int newWidth, int newHeight) {
int width = image.getWidth();
int height = image.getHeight();
if (newWidth >= width || newHeight >= height) {
throw new IllegalArgumentException("newWidth and newHeight cannot"
+ " be greater than the image" + " dimensions");
} else if (newWidth <= 0 || newHeight <= 0) {
throw new IllegalArgumentException("newWidth and newHeight must"
+ " be greater than 0");
}
BufferedImage thumb = image;
do {
if (width > newWidth) {
width /= 2;
if (width < newWidth) {
width = newWidth;
}
}
if (height > newHeight) {
height /= 2;
if (height < newHeight) {
height = newHeight;
}
}
BufferedImage temp = createCompatibleImage(image, width, height);
Graphics2D g2 = temp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(thumb, 0, 0, temp.getWidth(), temp.getHeight(), null);
g2.dispose();
thumb = temp;
} while (width != newWidth || height != newHeight);
return thumb;
}
/**
*
* Returns an array of pixels, stored as integers, from a
* BufferedImage
. The pixels are grabbed from a rectangular
* area defined by a location and two dimensions. Calling this method on an
* image of type different from BufferedImage.TYPE_INT_ARGB
and
* BufferedImage.TYPE_INT_RGB
will unmanage the image.
*
pixels
if non-null, a new array of integers
* otherwise
* @throws IllegalArgumentException
* is pixels
is non-null and of length < w*h
*/
public static int[] getPixels(BufferedImage img, int x, int y, int w, int h,
int[] pixels) {
if (w == 0 || h == 0) {
return new int[0];
}
if (pixels == null) {
pixels = new int[w * h];
} else if (pixels.length < w * h) {
throw new IllegalArgumentException("pixels array must have a length"
+ " >= w*h");
}
int imageType = img.getType();
if (imageType == BufferedImage.TYPE_INT_ARGB
|| imageType == BufferedImage.TYPE_INT_RGB) {
Raster raster = img.getRaster();
return (int[]) raster.getDataElements(x, y, w, h, pixels);
}
// Unmanages the image
return img.getRGB(x, y, w, h, pixels, 0, w);
}
/**
*
* Writes a rectangular area of pixels in the destination
* BufferedImage
. Calling this method on an image of type
* different from BufferedImage.TYPE_INT_ARGB
and
* BufferedImage.TYPE_INT_RGB
will unmanage the image.
*
pixels
is non-null and of length < w*h
*/
public static void setPixels(BufferedImage img, int x, int y, int w, int h,
int[] pixels) {
if (pixels == null || w == 0 || h == 0) {
return;
} else if (pixels.length < w * h) {
throw new IllegalArgumentException("pixels array must have a length"
+ " >= w*h");
}
int imageType = img.getType();
if (imageType == BufferedImage.TYPE_INT_ARGB
|| imageType == BufferedImage.TYPE_INT_RGB) {
WritableRaster raster = img.getRaster();
raster.setDataElements(x, y, w, h, pixels);
} else {
// Unmanages the image
img.setRGB(x, y, w, h, pixels, 0, w);
}
}
}