Merged gcj-eclipse branch to trunk.

From-SVN: r120621
This commit is contained in:
Tom Tromey
2007-01-09 19:58:05 +00:00
parent c648dedbde
commit 97b8365caf
17478 changed files with 606493 additions and 100744 deletions
@@ -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.
@@ -39,6 +39,7 @@ exception statement from your version. */
package java.awt.image;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
@@ -48,10 +49,14 @@ import java.awt.geom.Rectangle2D;
import java.util.Arrays;
/**
* This class performs affine transformation between two images or
* rasters in 2 dimensions.
* AffineTransformOp performs matrix-based transformations (translations,
* scales, flips, rotations, and shears).
*
* If interpolation is required, nearest neighbour, bilinear, and bicubic
* methods are available.
*
* @author Olga Rodimina (rodimina@redhat.com)
* @author Francis Kung (fkung@redhat.com)
*/
public class AffineTransformOp implements BufferedImageOp, RasterOp
{
@@ -74,6 +79,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 +108,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)
{
@@ -112,185 +119,165 @@ 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
* Creates a new BufferedImage with the size equal to that of the
* transformed image and the 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 a ColorModel is not specified, an appropriate ColorModel is used.
*
* @param src source image
* @param destCM color model for the destination image
* @return new compatible destination image
* @param src the source image.
* @param destCM color model for the destination image (can be null).
* @return a new compatible destination image.
*/
public BufferedImage createCompatibleDestImage (BufferedImage src,
ColorModel destCM)
{
if (destCM != null)
return new BufferedImage(destCM,
createCompatibleDestRaster(src.getRaster()),
src.isAlphaPremultiplied(), null);
// 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);
// 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());
else
return new BufferedImage(src.getWidth(), src.getHeight(),
BufferedImage.TYPE_INT_ARGB);
}
/**
* Creates empty WritableRaster with the size equal to the transformed
* source raster and correct number of bands
* Creates a new WritableRaster with the size equal to the transformed
* source raster and correct number of bands .
*
* @param src source raster
* @throws RasterFormatException if resulting width or height of raster is 0
* @return new compatible raster
* @param src the source raster.
* @throws RasterFormatException if resulting width or height of raster is 0.
* @return a new compatible raster.
*/
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
if (rect.getWidth () == 0 || rect.getHeight () == 0)
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
* @return transformed source 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 ());
dst = createCompatibleDestImage(src, null);
// 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
Graphics2D gr = (Graphics2D) dst.createGraphics ();
gr.setRenderingHints (hints);
gr.drawImage (src, transform, null);
Graphics2D gr = (Graphics2D) dst.createGraphics();
gr.setRenderingHints(hints);
gr.drawImage(src, transform, null);
return dst;
}
/**
* 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
* @return transformed raster
* @throws IllegalArgumentException if the source and destination are not
* compatible
* @return transformed raster.
*/
public final WritableRaster filter (Raster src, WritableRaster dst)
public final WritableRaster filter(Raster src, WritableRaster dst)
{
// Initial checks
if (dst == src)
throw new IllegalArgumentException("src image cannot be the same as"
+ " the dst image");
+ " the dst image");
if (dst == null)
dst = createCompatibleDestRaster(src);
if (src.getNumBands() != dst.getNumBands())
throw new IllegalArgumentException("src and dst must have same number"
+ " of bands");
+ " of bands");
double[] dpts = new double[dst.getWidth() * 2];
double[] pts = new double[dst.getWidth() * 2];
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++)
// Optimization for rasters that can be represented in the RGB colormodel:
// wrap the rasters in images, and let Cairo do the transformation
if (ColorModel.getRGBdefault().isCompatibleSampleModel(src.getSampleModel())
&& ColorModel.getRGBdefault().isCompatibleSampleModel(dst.getSampleModel()))
{
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)
{
// bottom or right edge
Arrays.fill(tmp, 0);
src.getPixel(xx, yy, tmp);
}
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);
}
WritableRaster src2 = Raster.createWritableRaster(src.getSampleModel(),
src.getDataBuffer(),
new Point(src.getMinX(),
src.getMinY()));
BufferedImage iSrc = new BufferedImage(ColorModel.getRGBdefault(),
src2, false, null);
BufferedImage iDst = new BufferedImage(ColorModel.getRGBdefault(), dst,
false, null);
return filter(iSrc, iDst).getRaster();
}
}
else
{
// Bicubic
throw new UnsupportedOperationException("not implemented yet");
}
// Otherwise, we need to do the transformation in java code...
// 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++)
{
for (int y = 0; y < dst.getHeight(); y++)
{
dstPts[i++] = x;
dstPts[i++] = y;
}
}
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,27 +301,22 @@ 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();
}
/**
* Returns interpolation type used during transformations
* Returns interpolation type used during transformations.
*
* @return interpolation type
*/
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;
}
@@ -355,7 +337,7 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp
/**
* Returns rendering hints that are used during transformation.
*
* @return rendering hints
* @return the rendering hints used in this Op.
*/
public final RenderingHints getRenderingHints ()
{
@@ -366,10 +348,261 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp
* Returns transform used in transformation between source and destination
* image.
*
* @return transform
* @return the transform used in this Op.
*/
public final AffineTransform getTransform ()
{
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();
Object xyarr = null;
Object xp1arr = null;
Object yp1arr = null;
Object xyp1arr = null;
double xy;
double xp1;
double yp1;
double xyp1;
double[] result = new double[src.getNumBands()];
// 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;
// Get surrounding pixels used in interpolation... optimized
// to use the smallest datatype possible.
if (src.getTransferType() == DataBuffer.TYPE_DOUBLE
|| src.getTransferType() == DataBuffer.TYPE_FLOAT)
{
xyarr = src.getPixel(x, y, (double[])xyarr);
xp1arr = src.getPixel(x+1, y, (double[])xp1arr);
yp1arr = src.getPixel(x, y+1, (double[])yp1arr);
xyp1arr = src.getPixel(x+1, y+1, (double[])xyp1arr);
}
else
{
xyarr = src.getPixel(x, y, (int[])xyarr);
xp1arr = src.getPixel(x+1, y, (int[])xp1arr);
yp1arr = src.getPixel(x, y+1, (int[])yp1arr);
xyp1arr = src.getPixel(x+1, y+1, (int[])xyp1arr);
}
// using
// array[] pixels = src.getPixels(x, y, 2, 2, pixels);
// instead of doing four individual src.getPixel() calls
// should be faster, but benchmarking shows that it's not...
// Run interpolation for each band
for (int j = 0; j < src.getNumBands(); j++)
{
// Pull individual sample values out of array
if (src.getTransferType() == DataBuffer.TYPE_DOUBLE
|| src.getTransferType() == DataBuffer.TYPE_FLOAT)
{
xy = ((double[])xyarr)[j];
xp1 = ((double[])xp1arr)[j];
yp1 = ((double[])yp1arr)[j];
xyp1 = ((double[])xyp1arr)[j];
}
else
{
xy = ((int[])xyarr)[j];
xp1 = ((int[])xp1arr)[j];
yp1 = ((int[])yp1arr)[j];
xyp1 = ((int[])xyp1arr)[j];
}
// If all four samples are identical, there's no need to
// calculate anything
if (xy == xp1 && xy == yp1 && xy == xyp1)
result[j] = xy;
// Run bilinear interpolation formula
else
result[j] = (xy * (1-xdiff) + xp1 * xdiff)
* (1-ydiff)
+ (yp1 * (1-xdiff) + xyp1 * xdiff)
* ydiff;
}
dst.setPixel((int)dpts[i] + dst.getMinX(),
(int)dpts[i+1] + dst.getMinY(),
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();
double[] result = new double[src.getNumBands()];
Object pixels = null;
// For all points on the destination raster, perform bicubic interpolation
// from corrosponding source points
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;
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, using the smallest
// datatype possible
if (src.getTransferType() == DataBuffer.TYPE_DOUBLE
|| src.getTransferType() == DataBuffer.TYPE_FLOAT)
{
pixels = src.getPixel(srcX, srcY, (double[])pixels);
for (int j = 0; j < result.length; j++)
result[j] += ((double[])pixels)[j] * r1 * r2;
}
else
{
pixels = src.getPixel(srcX, srcY, (int[])pixels);
for (int j = 0; j < result.length; j++)
result[j] += ((int[])pixels)[j] * r1 * r2;
}
}
// Put it all together
dst.setPixel((int)dpts[i] + dst.getMinX(),
(int)dpts[i+1] + dst.getMinY(),
result);
}
}
}
}
@@ -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,10 +37,10 @@ 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;
import java.util.Arrays;
/**
* Filter Raster pixels by applying a matrix.
@@ -53,6 +54,9 @@ import java.awt.geom.Rectangle2D;
* for the destination. Therefore the destination Raster must contain the
* same number of bands as the number of rows in the filter matrix.
*
* This Op assumes that samples are integers; floating point sample types will
* be rounded to their nearest integer value during filtering.
*
* @author Jerry Quinn (jlquinn@optonline.net)
*/
public class BandCombineOp implements RasterOp
@@ -65,52 +69,74 @@ 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[] dpix = new float[matrix.length];
int[] spix = new int[matrix[0].length - 1];
int[] spix2 = new int[matrix[0].length - 1];
int[] dpix = new int[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;
src.getPixel(x, y, spix);
// Do not re-calculate if pixel is identical to the last one
// (ie, blocks of the same colour)
if (!Arrays.equals(spix, spix2))
{
System.arraycopy(spix, 0, spix2, 0, spix.length);
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] * (int)matrix[i][j];
}
}
dest.setPixel(x, y, dpix);
}
dest.setPixel(x, y, dpix);
}
return dest;
}
@@ -125,28 +151,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 +205,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;
@@ -38,6 +38,7 @@ exception statement from your version. */
package java.awt.image;
import gnu.java.awt.Buffers;
import gnu.java.awt.ComponentDataBlitOp;
import java.awt.Graphics;
@@ -79,26 +80,36 @@ public class BufferedImage extends Image
TYPE_BYTE_BINARY = 12,
TYPE_BYTE_INDEXED = 13;
static final int[] bits3 = { 8, 8, 8 };
static final int[] bits4 = { 8, 8, 8, 8 };
static final int[] bits1byte = { 8 };
static final int[] bits1ushort = { 16 };
/**
* Vector of TileObservers (or null)
*/
Vector tileObservers;
static final int[] masks_int = { 0x00ff0000,
0x0000ff00,
0x000000ff,
DataBuffer.TYPE_INT };
static final int[] masks_565 = { 0xf800,
0x07e0,
0x001f,
DataBuffer.TYPE_USHORT};
static final int[] masks_555 = { 0x7c00,
0x03e0,
0x001f,
DataBuffer.TYPE_USHORT};
/**
* The image's WritableRaster
*/
WritableRaster raster;
/**
* The associated ColorModel
*/
ColorModel colorModel;
/**
* The image's properties (or null)
*/
Hashtable properties;
/**
* Whether alpha is premultiplied
*/
boolean isPremultiplied;
/**
* The predefined type, if any.
*/
int type;
Vector observers;
/**
* Creates a new <code>BufferedImage</code> with the specified width, height
* and type. Valid <code>type</code> values are:
@@ -119,155 +130,181 @@ public class BufferedImage extends Image
* <li>{@link #TYPE_BYTE_INDEXED}</li>
* </ul>
*
* @param w the width (must be > 0).
* @param h the height (must be > 0).
* @param width the width (must be > 0).
* @param height the height (must be > 0).
* @param type the image type (see the list of valid types above).
*
* @throws IllegalArgumentException if <code>w</code> or <code>h</code> is
* less than or equal to zero.
* @throws IllegalArgumentException if <code>width</code> or
* <code>height</code> is less than or equal to zero.
* @throws IllegalArgumentException if <code>type</code> is not one of the
* specified values.
*/
public BufferedImage(int w, int h, int type)
public BufferedImage(int width, int height, int type)
{
SampleModel sm = null;
ColorModel cm = null;
boolean alpha = false;
boolean premultiplied = false;
switch (type)
boolean premultiplied = (type == BufferedImage.TYPE_INT_ARGB_PRE ||
type == BufferedImage.TYPE_4BYTE_ABGR_PRE);
switch( type )
{
case TYPE_4BYTE_ABGR_PRE:
case TYPE_INT_ARGB_PRE:
premultiplied = true;
// fall through
case TYPE_INT_ARGB:
case TYPE_4BYTE_ABGR:
alpha = true;
}
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
switch (type)
{
case TYPE_INT_RGB:
case TYPE_INT_ARGB:
case TYPE_INT_ARGB_PRE:
case TYPE_USHORT_565_RGB:
case TYPE_USHORT_555_RGB:
int[] masks = null;
switch (type)
{
case TYPE_INT_RGB:
case TYPE_INT_ARGB:
case TYPE_INT_ARGB_PRE:
masks = masks_int;
break;
case TYPE_USHORT_565_RGB:
masks = masks_565;
break;
case TYPE_USHORT_555_RGB:
masks = masks_555;
break;
}
cm = new DirectColorModel(cs,
32, // 32 bits in an int
masks[0], // r
masks[1], // g
masks[2], // b
alpha ? 0xff000000 : 0,
premultiplied,
masks[3] // data type
);
case BufferedImage.TYPE_INT_RGB:
sm = new SinglePixelPackedSampleModel( DataBuffer.TYPE_INT,
width, height,
new int[]{ 0x00FF0000,
0x0000FF00,
0x000000FF } ) ;
cm = new DirectColorModel( 24, 0xff0000, 0xff00, 0xff );
break;
case TYPE_INT_BGR:
String msg =
"FIXME: Programmer is confused. Why (and how) does a " +
"TYPE_INT_BGR image use ComponentColorModel to store " +
"8-bit values? Is data type TYPE_INT or TYPE_BYTE. What " +
"is the difference between TYPE_INT_BGR and TYPE_3BYTE_BGR?";
throw new UnsupportedOperationException(msg);
case TYPE_3BYTE_BGR:
case TYPE_4BYTE_ABGR:
case TYPE_4BYTE_ABGR_PRE:
case TYPE_BYTE_GRAY:
case TYPE_USHORT_GRAY:
int[] bits = null;
int dataType = DataBuffer.TYPE_BYTE;
switch (type) {
case TYPE_3BYTE_BGR:
bits = bits3;
break;
case TYPE_4BYTE_ABGR:
case TYPE_4BYTE_ABGR_PRE:
bits = bits4;
break;
case TYPE_BYTE_GRAY:
bits = bits1byte;
cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
break;
case TYPE_USHORT_GRAY:
bits = bits1ushort;
cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
dataType = DataBuffer.TYPE_USHORT;
break;
}
cm = new ComponentColorModel(cs, bits, alpha, premultiplied,
alpha ?
Transparency.TRANSLUCENT:
Transparency.OPAQUE,
dataType);
case BufferedImage.TYPE_3BYTE_BGR:
sm = new PixelInterleavedSampleModel( DataBuffer.TYPE_BYTE,
width, height,
3, width * 3,
new int[]{ 2, 1, 0 } );
cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
false, false,
BufferedImage.OPAQUE,
DataBuffer.TYPE_BYTE);
break;
case BufferedImage.TYPE_INT_ARGB:
case BufferedImage.TYPE_INT_ARGB_PRE:
sm = new SinglePixelPackedSampleModel( DataBuffer.TYPE_INT,
width, height,
new int[]{ 0x00FF0000,
0x0000FF00,
0x000000FF,
0xFF000000 } );
if (premultiplied)
cm = new DirectColorModel( ColorSpace.getInstance(ColorSpace.CS_sRGB),
32, 0xff0000, 0xff00, 0xff, 0xff000000,
true,
Buffers.smallestAppropriateTransferType(32));
else
cm = new DirectColorModel( 32, 0xff0000, 0xff00, 0xff, 0xff000000 );
break;
case TYPE_BYTE_BINARY:
byte[] vals = { 0, (byte) 0xff };
cm = new IndexColorModel(8, 2, vals, vals, vals);
case BufferedImage.TYPE_4BYTE_ABGR:
case BufferedImage.TYPE_4BYTE_ABGR_PRE:
sm = new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE,
width, height,
4, 4*width,
new int[]{3, 2, 1, 0});
cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
true, premultiplied,
BufferedImage.TRANSLUCENT,
DataBuffer.TYPE_BYTE);
break;
case TYPE_BYTE_INDEXED:
String msg2 = "type not implemented yet";
throw new UnsupportedOperationException(msg2);
// FIXME: build color-cube and create color model
case BufferedImage.TYPE_INT_BGR:
sm = new SinglePixelPackedSampleModel( DataBuffer.TYPE_INT,
width, height,
new int[]{ 0x000000FF,
0x0000FF00,
0x00FF0000 } ) ;
cm = new DirectColorModel( 24, 0xff, 0xff00, 0xff0000 );
break;
case BufferedImage.TYPE_USHORT_565_RGB:
sm = new SinglePixelPackedSampleModel( DataBuffer.TYPE_USHORT,
width, height,
new int[]{ 0xF800,
0x7E0,
0x1F } ) ;
cm = new DirectColorModel( 16, 0xF800, 0x7E0, 0x1F );
break;
case BufferedImage.TYPE_USHORT_555_RGB:
sm = new SinglePixelPackedSampleModel( DataBuffer.TYPE_USHORT,
width, height,
new int[]{ 0x7C00,
0x3E0,
0x1F } ) ;
cm = new DirectColorModel( 15, 0x7C00, 0x3E0, 0x1F );
break;
case BufferedImage.TYPE_BYTE_INDEXED:
cm = createDefaultIndexedColorModel( false );
case BufferedImage.TYPE_BYTE_GRAY:
sm = new PixelInterleavedSampleModel( DataBuffer.TYPE_BYTE,
width, height,
1, width, new int[]{ 0 } );
break;
case BufferedImage.TYPE_USHORT_GRAY:
sm = new PixelInterleavedSampleModel( DataBuffer.TYPE_USHORT,
width, height,
1, width, new int[]{ 0 } );
break;
case BufferedImage.TYPE_BYTE_BINARY:
cm = createDefaultIndexedColorModel( true );
sm = new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE,
width, height, 1);
break;
default:
throw new IllegalArgumentException("Unknown image type " + type);
sm = null;
}
if( sm == null )
throw new IllegalArgumentException("Unknown predefined image type.");
init(cm,
cm.createCompatibleWritableRaster(w, h),
premultiplied,
null, // no properties
type
);
if( cm == null ) // only for the grayscale types
{
int buftype;
int[] bits = new int[1];
if( type == BufferedImage.TYPE_BYTE_GRAY )
{
buftype = DataBuffer.TYPE_BYTE;
bits[0] = 8;
}
else
{
buftype = DataBuffer.TYPE_USHORT;
bits[0] = 16;
}
ColorSpace graySpace = ColorSpace.getInstance( ColorSpace.CS_GRAY );
cm = new ComponentColorModel( graySpace, bits, false, false,
Transparency.OPAQUE, buftype );
}
init( cm,
Raster.createWritableRaster(sm, new Point( 0, 0 ) ),
premultiplied,
null, // no properties
type );
}
public BufferedImage(int w, int h, int type,
IndexColorModel indexcolormodel)
{
if ((type != TYPE_BYTE_BINARY) && (type != TYPE_BYTE_INDEXED))
throw new IllegalArgumentException("type must be binary or indexed");
throw new IllegalArgumentException("Type must be TYPE_BYTE_BINARY or TYPE_BYTE_INDEXED");
if( indexcolormodel.getMapSize() > 16 && type == TYPE_BYTE_BINARY )
throw new IllegalArgumentException("Type TYPE_BYTE_BINARY cannot have a larger than 16-color palette.");
if( indexcolormodel.getMapSize() > 256 )
throw new IllegalArgumentException("Byte type cannot have a larger than 256-color palette.");
init(indexcolormodel,
indexcolormodel.createCompatibleWritableRaster(w, h),
false, // not premultiplied (guess)
null, // no properties
type);
init( indexcolormodel,
indexcolormodel.createCompatibleWritableRaster(w, h),
indexcolormodel.isAlphaPremultiplied(),
null, // no properties
type );
}
public BufferedImage(ColorModel colormodel,
WritableRaster writableraster,
boolean premultiplied,
Hashtable properties)
Hashtable<?,?> properties)
{
init(colormodel, writableraster, premultiplied, properties,
TYPE_CUSTOM);
// TODO: perhaps try to identify type?
}
WritableRaster raster;
ColorModel colorModel;
Hashtable properties;
boolean isPremultiplied;
int type;
private void init(ColorModel cm,
WritableRaster writableraster,
boolean premultiplied,
@@ -280,12 +317,48 @@ public class BufferedImage extends Image
isPremultiplied = premultiplied;
this.type = type;
}
//public void addTileObserver(TileObserver tileobserver) {}
/**
* Creates the default palettes for the predefined indexed color types
* (256-color or black-and-white)
*
* @param binary - If <code>true</code>, a black and white palette,
* otherwise a default 256-color palette is returned.
*/
private IndexColorModel createDefaultIndexedColorModel( boolean binary )
{
if( binary )
{
byte[] t = new byte[]{ 0, (byte)255 };
return new IndexColorModel( 1, 2, t, t, t );
}
byte[] r = new byte[256];
byte[] g = new byte[256];
byte[] b = new byte[256];
int index = 0;
for( int i = 0; i < 6; i++ )
for( int j = 0; j < 6; j++ )
for( int k = 0; k < 6; k++ )
{
r[ index ] = (byte)(i * 51);
g[ index ] = (byte)(j * 51);
b[ index ] = (byte)(k * 51);
index++;
}
while( index < 256 )
{
r[ index ] = g[ index ] = b[ index ] =
(byte)(18 + (index - 216) * 6);
index++;
}
return new IndexColorModel( 8, 256, r, g, b );
}
public void coerceData(boolean premultiplied)
{
colorModel = colorModel.coerceData(raster, premultiplied);
isPremultiplied = premultiplied;
}
public WritableRaster copyData(WritableRaster dest)
@@ -555,7 +628,7 @@ public class BufferedImage extends Image
};
}
public Vector getSources()
public Vector<RenderedImage> getSources()
{
return null;
}
@@ -726,10 +799,10 @@ public class BufferedImage extends Image
*/
public void addTileObserver (TileObserver to)
{
if (observers == null)
observers = new Vector ();
if (tileObservers == null)
tileObservers = new Vector ();
observers.add (to);
tileObservers.add (to);
}
/**
@@ -741,10 +814,10 @@ public class BufferedImage extends Image
*/
public void removeTileObserver (TileObserver to)
{
if (observers == null)
if (tileObservers == null)
return;
observers.remove (to);
tileObservers.remove (to);
}
/**
@@ -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.
* well-defined conversion, an IllegalArgumentException is thrown.
*
* 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.
*
* 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);
}
/**
* 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)
{
if (spaces.length < 2)
throw new IllegalArgumentException("Not enough destination colorspace " +
"information");
// 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);
}
/**
* 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 ICC_Profile[] getICC_Profiles()
{
return profiles;
}
/** Return the rendering hints for this op. */
/**
* Returns the rendering hints for this op.
*
* @return The rendering hints for this Op, or null.
*/
public final RenderingHints getRenderingHints()
{
return hints;
}
/* (non-Javadoc)
* @see java.awt.image.RasterOp#filter(java.awt.image.Raster, java.awt.image.WritableRaster)
*/
public final WritableRaster filter(Raster src, WritableRaster dest)
{
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;
}
/* (non-Javadoc)
* @see java.awt.image.RasterOp#createCompatibleDestRaster(java.awt.image.Raster)
*/
public WritableRaster createCompatibleDestRaster(Raster src)
{
return src.createCompatibleWritableRaster();
}
/** 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()));
}
}
@@ -624,40 +624,40 @@ public abstract class ColorModel implements Transparency
return cspace;
}
// Typically overridden
public ColorModel coerceData(WritableRaster raster,
boolean isAlphaPremultiplied)
boolean isAlphaPremultiplied)
{
if (this.isAlphaPremultiplied == isAlphaPremultiplied)
return this;
// This method should always be overridden, but is not abstract.
throw new UnsupportedOperationException();
}
protected void coerceDataWorker(WritableRaster raster,
boolean isAlphaPremultiplied)
{
int w = raster.getWidth();
int h = raster.getHeight();
int x = raster.getMinX();
int y = raster.getMinY();
int size = w*h;
int size = w * h;
int numColors = getNumColorComponents();
int numComponents = getNumComponents();
int alphaScale = (1<<getComponentSize(numColors)) - 1;
int alphaScale = (1 << getComponentSize(numColors)) - 1;
double[] pixels = raster.getPixels(x, y, w, h, (double[]) null);
for (int i=0; i<size; i++)
for (int i = 0; i < size; i++)
{
double alpha = pixels[i*numComponents+numColors]*alphaScale;
for (int c=0; c<numColors; c++)
{
int offset = i*numComponents+c;
if (isAlphaPremultiplied)
pixels[offset] = pixels[offset]/alpha;
else
pixels[offset] = pixels[offset]*alpha;
}
double alpha = pixels[i * numComponents + numColors] / alphaScale;
for (int c = 0; c < numColors; c++)
{
int offset = i * numComponents + c;
if (isAlphaPremultiplied)
pixels[offset] = Math.round(pixels[offset] * alpha);
else
pixels[offset] = Math.round(pixels[offset] / alpha);
}
}
raster.setPixels(0, 0, w, h, pixels);
// FIXME: what can we return?
return null;
raster.setPixels(0, 0, w, h, pixels);
}
/**
@@ -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)
@@ -288,17 +306,16 @@ public class ComponentColorModel extends ColorModel
public ColorModel coerceData(WritableRaster raster,
boolean isAlphaPremultiplied) {
if (this.isAlphaPremultiplied == isAlphaPremultiplied)
if (this.isAlphaPremultiplied == isAlphaPremultiplied || !hasAlpha())
return this;
/* TODO: provide better implementation based on the
assumptions we can make due to the specific type of the
color model. */
super.coerceData(raster, isAlphaPremultiplied);
super.coerceDataWorker(raster, isAlphaPremultiplied);
return new ComponentColorModel(cspace, bits, hasAlpha(),
isAlphaPremultiplied, // argument
transparency, transferType);
return new ComponentColorModel(cspace, hasAlpha, isAlphaPremultiplied,
transparency, transferType);
}
public boolean isCompatibleRaster(Raster raster)
@@ -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)
{
@@ -209,6 +249,11 @@ public class ConvolveOp implements BufferedImageOp, RasterOp
int top = kernel.getYOrigin();
int bottom = Math.max(kHeight - top - 1, 0);
// Calculate max sample values for clipping
int[] maxValue = src.getSampleModel().getSampleSize();
for (int i = 0; i < maxValue.length; i++)
maxValue[i] = (int)Math.pow(2, maxValue[i]) - 1;
// process the region that is reachable...
int regionW = src.width - left - right;
int regionH = src.height - top - bottom;
@@ -228,7 +273,14 @@ 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 is undocumented, but determined by testing.
if (v > maxValue[b])
v = maxValue[b];
else if (v < 0)
v = 0;
dest.setSample(x + kernel.getXOrigin(), y + kernel.getYOrigin(),
b, v);
}
@@ -310,13 +362,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)
{
@@ -91,11 +91,12 @@ public class CropImageFilter extends ImageFilter
*
* @param props the list of properties associated with this image
*/
public void setProperties(Hashtable props)
public void setProperties(Hashtable<?, ?> props)
{
props.put("filters", "CropImageFilter");
if (consumer != null)
consumer.setProperties(props);
Hashtable<Object, Object> prop2 = (Hashtable<Object, Object>) props;
prop2.put("filters", "CropImageFilter");
if (consumer != null)
consumer.setProperties(prop2);
}
/**
@@ -393,20 +393,20 @@ public class DirectColorModel extends PackedColorModel
return Buffers.getData(buffer);
}
public final ColorModel coerceData (WritableRaster raster,
boolean isAlphaPremultiplied)
public ColorModel coerceData (WritableRaster raster,
boolean isAlphaPremultiplied)
{
if (this.isAlphaPremultiplied == isAlphaPremultiplied)
if (this.isAlphaPremultiplied == isAlphaPremultiplied || !hasAlpha())
return this;
/* TODO: provide better implementation based on the
assumptions we can make due to the specific type of the
color model. */
super.coerceData(raster, isAlphaPremultiplied);
return new ComponentColorModel(cspace, bits, hasAlpha(),
isAlphaPremultiplied, // argument
transparency, transferType);
super.coerceDataWorker(raster, isAlphaPremultiplied);
return new DirectColorModel(cspace, pixel_bits, getRedMask(),
getGreenMask(), getBlueMask(), getAlphaMask(),
isAlphaPremultiplied, transferType);
}
public boolean isCompatibleRaster(Raster raster)
@@ -136,7 +136,7 @@ public interface ImageConsumer
*
* @param props the list of properties associated with this image
*/
void setProperties(Hashtable props);
void setProperties(Hashtable<?,?> props);
/**
* This <code>ColorModel</code> should indicate the model used by
+163 -165
View File
@@ -49,180 +49,178 @@ import java.util.Hashtable;
*/
public class ImageFilter implements ImageConsumer, Cloneable
{
/**
* The consumer this filter is filtering an image data stream for.
* It is initialized in the method <code>getFilterInstance</code>.
*/
protected ImageConsumer consumer = null;
/**
* The consumer this filter is filtering an image data stream for.
* It is initialized in the method <code>getFilterInstance</code>.
*/
protected ImageConsumer consumer = null;
/**
* The <code>ImageConsumer</code> can use this method to request
* the pixels be delivered in top-down, left-right order.
* <br>
* The filter can respond in three different ways.
* <ul>
* <li>The default behavior is to forward the request to the
* <code>ImageProducer</code>
* using the method <code>requestTopDownLeftRightResend</code>
* and using the filter as the consumer.</li>
* <li>The filter has the pixels and can retransmit them in the
* top-down, left-right order.</li>
* <li>The filter can do nothing when this method is called.</li>
* </ul>
*/
public void resendTopDownLeftRight(ImageProducer ip)
{
ip.requestTopDownLeftRightResend(this);
}
/**
* The <code>ImageConsumer</code> can use this method to request
* the pixels be delivered in top-down, left-right order.
* <br>
* The filter can respond in three different ways.
* <ul>
* <li>The default behavior is to forward the request to the
* <code>ImageProducer</code>
* using the method <code>requestTopDownLeftRightResend</code>
* and using the filter as the consumer.</li>
* <li>The filter has the pixels and can retransmit them in the
* top-down, left-right order.</li>
* <li>The filter can do nothing when this method is called.</li>
* </ul>
*/
public void resendTopDownLeftRight(ImageProducer ip)
{
ip.requestTopDownLeftRightResend(this);
}
/**
* By default, returns a shallow copy of the object created by
* <code>Object.clone()</code>
*
* @see java.lang.Object#clone ()
*/
public Object clone()
{
try
{
return super.clone();
}
catch (CloneNotSupportedException e)
{
// This should never happen as this class implements the
// Cloneable interface.
throw new InternalError ();
}
}
/**
* By default, returns a shallow copy of the object created by
* <code>Object.clone()</code>
*
* @see java.lang.Object#clone ()
*/
public Object clone()
{
try
{
return super.clone();
}
catch (CloneNotSupportedException e)
{
// This should never happen as this class implements the
// Cloneable interface.
throw new InternalError ();
}
}
/**
* This is the only method which can set the
* <code>ImageConsumer</code> for this filter. By default a clone
* of this filter with the appropriate consumer set is returned.
*
* @see #clone ()
*/
public ImageFilter getFilterInstance(ImageConsumer ic)
{
if ( ic == null )
throw new IllegalArgumentException("null argument for ImageFilter.getFilterInstance(ImageConsumer)");
/**
* This is the only method which can set the
* <code>ImageConsumer</code> for this filter. By default a clone
* of this filter with the appropriate consumer set is returned.
*
* @see #clone ()
*/
public ImageFilter getFilterInstance(ImageConsumer ic)
{
ImageFilter f = (ImageFilter)clone();
f.consumer = ic;
return f;
}
consumer = ic;
ImageFilter f = (ImageFilter)clone();
consumer = null;
return f;
}
/**
* An <code>ImageProducer</code> indicates the size of the image
* being produced using this method. A filter can override this
* method to intercept these calls from the producer in order to
* change either the width or the height before in turn calling
* the consumer's <code>setDimensions</code> method.
*
* @param width the width of the image
* @param height the height of the image
*/
public void setDimensions(int width, int height)
{
consumer.setDimensions(width, height);
}
/**
* An <code>ImageProducer</code> indicates the size of the image
* being produced using this method. A filter can override this
* method to intercept these calls from the producer in order to
* change either the width or the height before in turn calling
* the consumer's <code>setDimensions</code> method.
*
* @param width the width of the image
* @param height the height of the image
*/
public void setDimensions(int width, int height)
{
if (consumer != null)
consumer.setDimensions(width, height);
}
/**
* An <code>ImageProducer</code> can set a list of properties
* associated with this image by using this method.
*
* @param props the list of properties associated with this image
*/
public void setProperties(Hashtable<?,?> props)
{
Hashtable copy = (Hashtable) props.clone();
Object o = copy.get("filters");
if (o == null)
copy.put("filters", toString());
else if (o instanceof String)
copy.put("filters", ((String) o) + toString());
/**
* An <code>ImageProducer</code> can set a list of properties
* associated with this image by using this method.
*
* @param props the list of properties associated with this image
*/
public void setProperties(Hashtable props)
{
props.put("filters", "ImageFilter");
if (consumer != null)
consumer.setProperties(props);
}
consumer.setProperties(copy);
}
/**
* Override this method to process calls to this method from the
* <code>ImageProducer</code>. By default the <code>setColorModel</code>
* method of the consumer is called with the specified <code>model</code>.
*
* @param model the color model to be used most often by setPixels
* @see ColorModel */
public void setColorModel(ColorModel model)
{
if (consumer != null)
consumer.setColorModel(model);
}
/**
* Override this method to process calls to this method from the
* <code>ImageProducer</code>. By default the <code>setColorModel</code>
* method of the consumer is called with the specified <code>model</code>.
*
* @param model the color model to be used most often by setPixels
*
* @see ColorModel
*/
public void setColorModel(ColorModel model)
{
consumer.setColorModel(model);
}
/**
* The <code>ImageProducer</code> should call this method with a
* bit mask of hints from any of <code>RANDOMPIXELORDER</code>,
* <code>TOPDOWNLEFTRIGHT</code>, <code>COMPLETESCANLINES</code>,
* <code>SINGLEPASS</code>, <code>SINGLEFRAME</code> from the
* <code>ImageConsumer</code> interface.
*
* @param flags a bit mask of hints
* @see ImageConsumer
*/
public void setHints(int flags)
{
if (consumer != null)
consumer.setHints(flags);
}
/**
* The <code>ImageProducer</code> should call this method with a
* bit mask of hints from any of <code>RANDOMPIXELORDER</code>,
* <code>TOPDOWNLEFTRIGHT</code>, <code>COMPLETESCANLINES</code>,
* <code>SINGLEPASS</code>, <code>SINGLEFRAME</code> from the
* <code>ImageConsumer</code> interface.
*
* @param flags a bit mask of hints
* @see ImageConsumer
*/
public void setHints(int flags)
{
consumer.setHints(flags);
}
/**
* This function delivers a rectangle of pixels where any
* pixel(m,n) is stored in the array as a <code>byte</code> at
* index (n * scansize + m + offset).
*
* @param x the x coordinate of the rectangle
* @param y the y coordinate of the rectangle
* @param w the width of the rectangle
* @param h the height of the rectangle
* @param model the <code>ColorModel</code> used to translate the pixels
* @param pixels the array of pixel values
* @param offset the index of the first pixels in the <code>pixels</code> array
* @param scansize the width to use in extracting pixels from the <code>pixels</code> array
*/
public void setPixels(int x, int y, int w, int h,
ColorModel model, byte[] pixels, int offset, int scansize)
{
if (consumer != null)
consumer.setPixels(x, y, w, h, model, pixels, offset, scansize);
}
/**
* This function delivers a rectangle of pixels where any
* pixel(m,n) is stored in the array as a <code>byte</code> at
* index (n * scansize + m + offset).
*
* @param x the x coordinate of the rectangle
* @param y the y coordinate of the rectangle
* @param w the width of the rectangle
* @param h the height of the rectangle
* @param model the <code>ColorModel</code> used to translate the pixels
* @param pixels the array of pixel values
* @param offset the index of the first pixels in the <code>pixels</code> array
* @param scansize the width to use in extracting pixels from the <code>pixels</code> array
*/
public void setPixels(int x, int y, int w, int h,
ColorModel model, byte[] pixels, int offset,
int scansize)
{
consumer.setPixels(x, y, w, h, model, pixels, offset, scansize);
}
/**
* This function delivers a rectangle of pixels where any
* pixel(m,n) is stored in the array as an <code>int</code> at
* index (n * scansize + m + offset).
*
* @param x the x coordinate of the rectangle
* @param y the y coordinate of the rectangle
* @param w the width of the rectangle
* @param h the height of the rectangle
* @param model the <code>ColorModel</code> used to translate the pixels
* @param pixels the array of pixel values
* @param offset the index of the first pixels in the <code>pixels</code> array
* @param scansize the width to use in extracting pixels from the <code>pixels</code> array
*/
public void setPixels(int x, int y, int w, int h,
ColorModel model, int[] pixels, int offset, int scansize)
{
if (consumer != null)
consumer.setPixels(x, y, w, h, model, pixels, offset, scansize);
}
/**
* This function delivers a rectangle of pixels where any
* pixel(m,n) is stored in the array as an <code>int</code> at
* index (n * scansize + m + offset).
*
* @param x the x coordinate of the rectangle
* @param y the y coordinate of the rectangle
* @param w the width of the rectangle
* @param h the height of the rectangle
* @param model the <code>ColorModel</code> used to translate the pixels
* @param pixels the array of pixel values
* @param offset the index of the first pixels in the <code>pixels</code> array
* @param scansize the width to use in extracting pixels from the <code>pixels</code> array
*/
public void setPixels(int x, int y, int w, int h,
ColorModel model, int[] pixels, int offset,
int scansize)
{
consumer.setPixels(x, y, w, h, model, pixels, offset, scansize);
}
/**
* The <code>ImageProducer</code> calls this method to indicate a
* single frame or the entire image is complete. The method is
* also used to indicate an error in loading or producing the
* image.
*/
public void imageComplete(int status)
{
if (consumer != null)
consumer.imageComplete(status);
}
/**
* The <code>ImageProducer</code> calls this method to indicate a
* single frame or the entire image is complete. The method is
* also used to indicate an error in loading or producing the
* image.
*/
public void imageComplete(int status)
{
consumer.imageComplete(status);
}
}
@@ -134,10 +134,6 @@ public class IndexColorModel extends ColorModel
if (size < 1)
throw new IllegalArgumentException("size < 1");
map_size = size;
if (0 <= trans && trans < size) {
this.trans = trans;
transparency = BITMASK;
}
rgb = new int[size];
for (int i = 0; i < size; i++)
{
@@ -146,6 +142,9 @@ public class IndexColorModel extends ColorModel
| ((greens[i] & 0xff) << 8)
| (blues[i] & 0xff));
}
setTransparentPixel(trans);
// Generate a bigint with 1's for every pixel
validBits = validBits.setBit(size).subtract(BigInteger.ONE);
}
@@ -275,8 +274,6 @@ public class IndexColorModel extends ColorModel
throw new IllegalArgumentException("size < 1");
map_size = size;
opaque = !hasAlpha;
if (0 <= trans && trans < size)
this.trans = trans;
rgb = new int[size];
if (hasAlpha)
@@ -318,6 +315,8 @@ public class IndexColorModel extends ColorModel
transparency = BITMASK;
}
setTransparentPixel(trans);
// Generate a bigint with 1's for every pixel
validBits = validBits.setBit(size).subtract(BigInteger.ONE);
}
@@ -361,9 +360,6 @@ public class IndexColorModel extends ColorModel
throw new IllegalArgumentException("size < 1");
map_size = size;
opaque = !hasAlpha;
if (0 <= trans && trans < size)
this.trans = trans;
rgb = new int[size];
if (!hasAlpha)
for (int i = 0; i < size; i++)
@@ -371,6 +367,8 @@ public class IndexColorModel extends ColorModel
else
System.arraycopy(cmap, start, rgb, 0, size);
setTransparentPixel(trans);
// Generate a bigint with 1's for every pixel
validBits = validBits.setBit(size).subtract(BigInteger.ONE);
}
@@ -584,12 +582,7 @@ public class IndexColorModel extends ColorModel
*/
public final int getAlpha(int pixel)
{
if (opaque && pixel != trans)
return 255;
if ((pixel == trans && trans != -1) || pixel >= map_size)
return 0;
return (0xFF000000 & rgb[pixel]) >> 24;
return (rgb[pixel] >> 24) & 0xFF;
}
/**
@@ -694,4 +687,43 @@ public class IndexColorModel extends ColorModel
return im;
}
/**
* Creates a {@link SampleModel} that is compatible to this color model.
* This will be a {@link MultiPixelPackedSampleModel} for bits/pixel of
* 1, 2 or 4, or a {@link ComponentColorModel} for the other cases.
*
* @param w the width of the sample model to create
* @param h the height of the sample model to create
*
* @return a compatible sample model
*/
public SampleModel createCompatibleSampleModel(int w, int h)
{
SampleModel sm;
if (pixel_bits == 1 || pixel_bits == 2 || pixel_bits == 4)
sm = new MultiPixelPackedSampleModel(transferType, w, h, pixel_bits);
else
sm = new ComponentSampleModel(transferType, w, h, 1, w, new int[]{0});
return sm;
}
/**
* Sets the transparent pixel. This is called by the various constructors.
*
* @param t the transparent pixel
*/
private void setTransparentPixel(int t)
{
if (t >= 0 && t < map_size)
{
rgb[t] &= 0xffffff; // Make the value transparent.
trans = t;
if (transparency == OPAQUE)
{
transparency = BITMASK;
hasAlpha = true;
}
}
}
}
+98 -43
View File
@@ -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;
@@ -67,7 +66,8 @@ public class LookupOp implements BufferedImageOp, RasterOp
private LookupTable lut;
private RenderingHints hints;
/** Construct a new LookupOp.
/**
* Construct a new LookupOp using the given LookupTable.
*
* @param lookup LookupTable to use.
* @param hints Rendering hints (can be null).
@@ -78,16 +78,40 @@ public class LookupOp implements BufferedImageOp, RasterOp
this.hints = hints;
}
/* (non-Javadoc)
* @see java.awt.image.BufferedImageOp#filter(java.awt.image.BufferedImage, java.awt.image.BufferedImage)
/**
* Converts the source image using the lookup table 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 image cannot use an IndexColorModel, and the destination image
* (if one is provided) must have the same size.
*
* @param src The source image.
* @param dst The destination image.
* @throws IllegalArgumentException if the rasters and/or color spaces are
* incompatible.
* @throws ArrayIndexOutOfBoundsException if a pixel in the source is not
* contained in the LookupTable.
* @return The convolved image.
*/
public final BufferedImage filter(BufferedImage src, BufferedImage dst)
{
if (src.getColorModel() instanceof IndexColorModel)
throw new IllegalArgumentException("LookupOp.filter: IndexColorModel "
+ "not allowed");
if (lut.getNumComponents() != 1
&& lut.getNumComponents() != src.getColorModel().getNumComponents()
&& lut.getNumComponents() != src.getColorModel().getNumColorComponents())
throw new IllegalArgumentException("LookupOp.filter: Incompatible " +
"lookup table and source image");
if (dst == null)
dst = createCompatibleDestImage(src, src.getColorModel());
dst = createCompatibleDestImage(src, null);
else if (src.getHeight() != dst.getHeight() || src.getWidth() != dst.getWidth())
throw new IllegalArgumentException("Source and destination images are " +
"different sizes.");
// Set up for potential colormodel mismatch
BufferedImage tgt;
@@ -116,33 +140,35 @@ public class LookupOp implements BufferedImageOp, RasterOp
sr.getPixel(x, y, dbuf);
System.arraycopy(dbuf, 0, tmp, 0, tmpBands);
dr.setPixel(x, y, lut.lookupPixel(tmp, dbuf));
/* The reference implementation does not use LookupTable.lookupPixel,
* but rather it seems to copy the table into a native array. The
* effect of this (a probable bug in their implementation) is that
* an out-of-bounds lookup on a ByteLookupTable will *not* throw an
* out of bounds exception, but will instead return random garbage.
* A bad lookup on a ShortLookupTable, however, will throw an
* exception.
*
* Instead of mimicing this behaviour, we always throw an
* ArrayOutofBoundsException by virtue of using
* LookupTable.lookupPixle.
*/
}
}
else if (lut.getNumComponents() != 1
&&
lut.getNumComponents() != src.getColorModel().getNumComponents())
throw new IllegalArgumentException("LookupOp.filter: "
+ "Incompatible lookup "
+ "table and source image");
// No alpha to ignore
int[] dbuf = new int[src.getColorModel().getNumComponents()];
// Filter the pixels
for (int y = src.getMinY(); y < src.getHeight() + src.getMinY(); y++)
for (int x = src.getMinX(); x < src.getWidth() + src.getMinX(); x++)
dr.setPixel(x, y, lut.lookupPixel(sr.getPixel(x, y, dbuf), dbuf));
if (tgt != dst)
else
{
// Convert between color models.
// TODO Check that premultiplied alpha is handled correctly here.
Graphics2D gg = dst.createGraphics();
gg.setRenderingHints(hints);
gg.drawImage(tgt, 0, 0, null);
gg.dispose();
// No alpha to ignore
int[] dbuf = new int[src.getColorModel().getNumComponents()];
// Filter the pixels
for (int y = src.getMinY(); y < src.getHeight() + src.getMinY(); y++)
for (int x = src.getMinX(); x < src.getWidth() + src.getMinX(); x++)
dr.setPixel(x, y, lut.lookupPixel(sr.getPixel(x, y, dbuf), dbuf));
}
if (tgt != dst)
new ColorConvertOp(hints).filter(tgt, dst);
return dst;
}
@@ -160,18 +186,27 @@ public class LookupOp implements BufferedImageOp, RasterOp
public BufferedImage createCompatibleDestImage(BufferedImage src,
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);
// This is a strange exception, done for compatibility with the reference
// (as demonstrated by a mauve testcase)
int imgType = src.getType();
if (imgType == BufferedImage.TYPE_USHORT_GRAY)
imgType = BufferedImage.TYPE_BYTE_GRAY;
return new BufferedImage(src.getWidth(), src.getHeight(), imgType);
}
/** Return corresponding destination point for source point.
/**
* Returns the corresponding destination point for a given source point.
*
* This Op will return the source point unchanged.
*
* 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)
*/
public final Point2D getPoint2D(Point2D src, Point2D dst)
{
@@ -182,7 +217,11 @@ public class LookupOp implements BufferedImageOp, RasterOp
return dst;
}
/** Return the LookupTable for this op. */
/**
* Return the LookupTable for this op.
*
* @return The lookup table.
*/
public final LookupTable getTable()
{
return lut;
@@ -196,7 +235,8 @@ public class LookupOp implements BufferedImageOp, RasterOp
return hints;
}
/** Filter a raster through a lookup table.
/**
* Filter a raster through a lookup table.
*
* Applies the lookup table for this Rasterop to each pixel of src and
* puts the results in dest. If dest is null, a new Raster is created and
@@ -206,8 +246,9 @@ public class LookupOp implements BufferedImageOp, RasterOp
* @param dest The destination raster.
* @return The WritableRaster with the filtered pixels.
* @throws IllegalArgumentException if lookup table has more than one
* component but not the same as src and dest.
* @see java.awt.image.RasterOp#filter(java.awt.image.Raster, java.awt.image.WritableRaster)
* component but not the same as src and dest.
* @throws ArrayIndexOutOfBoundsException if a pixel in the source is not
* contained in the LookupTable.
*/
public final WritableRaster filter(Raster src, WritableRaster dest)
{
@@ -216,12 +257,13 @@ public class LookupOp implements BufferedImageOp, RasterOp
dest = createCompatibleDestRaster(src);
else
if (src.getNumBands() != dest.getNumBands())
throw new IllegalArgumentException();
if (lut.getNumComponents() != 1
&& lut.getNumComponents() != src.getNumBands())
throw new IllegalArgumentException();
throw new IllegalArgumentException("Source and destination rasters " +
"are incompatible.");
if (lut.getNumComponents() != 1
&& lut.getNumComponents() != src.getNumBands())
throw new IllegalArgumentException("Lookup table is incompatible with " +
"this raster.");
// Allocate pixel storage.
int[] tmp = new int[src.getNumBands()];
@@ -230,6 +272,19 @@ public class LookupOp implements BufferedImageOp, RasterOp
for (int y = src.getMinY(); y < src.getHeight() + src.getMinY(); y++)
for (int x = src.getMinX(); x < src.getWidth() + src.getMinX(); x++)
dest.setPixel(x, y, lut.lookupPixel(src.getPixel(x, y, tmp), tmp));
/* The reference implementation does not use LookupTable.lookupPixel,
* but rather it seems to copy the table into a native array. The
* effect of this (a probable bug in their implementation) is that
* an out-of-bounds lookup on a ByteLookupTable will *not* throw an
* out of bounds exception, but will instead return random garbage.
* A bad lookup on a ShortLookupTable, however, will throw an
* exception.
*
* Instead of mimicing this behaviour, we always throw an
* ArrayOutofBoundsException by virtue of using
* LookupTable.lookupPixle.
*/
return dest;
}
@@ -1,5 +1,5 @@
/* MemoryImageSource.java -- Java class for providing image data
Copyright (C) 1999, 2004 Free Software Foundation, Inc.
Copyright (C) 1999, 2004, 2006, Free Software Foundation, Inc.
This file is part of GNU Classpath.
@@ -41,6 +41,9 @@ package java.awt.image;
import java.util.Hashtable;
import java.util.Vector;
/**
* An image producer that delivers image data from an array.
*/
public class MemoryImageSource implements ImageProducer
{
private boolean animated = false;
@@ -73,10 +76,19 @@ public class MemoryImageSource implements ImageProducer
}
/**
* Constructs an ImageProducer from memory
* Constructs an ImageProducer from memory.
*
* @param w the image width.
* @param h the image height.
* @param cm the color model.
* @param pix the image data.
* @param off the offset to the first pixel in the array.
* @param scan the number of array elements from a pixel on one row to the
* corresponding pixel on the next row.
* @param props image properties (<code>null</code> permitted).
*/
public MemoryImageSource(int w, int h, ColorModel cm, byte[] pix, int off,
int scan, Hashtable props)
int scan, Hashtable<?,?> props)
{
width = w;
height = h;
@@ -106,10 +118,19 @@ public class MemoryImageSource implements ImageProducer
}
/**
Constructs an ImageProducer from memory
*/
* Constructs an ImageProducer from memory
*
* @param w the image width.
* @param h the image height.
* @param cm the color model.
* @param pix the image data.
* @param off the offset to the first pixel in the array.
* @param scan the number of array elements from a pixel on one row to the
* corresponding pixel on the next row.
* @param props image properties (<code>null</code> permitted).
*/
public MemoryImageSource(int w, int h, ColorModel cm, int[] pix, int off,
int scan, Hashtable props)
int scan, Hashtable<?,?> props)
{
width = w;
height = h;
@@ -122,16 +143,32 @@ public class MemoryImageSource implements ImageProducer
}
/**
* Constructs an ImageProducer from memory using the default RGB ColorModel
* Constructs an ImageProducer from memory using the default RGB ColorModel.
*
* @param w the image width.
* @param h the image height.
* @param pix the image data.
* @param off the offset to the first pixel in the array.
* @param scan the number of array elements from a pixel on one row to the
* corresponding pixel on the next row.
* @param props image properties (<code>null</code> permitted).
*/
public MemoryImageSource(int w, int h, int[] pix, int off, int scan,
Hashtable props)
Hashtable<?,?> props)
{
this(w, h, ColorModel.getRGBdefault(), pix, off, scan, props);
}
/**
* Constructs an ImageProducer from memory using the default RGB ColorModel
* Constructs an ImageProducer from memory using the default RGB ColorModel.
*
* @param w the image width.
* @param h the image height.
* @param pix the image data.
* @param off the offset to the first pixel in the array.
* @param scan the number of array elements from a pixel on one row to the
* corresponding pixel on the next row.
*/
public MemoryImageSource(int w, int h, int[] pix, int off, int scan)
{
@@ -141,6 +178,8 @@ public class MemoryImageSource implements ImageProducer
/**
* Used to register an <code>ImageConsumer</code> with this
* <code>ImageProducer</code>.
*
* @param ic the image consumer.
*/
public synchronized void addConsumer(ImageConsumer ic)
{
@@ -153,6 +192,8 @@ public class MemoryImageSource implements ImageProducer
/**
* Used to determine if the given <code>ImageConsumer</code> is
* already registered with this <code>ImageProducer</code>.
*
* @param ic the image consumer.
*/
public synchronized boolean isConsumer(ImageConsumer ic)
{
@@ -164,6 +205,8 @@ public class MemoryImageSource implements ImageProducer
/**
* Used to remove an <code>ImageConsumer</code> from the list of
* registered consumers for this <code>ImageProducer</code>.
*
* @param ic the image consumer.
*/
public synchronized void removeConsumer(ImageConsumer ic)
{
@@ -197,6 +240,8 @@ public class MemoryImageSource implements ImageProducer
* Used to register an <code>ImageConsumer</code> with this
* <code>ImageProducer</code> and then request that this producer
* resend the image data in the order top-down, left-right.
*
* @param ic the image consumer.
*/
public void requestTopDownLeftRightResend(ImageConsumer ic)
{
@@ -219,7 +264,7 @@ public class MemoryImageSource implements ImageProducer
* sending animation. If this flag is set then full buffers are sent
* in the newPixels methods instead of just regions.
*
* @param fullbuffers - a flag indicating whether to send the full buffers
* @param fullbuffers a flag indicating whether to send the full buffers
*/
public synchronized void setFullBufferUpdates(boolean fullbuffers)
{
@@ -260,6 +305,11 @@ public class MemoryImageSource implements ImageProducer
/**
* Send an animation frame to the image consumers containing the specified
* pixels unless setFullBufferUpdates is set.
*
* @param x the x-coordinate.
* @param y the y-coordinate.
* @param w the width.
* @param h the height.
*/
public synchronized void newPixels(int x, int y, int w, int h)
{
@@ -306,6 +356,12 @@ public class MemoryImageSource implements ImageProducer
*
* If framenotify is set then a notification is sent when the frame
* is sent otherwise no status is sent.
*
* @param x the x-coordinate.
* @param y the y-coordinate.
* @param w the width.
* @param h the height.
* @param framenotify send notification?
*/
public synchronized void newPixels(int x, int y, int w, int h,
boolean framenotify)
@@ -55,7 +55,7 @@ public class PixelGrabber implements ImageConsumer
ColorModel model = ColorModel.getRGBdefault();
int hints;
Hashtable props;
Hashtable<?,?> props;
int int_pixel_buffer[];
boolean ints_delivered = false;
@@ -442,7 +442,7 @@ public class PixelGrabber implements ImageConsumer
* @param props a list of properties associated with the image being
* produced
*/
public synchronized void setProperties(Hashtable props)
public synchronized void setProperties(Hashtable<?,?> props)
{
this.props = props;
}
@@ -46,228 +46,220 @@ package java.awt.image;
*/
public abstract class RGBImageFilter extends ImageFilter
{
protected ColorModel origmodel;
protected ColorModel origmodel;
protected ColorModel newmodel;
protected ColorModel newmodel;
/**
Specifies whether to apply the filter to the index entries of the
IndexColorModel. Subclasses should set this to true if the filter
does not depend on the pixel's coordinate.
*/
protected boolean canFilterIndexColorModel = false;
/**
* Specifies whether to apply the filter to the index entries of the
* IndexColorModel. Subclasses should set this to true if the filter
* does not depend on the pixel's coordinate.
*/
protected boolean canFilterIndexColorModel = false;
/**
Construct new RGBImageFilter.
*/
public RGBImageFilter()
{
}
/**
* Construct new RGBImageFilter.
*/
public RGBImageFilter()
{
}
/**
* Sets the ColorModel used to filter with. If the specified ColorModel is IndexColorModel
* and canFilterIndexColorModel is true, we subsitute the ColorModel for a filtered one
* here and in setPixels whenever the original one appears. Otherwise overrides the default
* ColorModel of ImageProducer and specifies the default RGBColorModel
*
* @param model the color model to be used most often by setPixels
* @see ColorModel */
public void setColorModel(ColorModel model)
{
origmodel = model;
newmodel = model;
/**
* Sets the ColorModel used to filter with. If the specified ColorModel is
* IndexColorModel and canFilterIndexColorModel is true, we subsitute the
* ColorModel for a filtered one here and in setPixels whenever the original
* one appears. Otherwise overrides the default ColorModel of ImageProducer
* and specifies the default RGBColorModel
*
* @param model the color model to be used most often by setPixels
*
* @see ColorModel
*/
public void setColorModel(ColorModel model)
{
if ((model instanceof IndexColorModel) && canFilterIndexColorModel)
{
ColorModel newCM = filterIndexColorModel((IndexColorModel) model);
substituteColorModel(model, newCM);
consumer.setColorModel(newmodel);
}
else
{
consumer.setColorModel(ColorModel.getRGBdefault());
}
}
if( ( model instanceof IndexColorModel) && canFilterIndexColorModel ) {
newmodel = filterIndexColorModel( (IndexColorModel) model );
if (consumer != null)
consumer.setColorModel(newmodel);
}
else {
if (consumer != null)
consumer.setColorModel(ColorModel.getRGBdefault());
}
}
/**
Registers a new ColorModel to subsitute for the old ColorModel when
setPixels encounters the a pixel with the old ColorModel. The pixel
remains unchanged except for a new ColorModel.
@param oldcm the old ColorModel
@param newcm the new ColorModel
*/
public void substituteColorModel(ColorModel oldcm,
ColorModel newcm)
{
origmodel = oldcm;
newmodel = newcm;
}
/**
* Registers a new ColorModel to subsitute for the old ColorModel when
* setPixels encounters the a pixel with the old ColorModel. The pixel
* remains unchanged except for a new ColorModel.
*
* @param oldcm the old ColorModel
* @param newcm the new ColorModel
*/
public void substituteColorModel(ColorModel oldcm, ColorModel newcm)
{
origmodel = oldcm;
newmodel = newcm;
}
/**
Filters an IndexColorModel through the filterRGB function. Uses
coordinates of -1 to indicate its filtering an index and not a pixel.
/**
* Filters an IndexColorModel through the filterRGB function. Uses
* coordinates of -1 to indicate its filtering an index and not a pixel.
*
* @param icm an IndexColorModel to filter
*/
public IndexColorModel filterIndexColorModel(IndexColorModel icm)
{
int len = icm.getMapSize();
byte[] reds = new byte[len];
byte[] greens = new byte[len];
byte[] blues = new byte[len];
byte[] alphas = new byte[len];
@param icm an IndexColorModel to filter
*/
public IndexColorModel filterIndexColorModel(IndexColorModel icm)
{
int len = icm.getMapSize(), rgb;
byte reds[] = new byte[len], greens[] = new byte[len], blues[] = new byte[len], alphas[] = new byte[len];
icm.getAlphas( alphas );
icm.getReds( reds );
icm.getGreens( greens );
icm.getBlues( blues );
icm.getAlphas( alphas );
icm.getReds( reds );
icm.getGreens( greens );
icm.getBlues( blues );
for( int i = 0; i < len; i++ )
{
rgb = filterRGB( -1, -1, makeColor ( alphas[i], reds[i], greens[i], blues[i] ) );
alphas[i] = (byte)(( 0xff000000 & rgb ) >> 24);
reds[i] = (byte)(( 0xff0000 & rgb ) >> 16);
greens[i] = (byte)(( 0xff00 & rgb ) >> 8);
blues[i] = (byte)(0xff & rgb);
}
return new IndexColorModel( icm.getPixelSize(), len, reds, greens, blues, alphas );
}
int transparent = icm.getTransparentPixel();
boolean needAlpha = false;
for( int i = 0; i < len; i++ )
{
int rgb = filterRGB(-1, -1, icm.getRGB(i));
alphas[i] = (byte) (rgb >> 24);
if (alphas[i] != ((byte) 0xff) && i != transparent)
needAlpha = true;
reds[i] = (byte) (rgb >> 16);
greens[i] = (byte) (rgb >> 8);
blues[i] = (byte) (rgb);
}
IndexColorModel newIcm;
if (needAlpha)
newIcm = new IndexColorModel(icm.getPixelSize(), len, reds, greens,
blues, alphas);
else
newIcm = new IndexColorModel(icm.getPixelSize(), len, reds, greens,
blues, transparent);
return newIcm;
}
private int makeColor( byte a, byte r, byte g, byte b )
{
return ( 0xff000000 & (a << 24) | 0xff0000 & (r << 16) | 0xff00 & (g << 8) | 0xff & b );
}
/**
* This functions filters a set of RGB pixels through filterRGB.
*
* @param x the x coordinate of the rectangle
* @param y the y coordinate of the rectangle
* @param w the width of the rectangle
* @param h the height of the rectangle
* @param pixels the array of pixel values
* @param offset the index of the first pixels in the
* <code>pixels</code> array
* @param scansize the width to use in extracting pixels from the
* <code>pixels</code> array
*/
public void filterRGBPixels(int x, int y, int w, int h, int[] pixels,
int offset, int scansize)
{
int index = offset;
for (int yp = 0; yp < h; yp++)
{
for (int xp = 0; xp < w; xp++)
{
pixels[index] = filterRGB(xp + x, yp + y, pixels[index]);
index++;
}
index += scansize - w;
}
consumer.setPixels(x, y, w, h, ColorModel.getRGBdefault(), pixels, offset,
scansize);
}
/**
This functions filters a set of RGB pixels through filterRGB.
/**
* If the ColorModel is the same ColorModel which as already converted
* then it converts it the converted ColorModel. Otherwise it passes the
* array of pixels through filterRGBpixels.
*
* @param x the x coordinate of the rectangle
* @param y the y coordinate of the rectangle
* @param w the width of the rectangle
* @param h the height of the rectangle
* @param model the <code>ColorModel</code> used to translate the pixels
* @param pixels the array of pixel values
* @param offset the index of the first pixels in the <code>pixels</code>
* array
* @param scansize the width to use in extracting pixels from the
* <code>pixels</code> array
*/
public void setPixels(int x, int y, int w, int h, ColorModel model,
byte[] pixels, int offset, int scansize)
{
if (model == origmodel)
{
consumer.setPixels(x, y, w, h, newmodel, pixels, offset, scansize);
}
else
{
int[] filtered = new int[w];
int index = offset;
for (int yp = 0; yp < h; yp++)
{
for (int xp = 0; xp < w; xp++)
{
filtered[xp] = model.getRGB((pixels[index] & 0xff));
index++;
}
index += scansize - w;
filterRGBPixels(x, y + yp, w, 1, filtered, 0, w);
}
}
}
@param x the x coordinate of the rectangle
@param y the y coordinate of the rectangle
@param w the width of the rectangle
@param h the height of the rectangle
@param pixels the array of pixel values
@param offset the index of the first pixels in the <code>pixels</code> array
@param scansize the width to use in extracting pixels from the <code>pixels</code> array
*/
public void filterRGBPixels(int x, int y, int w, int h, int[] pixels,
int offset, int scansize)
{
for (int yp = 0; yp < h; yp++)
{
for (int xp = 0; xp < w; xp++)
{
pixels[offset + xp] = filterRGB(xp + x, yp + y, pixels[offset + xp]);
}
offset += scansize;
}
}
/**
* This function delivers a rectangle of pixels where any
* pixel(m,n) is stored in the array as an <code>int</code> at
* index (n * scansize + m + offset).
*
* @param x the x coordinate of the rectangle
* @param y the y coordinate of the rectangle
* @param w the width of the rectangle
* @param h the height of the rectangle
* @param model the <code>ColorModel</code> used to translate the pixels
* @param pixels the array of pixel values
* @param offset the index of the first pixels in the <code>pixels</code>
* array
* @param scansize the width to use in extracting pixels from the
* <code>pixels</code> array
*/
public void setPixels(int x, int y, int w, int h, ColorModel model,
int[] pixels, int offset, int scansize)
{
if (model == origmodel)
{
consumer.setPixels(x, y, w, h, newmodel, pixels, offset, scansize);
}
else
{
int[] filtered = new int[w];
int index = offset;
for (int yp = 0; yp < h; yp++)
{
for (int xp = 0; xp < w; xp++)
{
filtered[xp] = model.getRGB((pixels[index] & 0xff));
index++;
}
index += scansize - w;
filterRGBPixels(x, y + yp, w, 1, filtered, 0, w);
}
}
}
/**
* If the ColorModel is the same ColorModel which as already converted
* then it converts it the converted ColorModel. Otherwise it passes the
* array of pixels through filterRGBpixels.
*
* @param x the x coordinate of the rectangle
* @param y the y coordinate of the rectangle
* @param w the width of the rectangle
* @param h the height of the rectangle
* @param model the <code>ColorModel</code> used to translate the pixels
* @param pixels the array of pixel values
* @param offset the index of the first pixels in the <code>pixels</code> array
* @param scansize the width to use in extracting pixels from the <code>pixels</code> array
*/
public void setPixels(int x, int y, int w, int h,
ColorModel model, byte[] pixels,
int offset, int scansize)
{
if(model == origmodel && (model instanceof IndexColorModel) && canFilterIndexColorModel)
{
if (consumer != null)
consumer.setPixels(x, y, w, h, newmodel, pixels, offset, scansize);
}
else
{
int intPixels[] =
convertColorModelToDefault( x, y, w, h, model, pixels, offset, scansize );
filterRGBPixels( x, y, w, h, intPixels, offset, scansize );
if (consumer != null)
consumer.setPixels(x, y, w, h, ColorModel.getRGBdefault(), intPixels, offset, scansize);
}
}
/**
* This function delivers a rectangle of pixels where any
* pixel(m,n) is stored in the array as an <code>int</code> at
* index (n * scansize + m + offset).
*
* @param x the x coordinate of the rectangle
* @param y the y coordinate of the rectangle
* @param w the width of the rectangle
* @param h the height of the rectangle
* @param model the <code>ColorModel</code> used to translate the pixels
* @param pixels the array of pixel values
* @param offset the index of the first pixels in the <code>pixels</code> array
* @param scansize the width to use in extracting pixels from the <code>pixels</code> array
*/
public void setPixels(int x, int y, int w, int h,
ColorModel model, int[] pixels,
int offset, int scansize)
{
if(model == origmodel && (model instanceof IndexColorModel) && canFilterIndexColorModel)
{
if (consumer != null)
consumer.setPixels(x, y, w, h, newmodel, pixels, offset, scansize);
}
else
{
//FIXME: Store the filtered pixels in a separate temporary buffer?
convertColorModelToDefault( x, y, w, h, model, pixels, offset, scansize );
filterRGBPixels( x, y, w, h, pixels, offset, scansize );
if (consumer != null)
consumer.setPixels(x, y, w, h, ColorModel.getRGBdefault(), pixels, offset, scansize);
}
}
private int[] convertColorModelToDefault(int x, int y, int w, int h,
ColorModel model, byte pixels[],
int offset, int scansize)
{
int intPixels[] = new int[pixels.length];
for (int i = 0; i < pixels.length; i++)
intPixels[i] = makeColorbyDefaultCM(model, pixels[i]);
return intPixels;
}
private void convertColorModelToDefault(int x, int y, int w, int h,
ColorModel model, int pixels[],
int offset, int scansize)
{
for (int i = 0; i < pixels.length; i++)
pixels[i] = makeColorbyDefaultCM(model, pixels[i]);
}
private int makeColorbyDefaultCM(ColorModel model, byte rgb)
{
return makeColor( model.getAlpha( rgb ) * 4, model.getRed( rgb ) * 4, model.getGreen( rgb ) * 4, model.getBlue( rgb ) * 4 );
}
private int makeColorbyDefaultCM(ColorModel model, int rgb)
{
return makeColor( model.getAlpha( rgb ), model.getRed( rgb ), model.getGreen( rgb ), model.getBlue( rgb ) );
}
private int makeColor( int a, int r, int g, int b )
{
return (int)( 0xff000000 & (a << 24) | 0xff0000 & (r << 16) | 0xff00 & (g << 8) | 0xff & b );
}
/**
Filters a single pixel from the default ColorModel.
@param x x-coordinate
@param y y-coordinate
@param rgb color
*/
public abstract int filterRGB(int x,
int y,
int rgb);
/**
* Filters a single pixel from the default ColorModel.
*
* @param x x-coordinate
* @param y y-coordinate
* @param rgb color
*/
public abstract int filterRGB(int x, int y, int rgb);
}
+4 -3
View File
@@ -511,9 +511,10 @@ public class Raster
int height, int childMinX, int childMinY,
int[] bandList)
{
/* FIXME: Throw RasterFormatException if child bounds extends
beyond the bounds of this raster. */
if (parentX < minX || parentX + width > minX + this.width
|| parentY < minY || parentY + height > minY + this.height)
throw new RasterFormatException("Child raster extends beyond parent");
SampleModel sm = (bandList == null) ?
sampleModel :
sampleModel.createSubsetSampleModel(bandList);
@@ -46,7 +46,7 @@ import java.util.Vector;
*/
public interface RenderedImage
{
Vector getSources();
Vector<RenderedImage> getSources();
Object getProperty(String name);
String[] getPropertyNames();
ColorModel getColorModel();
@@ -46,6 +46,7 @@ import java.util.Hashtable;
* exact method is not defined by Sun but some sort of fast Box filter should
* probably be correct.
* <br>
* Currently this filter does nothing and needs to be implemented.
*
* @author C. Brian Jones (cbj@gnu.org)
*/
@@ -116,11 +117,11 @@ public class ReplicateScaleFilter extends ImageFilter
}
else if (destWidth < 0)
{
destWidth = (int) (width * ((double) destHeight / srcHeight));
destWidth = width * destHeight / srcHeight;
}
else if (destHeight < 0)
{
destHeight = (int) (height * ((double) destWidth / srcWidth));
destHeight = height * destWidth / srcWidth;
}
if (consumer != null)
@@ -133,11 +134,12 @@ public class ReplicateScaleFilter extends ImageFilter
*
* @param props the list of properties associated with this image
*/
public void setProperties(Hashtable props)
public void setProperties(Hashtable<?, ?> props)
{
props.put("filters", "ReplicateScaleFilter");
if (consumer != null)
consumer.setProperties(props);
Hashtable<Object, Object> prop2 = (Hashtable<Object, Object>) props;
prop2.put("filters", "ReplicateScaleFilter");
if (consumer != null)
consumer.setProperties(prop2);
}
/**
@@ -157,19 +159,35 @@ public class ReplicateScaleFilter extends ImageFilter
public void setPixels(int x, int y, int w, int h,
ColorModel model, byte[] pixels, int offset, int scansize)
{
double rx = ((double) srcWidth) / destWidth;
double ry = ((double) srcHeight) / destHeight;
int destScansize = (int) Math.round(scansize / rx);
byte[] destPixels = replicatePixels(x, y, w, h,
model, pixels, offset, scansize,
rx, ry, destScansize);
if (consumer != null)
consumer.setPixels((int) Math.floor(x/rx), (int) Math.floor(y/ry),
(int) Math.ceil(w/rx), (int) Math.ceil(h/ry),
model, destPixels, 0, destScansize);
if (srcrows == null || srccols == null)
setupSources();
int dx1 = (2 * x * destWidth + srcWidth - 1) / (2 * destWidth);
int dy1 = (2 * y * destHeight + srcHeight - 1) / (2 * destHeight);
byte[] pix;
if (outpixbuf != null && outpixbuf instanceof byte[])
{
pix = (byte[]) outpixbuf;
}
else
{
pix = new byte[destWidth];
outpixbuf = pix;
}
int sy, sx;
for (int yy = dy1; (sy = srcrows[yy]) < y + h; yy++)
{
int offs = offset + scansize * (sy - y);
int xx;
for (xx = dx1; (sx = srccols[xx]) < x + w; xx++)
{
pix[xx] = pixels[offs + sx - x];
}
if (xx > dx1)
{
consumer.setPixels(dx1, yy, xx - dx1, 1, model, pix, dx1,
destWidth);
}
}
}
/**
@@ -189,59 +207,52 @@ public class ReplicateScaleFilter extends ImageFilter
public void setPixels(int x, int y, int w, int h,
ColorModel model, int[] pixels, int offset, int scansize)
{
double rx = ((double) srcWidth) / destWidth;
double ry = ((double) srcHeight) / destHeight;
int destScansize = (int) Math.round(scansize / rx);
int[] destPixels = replicatePixels(x, y, w, h,
model, pixels, offset, scansize,
rx, ry, destScansize);
if (consumer != null)
consumer.setPixels((int) Math.floor(x/rx), (int) Math.floor(y/ry),
(int) Math.ceil(w/rx), (int) Math.ceil(h/ry),
model, destPixels, 0, destScansize);
if (srcrows == null || srccols == null)
setupSources();
int dx1 = (2 * x * destWidth + srcWidth - 1) / (2 * destWidth);
int dy1 = (2 * y * destHeight + srcHeight - 1) / (2 * destHeight);
int[] pix;
if (outpixbuf != null && outpixbuf instanceof int[])
{
pix = (int[]) outpixbuf;
}
else
{
pix = new int[destWidth];
outpixbuf = pix;
}
int sy, sx;
for (int yy = dy1; (sy = srcrows[yy]) < y + h; yy++)
{
int offs = offset + scansize * (sy - y);
int xx;
for (xx = dx1; (sx = srccols[xx]) < x + w; xx++)
{
pix[xx] = pixels[offs + sx - x];
}
if (xx > dx1)
{
consumer.setPixels(dx1, yy, xx - dx1, 1, model, pix, dx1,
destWidth);
}
}
}
private byte[] replicatePixels(int srcx, int srcy, int srcw, int srch,
ColorModel model, byte[] srcPixels,
int srcOffset, int srcScansize,
double rx, double ry, int destScansize)
{
byte[] destPixels =
new byte[(int) Math.ceil(srcw/rx) * (int) Math.ceil(srch/ry)];
int a, b;
for (int i = 0; i < destPixels.length; i++)
{
a = (int) ((int) ( ((double) i) / destScansize) * ry) * srcScansize;
b = (int) ((i % destScansize) * rx);
if ((a + b + srcOffset) < srcPixels.length)
destPixels[i] = srcPixels[a + b + srcOffset];
}
return destPixels;
}
private int[] replicatePixels(int srcx, int srcy, int srcw, int srch,
ColorModel model, int[] srcPixels,
int srcOffset, int srcScansize,
double rx, double ry, int destScansize)
{
int[] destPixels =
new int[(int) Math.ceil(srcw/rx) * (int) Math.ceil(srch/ry)];
int a, b;
for (int i = 0; i < destPixels.length; i++)
{
a = (int) ((int) ( ((double) i) / destScansize) * ry) * srcScansize;
b = (int) ((i % destScansize) * rx);
if ((a + b + srcOffset) < srcPixels.length)
destPixels[i] = srcPixels[a + b + srcOffset];
}
return destPixels;
}
/**
* Sets up the srcrows and srccols arrays.
*/
private void setupSources()
{
srcrows = new int[destHeight + 1];
for (int y = 0; y <= destHeight; y++)
{
srcrows[y] = (2 * y * srcHeight + srcHeight) / (2 * destHeight);
}
srccols = new int[destWidth + 1];
for (int x = 0; x <= destWidth; x++)
{
srccols[x] = (2 * x * srcWidth + srcWidth) / (2 * destWidth);
}
}
}
+231 -64
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2004 Free Software Foundation
/* Copyright (C) 2004, 2006 Free Software Foundation
This file is part of GNU Classpath.
@@ -43,7 +43,23 @@ import java.awt.geom.Rectangle2D;
import java.util.Arrays;
/**
* RescaleOp is a filter that changes each pixel by a scaling factor and offset.
*
* For filtering Rasters, either one scaling factor and offset can be specified,
* which will be applied to all bands; or a scaling factor and offset can be
* specified for each band.
*
* For BufferedImages, the scaling may apply to both color and alpha components.
* If only one scaling factor is provided, or if the number of factors provided
* equals the number of color components, the scaling is performed on all color
* components. Otherwise, the scaling is performed on all components including
* alpha. Alpha premultiplication is ignored.
*
* After filtering, if color conversion is necessary, the conversion happens,
* taking alpha premultiplication into account.
*
* @author Jerry Quinn (jlquinn@optonline.net)
* @author Francis Kung (fkung@redhat.com)
*/
public class RescaleOp implements BufferedImageOp, RasterOp
{
@@ -51,15 +67,43 @@ public class RescaleOp implements BufferedImageOp, RasterOp
private float[] offsets;
private RenderingHints hints = null;
/**
* Create a new RescaleOp object using the given scale factors and offsets.
*
* The length of the arrays must be equal to the number of bands (or number of
* data or color components) of the raster/image that this Op will be used on,
* otherwise an IllegalArgumentException will be thrown when calling the
* filter method.
*
* @param scaleFactors an array of scale factors.
* @param offsets an array of offsets.
* @param hints any rendering hints to use (can be null).
* @throws NullPointerException if the scaleFactors or offsets array is null.
*/
public RescaleOp(float[] scaleFactors,
float[] offsets,
RenderingHints hints)
{
this.scale = scaleFactors;
this.offsets = offsets;
int length = Math.min(scaleFactors.length, offsets.length);
scale = new float[length];
System.arraycopy(scaleFactors, 0, this.scale, 0, length);
this.offsets = new float[length];
System.arraycopy(offsets, 0, this.offsets, 0, length);
this.hints = hints;
}
/**
* Create a new RescaleOp object using the given scale factor and offset.
*
* The same scale factor and offset will be used on all bands/components.
*
* @param scaleFactor the scale factor to use.
* @param offset the offset to use.
* @param hints any rendering hints to use (can be null).
*/
public RescaleOp(float scaleFactor,
float offset,
RenderingHints hints)
@@ -69,22 +113,47 @@ public class RescaleOp implements BufferedImageOp, RasterOp
this.hints = hints;
}
/**
* Returns the scaling factors. This method accepts an optional array, which
* will be used to store the factors if not null (this avoids allocating a
* new array). If this array is too small to hold all the scaling factors,
* the array will be filled and the remaining factors discarded.
*
* @param scaleFactors array to store the scaling factors in (can be null).
* @return an array of scaling factors.
*/
public final float[] getScaleFactors(float[] scaleFactors)
{
if (scaleFactors == null)
scaleFactors = new float[scale.length];
System.arraycopy(scale, 0, scaleFactors, 0, scale.length);
System.arraycopy(scale, 0, scaleFactors, 0, Math.min(scale.length,
scaleFactors.length));
return scaleFactors;
}
/**
* Returns the offsets. This method accepts an optional array, which
* will be used to store the offsets if not null (this avoids allocating a
* new array). If this array is too small to hold all the offsets, the array
* will be filled and the remaining factors discarded.
*
* @param offsets array to store the offsets in (can be null).
* @return an array of offsets.
*/
public final float[] getOffsets(float[] offsets)
{
if (offsets == null)
offsets = new float[this.offsets.length];
System.arraycopy(this.offsets, 0, offsets, 0, this.offsets.length);
System.arraycopy(this.offsets, 0, offsets, 0, Math.min(this.offsets.length,
offsets.length));
return offsets;
}
/**
* Returns the number of scaling factors / offsets.
*
* @return the number of scaling factors / offsets.
*/
public final int getNumFactors()
{
return scale.length;
@@ -98,36 +167,74 @@ public class RescaleOp implements BufferedImageOp, RasterOp
return hints;
}
/* (non-Javadoc)
* @see java.awt.image.BufferedImageOp#filter(java.awt.image.BufferedImage, java.awt.image.BufferedImage)
/**
* Converts the source image using the scale factors and offsets 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 image cannot use an IndexColorModel, and the destination image
* (if one is provided) must have the same size.
*
* If the final value of a sample is beyond the range of the color model, it
* will be clipped to the appropriate maximum / minimum.
*
* @param src The source image.
* @param dst The destination image.
* @throws IllegalArgumentException if the rasters and/or color spaces are
* incompatible.
* @return The rescaled image.
*/
public final BufferedImage filter(BufferedImage src, BufferedImage dst)
{
// TODO Make sure premultiplied alpha is handled correctly.
// TODO See that color conversion is handled.
// TODO figure out how to use rendering hints.
if (scale.length != offsets.length)
throw new IllegalArgumentException();
// Initial checks
if (scale.length != 1
&& scale.length != src.getColorModel().getNumComponents()
&& (scale.length != src.getColorModel().getNumColorComponents()))
throw new IllegalArgumentException("Source image has wrong number of "
+ "bands for these scaling factors.");
ColorModel scm = src.getColorModel();
if (dst == null) dst = createCompatibleDestImage(src, null);
if (dst == null)
dst = createCompatibleDestImage(src, null);
else if (src.getHeight() != dst.getHeight()
|| src.getWidth() != dst.getWidth())
throw new IllegalArgumentException("Source and destination images are "
+ "different sizes.");
WritableRaster wsrc = src.getRaster();
WritableRaster wdst = dst.getRaster();
// Share constant across colors except alpha
if (scale.length == 1 || scale.length == scm.getNumColorComponents())
// Prepare for possible colorspace conversion
BufferedImage dst2 = dst;
if (dst.getColorModel().getColorSpace().getType() != src.getColorModel().getColorSpace().getType())
dst2 = createCompatibleDestImage(src, src.getColorModel());
// Figure out how many bands to scale
int numBands = scale.length;
if (scale.length == 1)
numBands = src.getColorModel().getNumColorComponents();
boolean[] bands = new boolean[numBands];
// this assumes the alpha, if present, is the last band
Arrays.fill(bands, true);
// Perform rescaling
filter(src.getRaster(), dst2.getRaster(), bands);
// Copy alpha band if needed (ie if it exists and wasn't scaled)
// NOTE: This assumes the alpha component is the last band!
if (src.getColorModel().hasAlpha()
&& numBands == src.getColorModel().getNumColorComponents())
{
// Construct a raster that doesn't include an alpha band.
int[] subbands = new int[scm.getNumColorComponents()];
for (int i=0; i < subbands.length; i++) subbands[i] = i;
wsrc =
wsrc.createWritableChild(wsrc.minX, wsrc.minY, wsrc.width, wsrc.height,
wsrc.minX, wsrc.minY, subbands);
}
// else all color bands
filter(wsrc, wdst);
dst2.getRaster().setSamples(0, 0, src.getWidth(), src.getHeight(),
numBands,
src.getRaster().getSamples(0, 0,
src.getWidth(),
src.getHeight(),
numBands,
(int[]) null));
}
// Perform colorspace conversion if needed
if (dst != dst2)
new ColorConvertOp(hints).filter(dst2, dst);
return dst;
}
@@ -136,50 +243,106 @@ public class RescaleOp implements BufferedImageOp, RasterOp
*/
public final WritableRaster filter(Raster src, WritableRaster dest)
{
if (dest == null) dest = src.createCompatibleWritableRaster();
// Required sanity checks
if (src.numBands != dest.numBands || scale.length != offsets.length)
throw new IllegalArgumentException();
if (scale.length != 1 && scale.length != src.numBands)
throw new IllegalArgumentException();
throw new IllegalArgumentException("Number of rasters is incompatible "
+ "with the number of scaling "
+ "factors provided.");
// Create scaling arrays if needed
float[] lscale = scale;
float[] loff = offsets;
if (scale.length == 1)
{
lscale = new float[src.numBands];
Arrays.fill(lscale, scale[0]);
loff = new float[src.numBands];
Arrays.fill(loff, offsets[0]);
}
if (dest == null)
dest = src.createCompatibleWritableRaster();
else if (src.getHeight() != dest.getHeight()
|| src.getWidth() != dest.getWidth())
throw new IllegalArgumentException("Source and destination rasters are "
+ "different sizes.");
else if (src.numBands != dest.numBands)
throw new IllegalArgumentException("Source and destination rasters "
+ "are incompatible.");
// TODO The efficiency here can be improved for various data storage
// patterns, aka SampleModels.
float[] pixel = new float[src.numBands];
for (int y = src.minY; y < src.height + src.minY; y++)
for (int x = src.minX; x < src.width + src.minX; x++)
{
src.getPixel(x, y, pixel);
for (int b = 0; b < src.numBands; b++)
pixel[b] = pixel[b] * lscale[b] + loff[b];
dest.setPixel(x, y, pixel);
}
// Filter all bands
boolean[] bands = new boolean[src.getNumBands()];
Arrays.fill(bands, true);
return filter(src, dest, bands);
}
/**
* Perform raster-based filtering on a selected number of bands.
*
* The length of the bands array should equal the number of bands; a true
* element indicates filtering should happen on the corresponding band, while
* a false element will skip the band.
*
* The rasters are assumed to be compatible and non-null.
*
* @param src the source raster.
* @param dest the destination raster.
* @param bands an array indicating which bands to filter.
* @throws NullPointerException if any parameter is null.
* @throws ArrayIndexOutOfBoundsException if the bands array is too small.
* @return the destination raster.
*/
private WritableRaster filter(Raster src, WritableRaster dest, boolean[] bands)
{
int[] values = new int[src.getHeight() * src.getWidth()];
float scaleFactor, offset;
// Find max sample value, to be used for clipping later
int[] maxValue = src.getSampleModel().getSampleSize();
for (int i = 0; i < maxValue.length; i++)
maxValue[i] = (int)Math.pow(2, maxValue[i]) - 1;
// TODO: can this be optimized further?
// Filter all samples of all requested bands
for (int band = 0; band < bands.length; band++)
if (bands[band])
{
values = src.getSamples(src.getMinX(), src.getMinY(), src.getWidth(),
src.getHeight(), band, values);
if (scale.length == 1)
{
scaleFactor = scale[0];
offset = offsets[0];
}
else
{
scaleFactor = scale[band];
offset = offsets[band];
}
for (int i = 0; i < values.length; i++)
{
values[i] = (int) (values[i] * scaleFactor + offset);
// Clip if needed
if (values[i] < 0)
values[i] = 0;
if (values[i] > maxValue[band])
values[i] = maxValue[band];
}
dest.setSamples(dest.getMinX(), dest.getMinY(), dest.getWidth(),
dest.getHeight(), band, values);
}
return dest;
}
/* (non-Javadoc)
* @see java.awt.image.BufferedImageOp#createCompatibleDestImage(java.awt.image.BufferedImage, java.awt.image.ColorModel)
/*
* (non-Javadoc)
*
* @see java.awt.image.BufferedImageOp#createCompatibleDestImage(java.awt.image.BufferedImage,
* java.awt.image.ColorModel)
*/
public BufferedImage createCompatibleDestImage(BufferedImage src,
ColorModel dstCM)
{
if (dstCM == null) dstCM = src.getColorModel();
WritableRaster wr = src.getRaster().createCompatibleWritableRaster();
BufferedImage image
= new BufferedImage(dstCM, wr, src.isPremultiplied, null);
return image;
if (dstCM == null)
return new BufferedImage(src.getWidth(), src.getHeight(), src.getType());
return new BufferedImage(dstCM,
src.getRaster().createCompatibleWritableRaster(),
src.isAlphaPremultiplied(), null);
}
/* (non-Javadoc)
@@ -209,9 +372,13 @@ public class RescaleOp implements BufferedImageOp, RasterOp
/* (non-Javadoc)
* @see java.awt.image.BufferedImageOp#getPoint2D(java.awt.geom.Point2D, java.awt.geom.Point2D)
*/
public final Point2D getPoint2D(Point2D src, Point2D dst) {
if (dst == null) dst = (Point2D) src.clone();
else dst.setLocation(src);
public final Point2D getPoint2D(Point2D src, Point2D dst)
{
if (dst == null)
dst = (Point2D) src.clone();
else
dst.setLocation(src);
return dst;
}
@@ -246,9 +246,7 @@ public abstract class SampleModel
public void setDataElements(int x, int y, int w, int h,
Object obj, DataBuffer data)
{
int size = w * h;
int numDataElements = getNumDataElements();
int dataSize = numDataElements * size;
Object pixelData;
switch (getTransferType())
@@ -257,25 +255,34 @@ public abstract class SampleModel
pixelData = new byte[numDataElements];
break;
case DataBuffer.TYPE_USHORT:
case DataBuffer.TYPE_SHORT:
pixelData = new short[numDataElements];
break;
case DataBuffer.TYPE_INT:
pixelData = new int[numDataElements];
break;
case DataBuffer.TYPE_FLOAT:
pixelData = new float[numDataElements];
break;
case DataBuffer.TYPE_DOUBLE:
pixelData = new double[numDataElements];
break;
default:
// Seems like the only sensible thing to do.
throw new ClassCastException();
// The RI silently igores invalid types.
pixelData = null;
}
int inOffset = 0;
for (int yy = y; yy < (y + h); yy++)
int inOffset = 0;
if (pixelData != null)
{
for (int xx = x; xx < (x + w); xx++)
for (int yy=y; yy<(y+h); yy++)
{
System.arraycopy(obj, inOffset, pixelData, 0,
numDataElements);
setDataElements(xx, yy, pixelData, data);
inOffset += numDataElements;
for (int xx=x; xx<(x+w); xx++)
{
System.arraycopy(obj, inOffset, pixelData, 0, numDataElements);
setDataElements(xx, yy, pixelData, data);
inOffset += numDataElements;
}
}
}
}
@@ -412,110 +412,31 @@ public class SinglePixelPackedSampleModel extends SampleModel
return (samples & bitMasks[b]) >>> bitOffsets[b];
}
/**
* This method implements a more efficient way to set data elements than the
* default implementation of the super class. It sets the data elements line
* by line instead of pixel by pixel.
*
* @param x The x-coordinate of the data elements in <code>obj</code>.
* @param y The y-coordinate of the data elements in <code>obj</code>.
* @param w The width of the data elements in <code>obj</code>.
* @param h The height of the data elements in <code>obj</code>.
* @param obj The primitive array containing the data elements to set.
* @param data The DataBuffer to store the data elements into.
* @see java.awt.image.SampleModel#setDataElements(int, int, int, int,
* java.lang.Object, java.awt.image.DataBuffer)
*/
public void setDataElements(int x, int y, int w, int h,
Object obj, DataBuffer data)
{
Object pixelData;
switch (getTransferType())
{
case DataBuffer.TYPE_BYTE:
pixelData = ((DataBufferByte) data).getData();
break;
case DataBuffer.TYPE_USHORT:
pixelData = ((DataBufferUShort) data).getData();
break;
case DataBuffer.TYPE_INT:
pixelData = ((DataBufferInt) data).getData();
break;
default:
// Seems like the only sensible thing to do.
throw new ClassCastException();
}
int inOffset = 0;
int dataOffset = scanlineStride*y + x + data.getOffset();
for (int yy=y; yy<(y+h); yy++)
{
System.arraycopy(obj,inOffset,pixelData,dataOffset,w);
dataOffset += scanlineStride;
inOffset += w;
}
}
public void setDataElements(int x, int y, Object obj, DataBuffer data)
{
int offset = scanlineStride*y + x + data.getOffset();
int transferType = getTransferType();
if (getTransferType() != data.getDataType())
{
throw new IllegalArgumentException("transfer type ("+
getTransferType()+"), "+
"does not match data "+
"buffer type (" +
data.getDataType() +
").");
}
try
{
switch (transferType)
{
case DataBuffer.TYPE_BYTE:
{
DataBufferByte out = (DataBufferByte) data;
byte[] in = (byte[]) obj;
out.getData()[offset] = in[0];
return;
}
case DataBuffer.TYPE_USHORT:
{
DataBufferUShort out = (DataBufferUShort) data;
short[] in = (short[]) obj;
out.getData()[offset] = in[0];
return;
}
case DataBuffer.TYPE_INT:
{
DataBufferInt out = (DataBufferInt) data;
int[] in = (int[]) obj;
out.getData()[offset] = in[0];
return;
}
// FIXME: Fill in the other possible types.
default:
throw new InternalError();
}
}
catch (ArrayIndexOutOfBoundsException aioobe)
{
String msg = "While writing data elements" +
", x="+x+", y="+y+
", width="+width+", height="+height+
", scanlineStride="+scanlineStride+
", offset="+offset+
", data.getSize()="+data.getSize()+
", data.getOffset()="+data.getOffset()+
": " +
aioobe;
throw new ArrayIndexOutOfBoundsException(msg);
}
switch (transferType)
{
case DataBuffer.TYPE_BYTE:
{
byte[] in = (byte[]) obj;
data.setElem(y * scanlineStride + x, ((int) in[0]) & 0xff);
break;
}
case DataBuffer.TYPE_USHORT:
{
short[] in = (short[]) obj;
data.setElem(y * scanlineStride + x, ((int) in[0]) & 0xffff);
break;
}
case DataBuffer.TYPE_INT:
{
int[] in = (int[]) obj;
data.setElem(y * scanlineStride + x, in[0]);
break;
}
}
}
/**
@@ -136,8 +136,9 @@ public class WritableRaster extends Raster
{
// This mirrors the code from the super class
// FIXME: Throw RasterFormatException if child bounds extends
// beyond the bounds of this raster.
if (parentX < minX || parentX + w > minX + width
|| parentY < minY || parentY + h > minY + height)
throw new RasterFormatException("Child raster extends beyond parent");
SampleModel sm = (bandList == null) ?
sampleModel :
@@ -149,6 +150,25 @@ public class WritableRaster extends Raster
sampleModelTranslateY + childMinY - parentY),
this);
}
public Raster createChild(int parentX, int parentY, int width,
int height, int childMinX, int childMinY,
int[] bandList)
{
if (parentX < minX || parentX + width > minX + this.width
|| parentY < minY || parentY + height > minY + this.height)
throw new RasterFormatException("Child raster extends beyond parent");
SampleModel sm = (bandList == null) ?
sampleModel :
sampleModel.createSubsetSampleModel(bandList);
return new WritableRaster(sm, dataBuffer,
new Rectangle(childMinX, childMinY, width, height),
new Point(sampleModelTranslateX + childMinX - parentX,
sampleModelTranslateY + childMinY - parentY),
this);
}
public void setDataElements(int x, int y, Object inData)
{
@@ -45,20 +45,20 @@ import java.util.Vector;
public class ParameterBlock implements Cloneable, Serializable
{
private static final long serialVersionUID = -7577115551785240750L;
protected Vector sources;
protected Vector parameters;
protected Vector<Object> sources;
protected Vector<Object> parameters;
public ParameterBlock()
{
this(new Vector(), new Vector());
this(new Vector<Object>(), new Vector<Object>());
}
public ParameterBlock(Vector sources)
public ParameterBlock(Vector<Object> sources)
{
this(sources, new Vector());
this(sources, new Vector<Object>());
}
public ParameterBlock(Vector sources, Vector parameters)
public ParameterBlock(Vector<Object> sources, Vector<Object> parameters)
{
this.sources = sources;
this.parameters = parameters;
@@ -80,9 +80,9 @@ public class ParameterBlock implements Cloneable, Serializable
{
ParameterBlock pb = (ParameterBlock) shallowClone();
if (sources != null)
pb.sources = (Vector) sources.clone();
pb.sources = (Vector<Object>) sources.clone();
if (parameters != null)
pb.parameters = (Vector) parameters.clone();
pb.parameters = (Vector<Object>) parameters.clone();
return pb;
}
@@ -119,12 +119,12 @@ public class ParameterBlock implements Cloneable, Serializable
return sources.size();
}
public Vector getSources()
public Vector<Object> getSources()
{
return sources;
}
public void setSources(Vector sources)
public void setSources(Vector<Object> sources)
{
this.sources = sources;
}
@@ -140,12 +140,12 @@ public class ParameterBlock implements Cloneable, Serializable
return parameters.size();
}
public Vector getParameters()
public Vector<Object> getParameters()
{
return parameters;
}
public void setParameters(Vector parameters)
public void setParameters(Vector<Object> parameters)
{
this.parameters = parameters;
}
@@ -46,7 +46,7 @@ public interface RenderableImage
{
String HINTS_OBSERVED = "HINTS_OBSERVED";
Vector getSources();
Vector<RenderableImage> getSources();
Object getProperty(String name);
String[] getPropertyNames();
boolean isDynamic();
@@ -55,7 +55,7 @@ public class RenderableImageOp implements RenderableImage
this.block = (ParameterBlock) block.clone();
}
public Vector getSources()
public Vector<RenderableImage> getSources()
{
if (block.sources == null)
return null;