diff options
author | Andrew John Hughes <gnu_andrew@member.fsf.org> | 2006-09-05 20:47:38 +0000 |
---|---|---|
committer | Andrew John Hughes <gnu_andrew@member.fsf.org> | 2006-09-05 20:47:38 +0000 |
commit | 228fc33f661314b6723b691635085ee4ff6a69b2 (patch) | |
tree | 50e78696593f8df21fe9330258dff98537d39f65 /java/awt/image/ColorConvertOp.java | |
parent | f090c35d5776db64813ccf19d8327cf0ae756ac7 (diff) | |
download | classpath-228fc33f661314b6723b691635085ee4ff6a69b2.tar.gz |
2006-09-05 Andrew John Hughes <gnu_andrew@member.fsf.org>
* Merge of HEAD-->generics from 2006/08/12 to 2006/09/03.
Diffstat (limited to 'java/awt/image/ColorConvertOp.java')
-rw-r--r-- | java/awt/image/ColorConvertOp.java | 443 |
1 files changed, 328 insertions, 115 deletions
diff --git a/java/awt/image/ColorConvertOp.java b/java/awt/image/ColorConvertOp.java index 1f85a5ecd..e6c85412d 100644 --- a/java/awt/image/ColorConvertOp.java +++ b/java/awt/image/ColorConvertOp.java @@ -38,7 +38,10 @@ exception statement from your version. */ package java.awt.image; +import gnu.java.awt.Buffers; + import java.awt.Graphics2D; +import java.awt.Point; import java.awt.RenderingHints; import java.awt.color.ColorSpace; import java.awt.color.ICC_ColorSpace; @@ -47,9 +50,9 @@ import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; /** - * ColorConvertOp is a filter for converting an image from one colorspace to - * another colorspace. The filter can convert the image through a sequence - * of colorspaces or just from source to destination. + * ColorConvertOp is a filter for converting images or rasters between + * colorspaces, either through a sequence of colorspaces or just from source to + * destination. * * Color conversion is done on the color components without alpha. Thus * if a BufferedImage has alpha premultiplied, this is divided out before @@ -63,24 +66,22 @@ import java.awt.geom.Rectangle2D; */ public class ColorConvertOp implements BufferedImageOp, RasterOp { - private ColorSpace srccs; - private ColorSpace dstcs; private RenderingHints hints; - private ICC_Profile[] profiles; + private ICC_Profile[] profiles = null; private ColorSpace[] spaces; - private boolean rasterValid; /** - * Convert BufferedImage through a ColorSpace. + * Convert a BufferedImage through a ColorSpace. * - * This filter version is only valid for BufferedImages. The source image - * is converted to cspace. If the destination is not null, it is then - * converted to the destination colorspace. Normally this filter will only - * be used with a null destination. + * Objects created with this constructor can be used to convert + * BufferedImage's to a destination ColorSpace. Attempts to convert Rasters + * with this constructor will result in an IllegalArgumentException when the + * filter(Raster, WritableRaster) method is called. * * @param cspace The target color space. - * @param hints Rendering hints to use in conversion, or null. + * @param hints Rendering hints to use in conversion, if any (may be null) + * @throws NullPointerException if the ColorSpace is null. */ public ColorConvertOp(ColorSpace cspace, RenderingHints hints) { @@ -88,9 +89,27 @@ public class ColorConvertOp implements BufferedImageOp, RasterOp throw new NullPointerException(); spaces = new ColorSpace[]{cspace}; this.hints = hints; - rasterValid = false; } + /** + * Convert from a source colorspace to a destination colorspace. + * + * This constructor takes two ColorSpace arguments as the source and + * destination color spaces. It is usually used with the + * filter(Raster, WritableRaster) method, in which case the source colorspace + * is assumed to correspond to the source Raster, and the destination + * colorspace with the destination Raster. + * + * If used with BufferedImages that do not match the source or destination + * colorspaces specified here, there is an implicit conversion from the + * source image to the source ColorSpace, or the destination ColorSpace to + * the destination image. + * + * @param srcCspace The source ColorSpace. + * @param dstCspace The destination ColorSpace. + * @param hints Rendering hints to use in conversion, if any (may be null). + * @throws NullPointerException if any ColorSpace is null. + */ public ColorConvertOp(ColorSpace srcCspace, ColorSpace dstCspace, RenderingHints hints) { @@ -101,61 +120,77 @@ public class ColorConvertOp implements BufferedImageOp, RasterOp } /** - * Convert from a source image destination image color space. + * Convert from a source colorspace to a destinatino colorspace. * * This constructor builds a ColorConvertOp from an array of ICC_Profiles. - * The source image will be converted through the sequence of color spaces + * The source will be converted through the sequence of color spaces * defined by the profiles. If the sequence of profiles doesn't give a - * well-defined conversion, throws IllegalArgumentException. - * - * NOTE: Sun's docs don't clearly define what a well-defined conversion is - * - or perhaps someone smarter can come along and sort it out. + * well-defined conversion, an IllegalArgumentException is thrown. * - * For BufferedImages, when the first and last profiles match the - * requirements of the source and destination color space respectively, the - * corresponding conversion is unnecessary. TODO: code this up. I don't - * yet understand how you determine this. + * If used with BufferedImages that do not match the source or destination + * colorspaces specified here, there is an implicit conversion from the + * source image to the source ColorSpace, or the destination ColorSpace to + * the destination image. * * For Rasters, the first and last profiles must have the same number of * bands as the source and destination Rasters, respectively. If this is * not the case, or there fewer than 2 profiles, an IllegalArgumentException * will be thrown. * - * @param profiles - * @param hints + * @param profiles An array of ICC_Profile's to convert through. + * @param hints Rendering hints to use in conversion, if any (may be null). + * @throws NullPointerException if the profile array is null. + * @throws IllegalArgumentException if the array is not a well-defined + * conversion. */ public ColorConvertOp(ICC_Profile[] profiles, RenderingHints hints) { if (profiles == null) throw new NullPointerException(); + this.hints = hints; this.profiles = profiles; - // TODO: Determine if this is well-defined. + // Create colorspace array with space for src and dest colorspace + // Note that the ICC_ColorSpace constructor will throw an + // IllegalArgumentException if the profile is invalid; thus we check + // for a "well defined conversion" spaces = new ColorSpace[profiles.length]; for (int i = 0; i < profiles.length; i++) spaces[i] = new ICC_ColorSpace(profiles[i]); } - /** Convert from source image color space to destination image color space. + /** + * Convert from source color space to destination color space. * * Only valid for BufferedImage objects, this Op converts from the source - * color space to the destination color space. The destination can't be - * null for this operation. + * image's color space to the destination image's color space. * - * @param hints Rendering hints to use during conversion, or null. + * The destination in the filter(BufferedImage, BufferedImage) method cannot + * be null for this operation, and it also cannot be used with the + * filter(Raster, WritableRaster) method. + * + * @param hints Rendering hints to use in conversion, if any (may be null). */ public ColorConvertOp(RenderingHints hints) { - this.hints = hints; - srccs = null; - dstcs = null; - rasterValid = false; + this.hints = hints; + spaces = new ColorSpace[0]; } - /* (non-Javadoc) - * @see java.awt.image.BufferedImageOp#filter(java.awt.image.BufferedImage, - java.awt.image.BufferedImage) + /** + * Converts the source image using the conversion path specified in the + * constructor. The resulting image is stored in the destination image if one + * is provided; otherwise a new BufferedImage is created and returned. + * + * The source and destination BufferedImage (if one is supplied) must have + * the same dimensions. + * + * @param src The source image. + * @param dst The destination image. + * @throws IllegalArgumentException if the rasters and/or color spaces are + * incompatible. + * @return The transformed image. */ public final BufferedImage filter(BufferedImage src, BufferedImage dst) { @@ -163,129 +198,241 @@ public class ColorConvertOp implements BufferedImageOp, RasterOp // For now we just suck it up and create intermediate buffers. if (dst == null && spaces.length == 0) - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Not enough color space information " + + "to complete conversion."); + + if (dst != null + && (src.getHeight() != dst.getHeight() || src.getWidth() != dst.getWidth())) + throw new IllegalArgumentException("Source and destination images have " + + "different dimensions"); // Make sure input isn't premultiplied by alpha if (src.isAlphaPremultiplied()) - { - BufferedImage tmp = createCompatibleDestImage(src, src.getColorModel()); - copyimage(src, tmp); - tmp.coerceData(false); - src = tmp; - } + { + BufferedImage tmp = createCompatibleDestImage(src, src.getColorModel()); + copyimage(src, tmp); + tmp.coerceData(false); + src = tmp; + } - ColorModel scm = src.getColorModel(); + // Convert through defined intermediate conversions + BufferedImage tmp; for (int i = 0; i < spaces.length; i++) - { - BufferedImage tmp = createCompatibleDestImage(src, scm); - copyimage(src, tmp); - src = tmp; - } + { + if (src.getColorModel().getColorSpace().getType() != spaces[i].getType()) + { + tmp = createCompatibleDestImage(src, + createCompatibleColorModel(src, + spaces[i])); + copyimage(src, tmp); + src = tmp; + } + } - // Intermediate conversions leave result in src + // No implicit conversion to destination type needed; return result from the + // last intermediate conversions (which was left in src) if (dst == null) - return src; - - // Apply final conversion - copyimage(src, dst); - + dst = src; + + // Implicit conversion to destination image's color space + else + copyimage(src, dst); + return dst; } - /* (non-Javadoc) - * @see java.awt.image.BufferedImageOp#createCompatibleDestImage(java.awt.image.BufferedImage, java.awt.image.ColorModel) + /** + * Converts the source raster using the conversion path specified in the + * constructor. The resulting raster is stored in the destination raster if + * one is provided; otherwise a new WritableRaster is created and returned. + * + * This operation is not valid with every constructor of this class; see + * the constructors for details. Further, the source raster must have the + * same number of bands as the source ColorSpace, and the destination raster + * must have the same number of bands as the destination ColorSpace. + * + * The source and destination raster (if one is supplied) must also have the + * same dimensions. + * + * @param src The source raster. + * @param dest The destination raster. + * @throws IllegalArgumentException if the rasters and/or color spaces are + * incompatible. + * @return The transformed raster. + */ + public final WritableRaster filter(Raster src, WritableRaster dest) + { + // Various checks to ensure that the rasters and color spaces are compatible + if (spaces.length < 2) + throw new IllegalArgumentException("Not enough information about " + + "source and destination colorspaces."); + + if (spaces[0].getNumComponents() != src.getNumBands() + || (dest != null && spaces[spaces.length - 1].getNumComponents() != dest.getNumBands())) + throw new IllegalArgumentException("Source or destination raster " + + "contains the wrong number of bands."); + + if (dest != null + && (src.getHeight() != dest.getHeight() || src.getWidth() != dest.getWidth())) + throw new IllegalArgumentException("Source and destination rasters " + + "have different dimensions"); + + // Need to iterate through each color space. + // spaces[0] corresponds to the ColorSpace of the source raster, and + // spaces[spaces.length - 1] corresponds to the ColorSpace of the + // destination, with any number (or zero) of intermediate conversions. + + for (int i = 0; i < spaces.length - 2; i++) + { + WritableRaster tmp = createCompatibleDestRaster(src, spaces[i + 1], + false, + src.getTransferType()); + copyraster(src, spaces[i], tmp, spaces[i + 1]); + src = tmp; + } + + // The last conversion is done outside of the loop so that we can + // use the dest raster supplied, instead of creating our own temp raster + if (dest == null) + dest = createCompatibleDestRaster(src, spaces[spaces.length - 1], false, + DataBuffer.TYPE_BYTE); + copyraster(src, spaces[spaces.length - 2], dest, spaces[spaces.length - 1]); + + return dest; + } + + /** + * Creates an empty BufferedImage with the size equal to the source and the + * correct number of bands for the conversion defined in this Op. The newly + * created image is created with the specified ColorModel, or if no ColorModel + * is supplied, an appropriate one is chosen. + * + * @param src The source image. + * @param dstCM A color model for the destination image (may be null). + * @throws IllegalArgumentException if an appropriate colormodel cannot be + * chosen with the information given. + * @return The new compatible destination image. */ public BufferedImage createCompatibleDestImage(BufferedImage src, - ColorModel dstCM) + ColorModel dstCM) { - // FIXME: set properties to those in src + if (dstCM == null && spaces.length == 0) + throw new IllegalArgumentException("Don't know the destination " + + "colormodel"); + + if (dstCM == null) + { + dstCM = createCompatibleColorModel(src, spaces[spaces.length - 1]); + } + return new BufferedImage(dstCM, - src.getRaster().createCompatibleWritableRaster(), - src.isPremultiplied, - null); + createCompatibleDestRaster(src.getRaster(), + dstCM.getColorSpace(), + src.getColorModel().hasAlpha, + dstCM.getTransferType()), + src.isPremultiplied, null); } - public final ICC_Profile[] getICC_Profiles() + /** + * Creates a new WritableRaster with the size equal to the source and the + * correct number of bands. + * + * Note, the new Raster will always use a BYTE storage size, regardless of + * the color model or defined destination; this is for compatibility with + * the reference implementation. + * + * @param src The source Raster. + * @throws IllegalArgumentException if there isn't enough colorspace + * information to create a compatible Raster. + * @return The new compatible destination raster. + */ + public WritableRaster createCompatibleDestRaster(Raster src) { - return profiles; - } + if (spaces.length < 2) + throw new IllegalArgumentException("Not enough destination colorspace " + + "information"); - /** Return the rendering hints for this op. */ - public final RenderingHints getRenderingHints() - { - return hints; + // Create a new raster with the last ColorSpace in the conversion + // chain, and with no alpha (implied) + return createCompatibleDestRaster(src, spaces[spaces.length-1], false, + DataBuffer.TYPE_BYTE); } - /* (non-Javadoc) - * @see java.awt.image.RasterOp#filter(java.awt.image.Raster, java.awt.image.WritableRaster) + /** + * Returns the array of ICC_Profiles used to create this Op, or null if the + * Op was created using ColorSpace arguments. + * + * @return The array of ICC_Profiles, or null. */ - public final WritableRaster filter(Raster src, WritableRaster dest) + public final ICC_Profile[] getICC_Profiles() { - if (!rasterValid) - throw new IllegalArgumentException(); - - // Need to iterate through each color space - there must be at least 2 - for (int i = 1; i < spaces.length - 1; i++) - { - // FIXME: this is wrong. tmp needs to have the same number of bands as - // spaces[i] has. - WritableRaster tmp = createCompatibleDestRaster(src); - copyraster(src, spaces[i - 1], tmp, spaces[i]); - src = tmp; - } - - // FIXME: this is wrong. dst needs to have the same number of bands as - // spaces[i] has. - if (dest == null) - dest = createCompatibleDestRaster(src); - copyraster(src, spaces[spaces.length - 2], - dest, spaces[spaces.length - 1]); - - return dest; + return profiles; } - /* (non-Javadoc) - * @see java.awt.image.RasterOp#createCompatibleDestRaster(java.awt.image.Raster) + /** + * Returns the rendering hints for this op. + * + * @return The rendering hints for this Op, or null. */ - public WritableRaster createCompatibleDestRaster(Raster src) + public final RenderingHints getRenderingHints() { - return src.createCompatibleWritableRaster(); + return hints; } - /** Return corresponding destination point for source point. + /** + * Returns the corresponding destination point for a source point. + * Because this is not a geometric operation, the destination and source + * points will be identical. * - * LookupOp will return the value of src unchanged. * @param src The source point. - * @param dst The destination point. - * @see java.awt.image.RasterOp#getPoint2D(java.awt.geom.Point2D, java.awt.geom.Point2D) + * @param dst The transformed destination point. + * @return The transformed destination point. */ public final Point2D getPoint2D(Point2D src, Point2D dst) { - if (dst == null) return (Point2D)src.clone(); + if (dst == null) + return (Point2D)src.clone(); + dst.setLocation(src); return dst; } - /* (non-Javadoc) - * @see java.awt.image.BufferedImageOp#getBounds2D(java.awt.image.BufferedImage) + /** + * Returns the corresponding destination boundary of a source boundary. + * Because this is not a geometric operation, the destination and source + * boundaries will be identical. + * + * @param src The source boundary. + * @return The boundaries of the destination. */ public final Rectangle2D getBounds2D(BufferedImage src) { return src.getRaster().getBounds(); } - /* (non-Javadoc) - * @see java.awt.image.RasterOp#getBounds2D(java.awt.image.Raster) + /** + * Returns the corresponding destination boundary of a source boundary. + * Because this is not a geometric operation, the destination and source + * boundaries will be identical. + * + * @param src The source boundary. + * @return The boundaries of the destination. */ public final Rectangle2D getBounds2D(Raster src) { return src.getBounds(); } - - // According to Sven de Marothy, we need to copy the src into the dest - // using Graphics2D, in order to use the rendering hints. + + /** + * Copy a source image to a destination image, respecting their colorspaces + * and performing colorspace conversions if necessary. + * + * @param src The source image. + * @param dst The destination image. + */ private void copyimage(BufferedImage src, BufferedImage dst) { + // This is done using Graphics2D in order to respect the rendering hints. Graphics2D gg = dst.createGraphics(); // If no hints are set there is no need to call @@ -297,13 +444,23 @@ public class ColorConvertOp implements BufferedImageOp, RasterOp gg.dispose(); } - private void copyraster(Raster src, ColorSpace scs, WritableRaster dst, - ColorSpace dcs) + /** + * Copy a source raster to a destination raster, performing a colorspace + * conversion between the two. The conversion will respect the + * KEY_COLOR_RENDERING rendering hint if one is present. + * + * @param src The source raster. + * @param scs The colorspace of the source raster. + * @dst The destination raster. + * @dcs The colorspace of the destination raster. + */ + private void copyraster(Raster src, ColorSpace scs, WritableRaster dst, ColorSpace dcs) { float[] sbuf = new float[src.getNumBands()]; - if (hints.get(RenderingHints.KEY_COLOR_RENDERING) == - RenderingHints.VALUE_COLOR_RENDER_QUALITY) + if (hints != null + && hints.get(RenderingHints.KEY_COLOR_RENDERING) == + RenderingHints.VALUE_COLOR_RENDER_QUALITY) { // use cie for accuracy for (int y = src.getMinY(); y < src.getHeight() + src.getMinY(); y++) @@ -321,4 +478,60 @@ public class ColorConvertOp implements BufferedImageOp, RasterOp } } + /** + * This method creates a color model with the same colorspace and alpha + * settings as the source image. The created color model will always be a + * ComponentColorModel and have a BYTE transfer type. + * + * @param img The source image. + * @param cs The ColorSpace to use. + * @return A color model compatible with the source image. + */ + private ColorModel createCompatibleColorModel(BufferedImage img, ColorSpace cs) + { + // The choice of ComponentColorModel and DataBuffer.TYPE_BYTE is based on + // Mauve testing of the reference implementation. + return new ComponentColorModel(cs, + img.getColorModel().hasAlpha(), + img.isAlphaPremultiplied(), + img.getColorModel().getTransparency(), + DataBuffer.TYPE_BYTE); + } + + /** + * This method creates a compatible Raster, given a source raster, colorspace, + * alpha value, and transfer type. + * + * @param src The source raster. + * @param cs The ColorSpace to use. + * @param hasAlpha Whether the raster should include a component for an alpha. + * @param transferType The size of a single data element. + * @return A compatible WritableRaster. + */ + private WritableRaster createCompatibleDestRaster(Raster src, ColorSpace cs, + boolean hasAlpha, + int transferType) + { + // The use of a PixelInterleavedSampleModel weas determined using mauve + // tests, based on the reference implementation + + int numComponents = cs.getNumComponents(); + if (hasAlpha) + numComponents++; + + int[] offsets = new int[numComponents]; + for (int i = 0; i < offsets.length; i++) + offsets[i] = i; + + DataBuffer db = Buffers.createBuffer(transferType, + src.getWidth() * src.getHeight() * numComponents, + 1); + return new WritableRaster(new PixelInterleavedSampleModel(transferType, + src.getWidth(), + src.getHeight(), + numComponents, + numComponents * src.getWidth(), + offsets), + db, new Point(src.getMinX(), src.getMinY())); + } } |