summaryrefslogtreecommitdiff
path: root/java/awt/image
diff options
context:
space:
mode:
Diffstat (limited to 'java/awt/image')
-rw-r--r--java/awt/image/AffineTransformOp.java373
-rw-r--r--java/awt/image/BandCombineOp.java114
-rw-r--r--java/awt/image/ColorConvertOp.java443
-rw-r--r--java/awt/image/ColorModel.java2
-rw-r--r--java/awt/image/ComponentColorModel.java22
-rw-r--r--java/awt/image/ConvolveOp.java128
6 files changed, 776 insertions, 306 deletions
diff --git a/java/awt/image/AffineTransformOp.java b/java/awt/image/AffineTransformOp.java
index bb4b79523..7820684d2 100644
--- a/java/awt/image/AffineTransformOp.java
+++ b/java/awt/image/AffineTransformOp.java
@@ -1,6 +1,6 @@
/* AffineTransformOp.java -- This class performs affine
transformation between two images or rasters in 2 dimensions.
- Copyright (C) 2004 Free Software Foundation
+ Copyright (C) 2004, 2006 Free Software Foundation
This file is part of GNU Classpath.
@@ -52,6 +52,7 @@ import java.util.Arrays;
* rasters in 2 dimensions.
*
* @author Olga Rodimina (rodimina@redhat.com)
+ * @author Francis Kung (fkung@redhat.com)
*/
public class AffineTransformOp implements BufferedImageOp, RasterOp
{
@@ -74,6 +75,7 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp
*
* @param xform AffineTransform that will applied to the source image
* @param interpolationType type of interpolation used
+ * @throws ImagingOpException if the transform matrix is noninvertible
*/
public AffineTransformOp (AffineTransform xform, int interpolationType)
{
@@ -102,6 +104,7 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp
*
* @param xform AffineTransform that will applied to the source image
* @param hints rendering hints that will be used during transformation
+ * @throws ImagingOpException if the transform matrix is noninvertible
*/
public AffineTransformOp (AffineTransform xform, RenderingHints hints)
{
@@ -115,8 +118,7 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp
* Creates empty BufferedImage with the size equal to that of the
* transformed image and correct number of bands. The newly created
* image is created with the specified ColorModel.
- * If the ColorModel is equal to null, then image is created
- * with the ColorModel of the source image.
+ * If the ColorModel is equal to null, an appropriate ColorModel is used.
*
* @param src source image
* @param destCM color model for the destination image
@@ -125,17 +127,21 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp
public BufferedImage createCompatibleDestImage (BufferedImage src,
ColorModel destCM)
{
+ if (destCM != null)
+ return new BufferedImage(destCM,
+ createCompatibleDestRaster(src.getRaster()),
+ src.isAlphaPremultiplied(), null);
+
+ // This behaviour was determined by Mauve testcases, and is compatible
+ // with the reference implementation
+ if (src.getType() == BufferedImage.TYPE_INT_ARGB_PRE
+ || src.getType() == BufferedImage.TYPE_4BYTE_ABGR
+ || src.getType() == BufferedImage.TYPE_4BYTE_ABGR_PRE)
+ return new BufferedImage(src.getWidth(), src.getHeight(), src.getType());
- // if destCm is not specified, use color model of the source image
-
- if (destCM == null)
- destCM = src.getColorModel ();
-
- return new BufferedImage (destCM,
- createCompatibleDestRaster (src.getRaster ()),
- src.isAlphaPremultiplied (),
- null);
-
+ else
+ return new BufferedImage(src.getWidth(), src.getHeight(),
+ BufferedImage.TYPE_INT_ARGB);
}
/**
@@ -148,7 +154,7 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp
*/
public WritableRaster createCompatibleDestRaster (Raster src)
{
- Rectangle rect = (Rectangle) getBounds2D (src);
+ Rectangle2D rect = getBounds2D(src);
// throw RasterFormatException if resulting width or height of the
// transformed raster is 0
@@ -156,33 +162,31 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp
if (rect.getWidth () == 0 || rect.getHeight () == 0)
throw new RasterFormatException("width or height is 0");
- return src.createCompatibleWritableRaster ((int) rect.getWidth (),
- (int) rect.getHeight ());
+ return src.createCompatibleWritableRaster((int) rect.getWidth(),
+ (int) rect.getHeight());
}
/**
* Transforms source image using transform specified at the constructor.
- * The resulting transformed image is stored in the destination image.
+ * The resulting transformed image is stored in the destination image if one
+ * is provided; otherwise a new BufferedImage is created and returned.
*
* @param src source image
* @param dst destination image
+ * @throws IllegalArgumentException if the source and destination image are
+ * the same
* @return transformed source image
*/
public final BufferedImage filter (BufferedImage src, BufferedImage dst)
{
if (dst == src)
- throw new IllegalArgumentException ("src image cannot be the same as the dst image");
-
- // If the destination image is null, then BufferedImage is
- // created with ColorModel of the source image
+ throw new IllegalArgumentException ("src image cannot be the same as " +
+ "the dst image");
+ // If the destination image is null, then use a compatible BufferedImage
if (dst == null)
- dst = createCompatibleDestImage(src, src.getColorModel ());
-
- // FIXME: Must check if color models of src and dst images are the same.
- // If it is not, then source image should be converted to color model
- // of the destination image
+ dst = createCompatibleDestImage(src, null);
Graphics2D gr = (Graphics2D) dst.createGraphics ();
gr.setRenderingHints (hints);
@@ -193,10 +197,13 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp
/**
* Transforms source raster using transform specified at the constructor.
- * The resulting raster is stored in the destination raster.
+ * The resulting raster is stored in the destination raster if it is not
+ * null, otherwise a new raster is created and returned.
*
* @param src source raster
* @param dst destination raster
+ * @throws IllegalArgumentException if the source and destination are not
+ * compatible
* @return transformed raster
*/
public final WritableRaster filter (Raster src, WritableRaster dst)
@@ -212,85 +219,47 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp
throw new IllegalArgumentException("src and dst must have same number"
+ " of bands");
- double[] dpts = new double[dst.getWidth() * 2];
- double[] pts = new double[dst.getWidth() * 2];
+ // Create arrays to hold all the points
+ double[] dstPts = new double[dst.getHeight() * dst.getWidth() * 2];
+ double[] srcPts = new double[dst.getHeight() * dst.getWidth() * 2];
+
+ // Populate array with all points in the *destination* raster
+ int i = 0;
for (int x = 0; x < dst.getWidth(); x++)
- {
- dpts[2 * x] = x + dst.getMinX();
- dpts[2 * x + 1] = x;
- }
- Rectangle srcbounds = src.getBounds();
- if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR))
- {
- for (int y = dst.getMinY(); y < dst.getMinY() + dst.getHeight(); y++)
- {
- try {
- transform.inverseTransform(dpts, 0, pts, 0, dst.getWidth() * 2);
- } catch (NoninvertibleTransformException e) {
- // Can't happen since the constructor traps this
- e.printStackTrace();
- }
-
- for (int x = 0; x < dst.getWidth(); x++)
- {
- if (!srcbounds.contains(pts[2 * x], pts[2 * x + 1]))
- continue;
- dst.setDataElements(x + dst.getMinX(), y,
- src.getDataElements((int)pts[2 * x],
- (int)pts[2 * x + 1],
- null));
- }
- }
- }
- else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BILINEAR))
- {
- double[] tmp = new double[4 * src.getNumBands()];
- for (int y = dst.getMinY(); y < dst.getMinY() + dst.getHeight(); y++)
{
- try {
- transform.inverseTransform(dpts, 0, pts, 0, dst.getWidth() * 2);
- } catch (NoninvertibleTransformException e) {
- // Can't happen since the constructor traps this
- e.printStackTrace();
- }
-
- for (int x = 0; x < dst.getWidth(); x++)
- {
- if (!srcbounds.contains(pts[2 * x], pts[2 * x + 1]))
- continue;
- int xx = (int)pts[2 * x];
- int yy = (int)pts[2 * x + 1];
- double dx = (pts[2 * x] - xx);
- double dy = (pts[2 * x + 1] - yy);
-
- // TODO write this more intelligently
- if (xx == src.getMinX() + src.getWidth() - 1 ||
- yy == src.getMinY() + src.getHeight() - 1)
+ for (int y = 0; y < dst.getHeight(); y++)
{
- // bottom or right edge
- Arrays.fill(tmp, 0);
- src.getPixel(xx, yy, tmp);
+ dstPts[i++] = x;
+ dstPts[i++] = y;
}
- else
- {
- // Normal case
- src.getPixels(xx, yy, 2, 2, tmp);
- for (int b = 0; b < src.getNumBands(); b++)
- tmp[b] = dx * dy * tmp[b]
- + (1 - dx) * dy * tmp[b + src.getNumBands()]
- + dx * (1 - dy) * tmp[b + 2 * src.getNumBands()]
- + (1 - dx) * (1 - dy) * tmp[b + 3 * src.getNumBands()];
- }
- dst.setPixel(x, y, tmp);
- }
}
- }
- else
- {
- // Bicubic
- throw new UnsupportedOperationException("not implemented yet");
- }
+ Rectangle srcbounds = src.getBounds();
+
+ // Use an inverse transform to map each point in the destination to
+ // a point in the source. Note that, while all points in the destination
+ // matrix are integers, this is not necessarily true for points in the
+ // source (hence why interpolation is required)
+ try
+ {
+ AffineTransform inverseTx = transform.createInverse();
+ inverseTx.transform(dstPts, 0, srcPts, 0, dstPts.length / 2);
+ }
+ catch (NoninvertibleTransformException e)
+ {
+ // Shouldn't happen since the constructor traps this
+ throw new ImagingOpException(e.getMessage());
+ }
+
+ // Different interpolation methods...
+ if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR))
+ filterNearest(src, dst, dstPts, srcPts);
+ else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BILINEAR))
+ filterBilinear(src, dst, dstPts, srcPts);
+
+ else // bicubic
+ filterBicubic(src, dst, dstPts, srcPts);
+
return dst;
}
@@ -314,16 +283,7 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp
*/
public final Rectangle2D getBounds2D (Raster src)
{
- // determine new size for the transformed raster.
- // Need to calculate transformed coordinates of the lower right
- // corner of the raster. The upper left corner is always (0,0)
-
- double x2 = (double) src.getWidth () + src.getMinX ();
- double y2 = (double) src.getHeight () + src.getMinY ();
- Point2D p2 = getPoint2D (new Point2D.Double (x2,y2), null);
-
- Rectangle2D rect = new Rectangle (0, 0, (int) p2.getX (), (int) p2.getY ());
- return rect.getBounds ();
+ return transform.createTransformedShape(src.getBounds()).getBounds2D();
}
/**
@@ -333,8 +293,12 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp
*/
public final int getInterpolationType ()
{
- if(hints.containsValue (RenderingHints.VALUE_INTERPOLATION_BILINEAR))
+ if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BILINEAR))
return TYPE_BILINEAR;
+
+ else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BICUBIC))
+ return TYPE_BICUBIC;
+
else
return TYPE_NEAREST_NEIGHBOR;
}
@@ -372,4 +336,191 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp
{
return transform;
}
+
+ /**
+ * Perform nearest-neighbour filtering
+ *
+ * @param src the source raster
+ * @param dst the destination raster
+ * @param dpts array of points on the destination raster
+ * @param pts array of corresponding points on the source raster
+ */
+ private void filterNearest(Raster src, WritableRaster dst, double[] dpts,
+ double[] pts)
+ {
+ Rectangle srcbounds = src.getBounds();
+
+ // For all points on the destination raster, copy the value from the
+ // corrosponding (rounded) source point
+ for (int i = 0; i < dpts.length; i += 2)
+ {
+ int srcX = (int) Math.round(pts[i]) + src.getMinX();
+ int srcY = (int) Math.round(pts[i + 1]) + src.getMinY();
+
+ if (srcbounds.contains(srcX, srcY))
+ dst.setDataElements((int) dpts[i] + dst.getMinX(),
+ (int) dpts[i + 1] + dst.getMinY(),
+ src.getDataElements(srcX, srcY, null));
+ }
+ }
+
+ /**
+ * Perform bilinear filtering
+ *
+ * @param src the source raster
+ * @param dst the destination raster
+ * @param dpts array of points on the destination raster
+ * @param pts array of corresponding points on the source raster
+ */
+ private void filterBilinear(Raster src, WritableRaster dst, double[] dpts,
+ double[] pts)
+ {
+ Rectangle srcbounds = src.getBounds();
+
+ // For all points in the destination raster, use bilinear interpolation
+ // to find the value from the corrosponding source points
+ for (int i = 0; i < dpts.length; i += 2)
+ {
+ int srcX = (int) Math.round(pts[i]) + src.getMinX();
+ int srcY = (int) Math.round(pts[i + 1]) + src.getMinY();
+
+ if (srcbounds.contains(srcX, srcY))
+ {
+ // Corner case at the bottom or right edge; use nearest neighbour
+ if (pts[i] >= src.getWidth() - 1
+ || pts[i + 1] >= src.getHeight() - 1)
+ dst.setDataElements((int) dpts[i] + dst.getMinX(),
+ (int) dpts[i + 1] + dst.getMinY(),
+ src.getDataElements(srcX, srcY, null));
+
+ // Standard case, apply the bilinear formula
+ else
+ {
+ int x = (int) Math.floor(pts[i] + src.getMinX());
+ int y = (int) Math.floor(pts[i + 1] + src.getMinY());
+ double xdiff = pts[i] + src.getMinX() - x;
+ double ydiff = pts[i + 1] + src.getMinY() - y;
+
+ // Run the interpolation for each band
+ for (int j = 0; j < src.getNumBands(); j++)
+ {
+ double result = (src.getSampleDouble(x, y, j) * (1 - xdiff)
+ + src.getSampleDouble(x + 1, y, j) * xdiff)
+ * (1 - ydiff)
+ + (src.getSampleDouble(x, y + 1, j)
+ * (1 - xdiff)
+ + src.getSampleDouble(x + 1, y + 1, j)
+ * xdiff)
+ * ydiff;
+ dst.setSample((int) dpts[i] + dst.getMinX(),
+ (int) dpts[i + 1] + dst.getMinY(),
+ j, result);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Perform bicubic filtering
+ * based on http://local.wasp.uwa.edu.au/~pbourke/colour/bicubic/
+ *
+ * @param src the source raster
+ * @param dst the destination raster
+ * @param dpts array of points on the destination raster
+ * @param pts array of corresponding points on the source raster
+ */
+ private void filterBicubic(Raster src, WritableRaster dst, double[] dpts,
+ double[] pts)
+ {
+ Rectangle srcbounds = src.getBounds();
+
+ // For all points on the destination raster, perform bicubic interpolation
+ // from corrosponding source points
+ double[] result = new double[src.getNumBands()];
+ for (int i = 0; i < dpts.length; i += 2)
+ {
+ if (srcbounds.contains((int) Math.round(pts[i]) + src.getMinX(),
+ (int) Math.round(pts[i + 1]) + src.getMinY()))
+ {
+ int x = (int) Math.floor(pts[i] + src.getMinX());
+ int y = (int) Math.floor(pts[i + 1] + src.getMinY());
+ double dx = pts[i] + src.getMinX() - x;
+ double dy = pts[i + 1] + src.getMinY() - y;
+ Arrays.fill(result, 0);
+
+ for (int m = - 1; m < 3; m++)
+ {
+ for (int n = - 1; n < 3; n++)
+ {
+ // R(x) = ( P(x+2)^3 - 4 P(x+1)^3 + 6 P(x)^3 - 4 P(x-1)^3 ) / 6
+ double r1 = 0;
+ double r2 = 0;
+
+ // Calculate R(m - dx)
+ double rx = m - dx + 2;
+ if (rx > 0)
+ r1 += rx * rx * rx;
+
+ rx = m - dx + 1;
+ if (rx > 0)
+ r1 -= 4 * rx * rx * rx;
+
+ rx = m - dx;
+ if (rx > 0)
+ r1 += 6 * rx * rx * rx;
+
+ rx = m - dx - 1;
+ if (rx > 0)
+ r1 -= 4 * rx * rx * rx;
+
+ r1 /= 6;
+
+ // Calculate R(dy - n);
+ rx = dy - n + 2;
+ if (rx > 0)
+ r2 += rx * rx * rx;
+
+ rx = dy - n + 1;
+ if (rx > 0)
+ r2 -= 4 * rx * rx * rx;
+
+ rx = dy - n;
+ if (rx > 0)
+ r2 += 6 * rx * rx * rx;
+
+ rx = dy - n - 1;
+ if (rx > 0)
+ r2 -= 4 * rx * rx * rx;
+
+ r2 /= 6;
+
+ // Calculate F(i+m, j+n) R(m - dx) R(dy - n)
+ // Check corner cases
+ int srcX = x + m;
+ if (srcX >= src.getMinX() + src.getWidth())
+ srcX = src.getMinX() + src.getWidth() - 1;
+ else if (srcX < src.getMinX())
+ srcX = src.getMinX();
+
+ int srcY = y + n;
+ if (srcY >= src.getMinY() + src.getHeight())
+ srcY = src.getMinY() + src.getHeight() - 1;
+ else if (srcY < src.getMinY())
+ srcY = src.getMinY();
+
+ // Calculate once for each band
+ for (int j = 0; j < result.length; j++)
+ result[j] += src.getSample(srcX, srcY, j) * r1 * r2;
+ }
+ }
+
+ // Put it all together
+ for (int j = 0; j < result.length; j++)
+ dst.setSample((int) dpts[i] + dst.getMinX(),
+ (int) dpts[i + 1] + dst.getMinY(),
+ j, result[j]);
+ }
+ }
+ }
}
diff --git a/java/awt/image/BandCombineOp.java b/java/awt/image/BandCombineOp.java
index 634125ed2..c4e2d5810 100644
--- a/java/awt/image/BandCombineOp.java
+++ b/java/awt/image/BandCombineOp.java
@@ -1,4 +1,5 @@
-/* Copyright (C) 2004 Free Software Foundation
+/* BandCombineOp.java - perform a combination on the bands of a raster
+ Copyright (C) 2004, 2006 Free Software Foundation
This file is part of GNU Classpath.
@@ -36,7 +37,6 @@ exception statement from your version. */
package java.awt.image;
-import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
@@ -65,52 +65,66 @@ public class BandCombineOp implements RasterOp
*
* @param matrix The matrix to filter pixels with.
* @param hints Rendering hints to apply. Ignored.
+ * @throws ArrayIndexOutOfBoundsException if the matrix is invalid
*/
public BandCombineOp(float[][] matrix, RenderingHints hints)
{
- this.matrix = matrix;
+ this.matrix = new float[matrix.length][];
+ int width = matrix[0].length;
+ for (int i = 0; i < matrix.length; i++)
+ {
+ this.matrix[i] = new float[width + 1];
+ for (int j = 0; j < width; j++)
+ this.matrix[i][j] = matrix[i][j];
+
+ // The reference implementation pads the array with a trailing zero...
+ this.matrix[i][width] = 0;
+ }
+
this.hints = hints;
}
/**
- * Filter Raster pixels through a matrix.
- *
- * Applies the Op matrix to source pixes to produce dest pixels. Each row
- * of the matrix is multiplied by the src pixel components to produce the
- * dest pixel. If matrix is one more than the number of bands in the src,
- * the last element is implicitly multiplied by 1, i.e. added to the sum
- * for that dest component.
- *
- * If dest is null, a suitable Raster is created. This implementation uses
- * createCompatibleDestRaster.
+ * Filter Raster pixels through a matrix. Applies the Op matrix to source
+ * pixes to produce dest pixels. Each row of the matrix is multiplied by the
+ * src pixel components to produce the dest pixel. If matrix is one more than
+ * the number of bands in the src, the last element is implicitly multiplied
+ * by 1, i.e. added to the sum for that dest component. If dest is null, a
+ * suitable Raster is created. This implementation uses
+ * createCompatibleDestRaster.
*
* @param src The source Raster.
- * @param dest The destination Raster, or null.
- * @returns The destination Raster or an allocated Raster.
+ * @param dest The destination Raster, or null.
+ * @throws IllegalArgumentException if the destination raster is incompatible
+ * with the source raster.
+ * @return The filtered Raster.
* @see java.awt.image.RasterOp#filter(java.awt.image.Raster,
- *java.awt.image.WritableRaster)
+ * java.awt.image.WritableRaster)
*/
public WritableRaster filter(Raster src, WritableRaster dest) {
if (dest == null)
dest = createCompatibleDestRaster(src);
-
+ else if (dest.getNumBands() != src.getNumBands()
+ || dest.getTransferType() != src.getTransferType())
+ throw new IllegalArgumentException("Destination raster is incompatible with source raster");
+
// Filter the pixels
- float[] spix = new float[matrix[0].length];
+ float[] spix = new float[matrix[0].length - 1];
float[] dpix = new float[matrix.length];
for (int y = src.getMinY(); y < src.getHeight() + src.getMinY(); y++)
for (int x = src.getMinX(); x < src.getWidth() + src.getMinX(); x++)
- {
- // In case matrix rows have implicit translation
- spix[spix.length - 1] = 1.0f;
- src.getPixel(x, y, spix);
- for (int i = 0; i < matrix.length; i++)
{
- dpix[i] = 0;
- for (int j = 0; j < matrix[0].length; j++)
- dpix[i] += spix[j] * matrix[i][j];
+ // In case matrix rows have implicit translation
+ spix[spix.length - 1] = 1.0f;
+ src.getPixel(x, y, spix);
+ for (int i = 0; i < matrix.length; i++)
+ {
+ dpix[i] = 0;
+ for (int j = 0; j < matrix[0].length - 1; j++)
+ dpix[i] += spix[j] * matrix[i][j];
+ }
+ dest.setPixel(x, y, dpix);
}
- dest.setPixel(x, y, dpix);
- }
return dest;
}
@@ -125,28 +139,48 @@ public class BandCombineOp implements RasterOp
/**
* Creates a new WritableRaster that can be used as the destination for this
- * Op. This implementation creates a Banded Raster with data type FLOAT.
- * @see
- *java.awt.image.RasterOp#createCompatibleDestRaster(java.awt.image.Raster)
+ * Op. The number of bands in the source raster must equal the number of rows
+ * in the op matrix, which must also be equal to either the number of columns
+ * or (columns - 1) in the matrix.
+ *
+ * @param src The source raster.
+ * @return A compatible raster.
+ * @see java.awt.image.RasterOp#createCompatibleDestRaster(java.awt.image.Raster)
+ * @throws IllegalArgumentException if the raster is incompatible with the
+ * matrix.
*/
public WritableRaster createCompatibleDestRaster(Raster src)
{
- return Raster.createBandedRaster(DataBuffer.TYPE_FLOAT, src.getWidth(),
- src.getHeight(), matrix.length,
- new Point(src.getMinX(), src.getMinY()));
+ // Destination raster must have same number of bands as source
+ if (src.getNumBands() != matrix.length)
+ throw new IllegalArgumentException("Number of rows in matrix specifies an "
+ + "incompatible number of bands");
+
+ // We use -1 and -2 because we previously padded the rows with a trailing 0
+ if (src.getNumBands() != matrix[0].length - 1
+ && src.getNumBands() != matrix[0].length - 2)
+ throw new IllegalArgumentException("Incompatible number of bands: "
+ + "the number of bands in the raster must equal the number of "
+ + "columns in the matrix, optionally minus one");
+
+ return src.createCompatibleWritableRaster();
}
- /** Return corresponding destination point for source point.
+ /**
+ * Return corresponding destination point for source point. Because this is
+ * not a geometric operation, it simply returns a copy of the source.
*
- * LookupOp will return the value of src unchanged.
* @param src The source point.
* @param dst The destination point.
+ * @return dst The destination point.
* @see java.awt.image.RasterOp#getPoint2D(java.awt.geom.Point2D,
*java.awt.geom.Point2D)
*/
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;
}
@@ -159,7 +193,11 @@ public class BandCombineOp implements RasterOp
return hints;
}
- /** Return the matrix for this Op. */
+ /**
+ * Return the matrix used in this operation.
+ *
+ * @return The matrix used in this operation.
+ */
public final float[][] getMatrix()
{
return matrix;
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()));
+ }
}
diff --git a/java/awt/image/ColorModel.java b/java/awt/image/ColorModel.java
index 9e559db37..fc413d0b4 100644
--- a/java/awt/image/ColorModel.java
+++ b/java/awt/image/ColorModel.java
@@ -628,7 +628,7 @@ public abstract class ColorModel implements Transparency
public ColorModel coerceData(WritableRaster raster,
boolean isAlphaPremultiplied)
{
- if (this.isAlphaPremultiplied == isAlphaPremultiplied)
+ if (this.isAlphaPremultiplied == isAlphaPremultiplied || ! hasAlpha)
return this;
int w = raster.getWidth();
diff --git a/java/awt/image/ComponentColorModel.java b/java/awt/image/ComponentColorModel.java
index f56688f93..f3f3e374b 100644
--- a/java/awt/image/ComponentColorModel.java
+++ b/java/awt/image/ComponentColorModel.java
@@ -42,9 +42,11 @@ import gnu.java.awt.Buffers;
import java.awt.Point;
import java.awt.color.ColorSpace;
+import java.util.Arrays;
public class ComponentColorModel extends ColorModel
{
+ // Find sum of all elements of the array.
private static int sum(int[] values)
{
int sum = 0;
@@ -52,6 +54,22 @@ public class ComponentColorModel extends ColorModel
sum += values[i];
return sum;
}
+
+ // Create an appropriate array of bits, given a colorspace (ie, number of
+ // bands), size of the storage data type, and presence of an alpha band.
+ private static int[] findBits(ColorSpace colorSpace, int transferType,
+ boolean hasAlpha)
+ {
+ int[] bits;
+ if (hasAlpha)
+ bits = new int[colorSpace.getNumComponents()+1];
+ else
+ bits = new int[colorSpace.getNumComponents()];
+
+ Arrays.fill(bits, DataBuffer.getDataTypeSize(transferType));
+
+ return bits;
+ }
public ComponentColorModel(ColorSpace colorSpace, int[] bits,
boolean hasAlpha,
@@ -84,8 +102,8 @@ public class ComponentColorModel extends ColorModel
boolean isAlphaPremultiplied,
int transparency, int transferType)
{
- this(colorSpace, null, hasAlpha, isAlphaPremultiplied,
- transparency, transferType);
+ this(colorSpace, findBits(colorSpace, transferType, hasAlpha), hasAlpha,
+ isAlphaPremultiplied, transparency, transferType);
}
public int getRed(int pixel)
diff --git a/java/awt/image/ConvolveOp.java b/java/awt/image/ConvolveOp.java
index ffb834874..cd3b01131 100644
--- a/java/awt/image/ConvolveOp.java
+++ b/java/awt/image/ConvolveOp.java
@@ -38,7 +38,6 @@ exception statement from your version. */
package java.awt.image;
-import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
@@ -51,11 +50,13 @@ import java.awt.geom.Rectangle2D;
* with elements in the kernel to compute a new pixel.
*
* Each band in a Raster is convolved and copied to the destination Raster.
+ * For BufferedImages, convolution is applied to all components. Color
+ * conversion will be applied if needed.
*
- * For BufferedImages, convolution is applied to all components. If the
- * source is not premultiplied, the data will be premultiplied before
- * convolving. Premultiplication will be undone if the destination is not
- * premultiplied. Color conversion will be applied if needed.
+ * Note that this filter ignores whether the source or destination is alpha
+ * premultiplied. The reference spec states that data will be premultiplied
+ * prior to convolving and divided back out afterwards (if needed), but testing
+ * has shown that this is not the case with their implementation.
*
* @author jlquinn@optonline.net
*/
@@ -104,59 +105,83 @@ public class ConvolveOp implements BufferedImageOp, RasterOp
hints = null;
}
-
- /* (non-Javadoc)
- * @see java.awt.image.BufferedImageOp#filter(java.awt.image.BufferedImage,
- * java.awt.image.BufferedImage)
+ /**
+ * Converts the source image using the kernel 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 convolved image.
*/
public final BufferedImage filter(BufferedImage src, BufferedImage dst)
{
if (src == dst)
- throw new IllegalArgumentException();
+ throw new IllegalArgumentException("Source and destination images " +
+ "cannot be the same.");
if (dst == null)
dst = createCompatibleDestImage(src, src.getColorModel());
// Make sure source image is premultiplied
BufferedImage src1 = src;
- if (!src.isPremultiplied)
+ // The spec says we should do this, but mauve testing shows that Sun's
+ // implementation does not check this.
+ /*
+ if (!src.isAlphaPremultiplied())
{
src1 = createCompatibleDestImage(src, src.getColorModel());
src.copyData(src1.getRaster());
src1.coerceData(true);
}
+ */
BufferedImage dst1 = dst;
- if (!src.getColorModel().equals(dst.getColorModel()))
+ if (src1.getColorModel().getColorSpace().getType() != dst.getColorModel().getColorSpace().getType())
dst1 = createCompatibleDestImage(src, src.getColorModel());
filter(src1.getRaster(), dst1.getRaster());
+ // Since we don't coerceData above, we don't need to divide it back out.
+ // This is wrong (one mauve test specifically tests converting a non-
+ // premultiplied image to a premultiplied image, and it shows that Sun
+ // simply ignores the premultipled flag, contrary to the spec), but we
+ // mimic it for compatibility.
+ /*
+ if (! dst.isAlphaPremultiplied())
+ dst1.coerceData(false);
+ */
+
+ // Convert between color models if needed
if (dst1 != dst)
- {
- // Convert between color models.
- // TODO Check that premultiplied alpha is handled correctly here.
- Graphics2D gg = dst.createGraphics();
- gg.setRenderingHints(hints);
- gg.drawImage(dst1, 0, 0, null);
- gg.dispose();
- }
-
+ new ColorConvertOp(hints).filter(dst1, dst);
+
return dst;
}
- /* (non-Javadoc)
- * @see
- * java.awt.image.BufferedImageOp#createCompatibleDestImage(java.awt.image.BufferedImage,
- * java.awt.image.ColorModel)
+ /**
+ * Creates an empty BufferedImage with the size equal to the source and the
+ * correct number of bands. The new 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).
+ * @return The new compatible destination image.
*/
public BufferedImage createCompatibleDestImage(BufferedImage src,
- ColorModel dstCM)
+ ColorModel dstCM)
{
- // FIXME: set properties to those in src
- return new BufferedImage(dstCM,
- src.getRaster().createCompatibleWritableRaster(),
- src.isPremultiplied, null);
+ if (dstCM != null)
+ return new BufferedImage(dstCM,
+ src.getRaster().createCompatibleWritableRaster(),
+ src.isAlphaPremultiplied(), null);
+
+ return new BufferedImage(src.getWidth(), src.getHeight(), src.getType());
}
/* (non-Javadoc)
@@ -168,6 +193,8 @@ public class ConvolveOp implements BufferedImageOp, RasterOp
}
/**
+ * Get the edge condition for this Op.
+ *
* @return The edge condition.
*/
public int getEdgeCondition()
@@ -185,9 +212,22 @@ public class ConvolveOp implements BufferedImageOp, RasterOp
return (Kernel) kernel.clone();
}
- /* (non-Javadoc)
- * @see java.awt.image.RasterOp#filter(java.awt.image.Raster,
- * java.awt.image.WritableRaster)
+ /**
+ * Converts the source raster using the kernel 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.
+ *
+ * If the convolved value for a sample is outside the range of [0-255], it
+ * will be clipped.
+ *
+ * The source and destination raster (if one is supplied) cannot be the same,
+ * and must also have the same dimensions.
+ *
+ * @param src The source raster.
+ * @param dest The destination raster.
+ * @throws IllegalArgumentException if the rasters identical.
+ * @throws ImagingOpException if the convolution is not possible.
+ * @return The transformed raster.
*/
public final WritableRaster filter(Raster src, WritableRaster dest)
{
@@ -228,7 +268,16 @@ public class ConvolveOp implements BufferedImageOp, RasterOp
v += tmp[tmp.length - i - 1] * kvals[i];
// FIXME: in the above line, I've had to reverse the order of
// the samples array to make the tests pass. I haven't worked
- // out why this is necessary.
+ // out why this is necessary.
+
+ // This clipping is pretty strange, and seems to be hard-coded
+ // at 255 (regardless of the raster's datatype or transfertype).
+ // But it's what the reference does.
+ if (v > 255)
+ v = 255;
+ if (v < 0)
+ v = 0;
+
dest.setSample(x + kernel.getXOrigin(), y + kernel.getYOrigin(),
b, v);
}
@@ -310,13 +359,14 @@ public class ConvolveOp implements BufferedImageOp, RasterOp
return src.getBounds();
}
- /** 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.
*
- * ConvolveOp 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)
{