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);
}
}
}
}