summaryrefslogtreecommitdiff
path: root/java/awt/image/AffineTransformOp.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/awt/image/AffineTransformOp.java')
-rw-r--r--java/awt/image/AffineTransformOp.java314
1 files changed, 198 insertions, 116 deletions
diff --git a/java/awt/image/AffineTransformOp.java b/java/awt/image/AffineTransformOp.java
index 7820684d2..849c5b050 100644
--- a/java/awt/image/AffineTransformOp.java
+++ b/java/awt/image/AffineTransformOp.java
@@ -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,8 +49,11 @@ 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)
@@ -115,14 +119,14 @@ 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, an appropriate ColorModel is used.
+ * 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)
@@ -145,21 +149,18 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp
}
/**
- * 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)
{
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(),
@@ -175,24 +176,22 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp
* @param dst destination image
* @throws IllegalArgumentException if the source and destination image are
* the same
- * @return transformed source image
+ * @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");
+ 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 the destination image is null, then use a compatible BufferedImage
if (dst == null)
dst = createCompatibleDestImage(src, null);
- 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;
-
}
/**
@@ -204,21 +203,40 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp
* @param dst destination raster
* @throws IllegalArgumentException if the source and destination are not
* compatible
- * @return transformed raster
+ * @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");
+ // 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()))
+ {
+ 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();
+ }
+
+ // 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];
@@ -287,7 +305,7 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp
}
/**
- * Returns interpolation type used during transformations
+ * Returns interpolation type used during transformations.
*
* @return interpolation type
*/
@@ -319,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 ()
{
@@ -330,7 +348,7 @@ 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 ()
{
@@ -377,6 +395,18 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp
{
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)
@@ -400,22 +430,65 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp
int y = (int) Math.floor(pts[i + 1] + src.getMinY());
double xdiff = pts[i] + src.getMinX() - x;
double ydiff = pts[i + 1] + src.getMinY() - y;
-
- // Run the interpolation for each band
+
+ // 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++)
{
- double result = (src.getSampleDouble(x, y, j) * (1 - xdiff)
- + src.getSampleDouble(x + 1, y, j) * xdiff)
- * (1 - ydiff)
- + (src.getSampleDouble(x, y + 1, j)
- * (1 - xdiff)
- + src.getSampleDouble(x + 1, y + 1, j)
- * xdiff)
- * ydiff;
- dst.setSample((int) dpts[i] + dst.getMinX(),
- (int) dpts[i + 1] + dst.getMinY(),
- j, result);
+ // 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);
}
}
}
@@ -434,10 +507,11 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp
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
- double[] result = new double[src.getNumBands()];
for (int i = 0; i < dpts.length; i += 2)
{
if (srcbounds.contains((int) Math.round(pts[i]) + src.getMinX(),
@@ -450,76 +524,84 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp
Arrays.fill(result, 0);
for (int m = - 1; m < 3; m++)
- {
- for (int n = - 1; n < 3; n++)
- {
- // R(x) = ( P(x+2)^3 - 4 P(x+1)^3 + 6 P(x)^3 - 4 P(x-1)^3 ) / 6
- double r1 = 0;
- double r2 = 0;
-
- // Calculate R(m - dx)
- double rx = m - dx + 2;
- if (rx > 0)
- r1 += rx * rx * rx;
-
- rx = m - dx + 1;
- if (rx > 0)
- r1 -= 4 * rx * rx * rx;
-
- rx = m - dx;
- if (rx > 0)
- r1 += 6 * rx * rx * rx;
-
- rx = m - dx - 1;
- if (rx > 0)
- r1 -= 4 * rx * rx * rx;
-
- r1 /= 6;
-
- // Calculate R(dy - n);
- rx = dy - n + 2;
- if (rx > 0)
- r2 += rx * rx * rx;
-
- rx = dy - n + 1;
- if (rx > 0)
- r2 -= 4 * rx * rx * rx;
-
- rx = dy - n;
- if (rx > 0)
- r2 += 6 * rx * rx * rx;
-
- rx = dy - n - 1;
- if (rx > 0)
- r2 -= 4 * rx * rx * rx;
-
- r2 /= 6;
-
- // Calculate F(i+m, j+n) R(m - dx) R(dy - n)
- // Check corner cases
- int srcX = x + m;
- if (srcX >= src.getMinX() + src.getWidth())
- srcX = src.getMinX() + src.getWidth() - 1;
- else if (srcX < src.getMinX())
- srcX = src.getMinX();
-
- int srcY = y + n;
- if (srcY >= src.getMinY() + src.getHeight())
- srcY = src.getMinY() + src.getHeight() - 1;
- else if (srcY < src.getMinY())
- srcY = src.getMinY();
-
- // Calculate once for each band
- for (int j = 0; j < result.length; j++)
- result[j] += src.getSample(srcX, srcY, j) * r1 * r2;
- }
- }
+ 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
- for (int j = 0; j < result.length; j++)
- dst.setSample((int) dpts[i] + dst.getMinX(),
- (int) dpts[i + 1] + dst.getMinY(),
- j, result[j]);
+ dst.setPixel((int)dpts[i] + dst.getMinX(),
+ (int)dpts[i+1] + dst.getMinY(),
+ result);
}
}
}