- /*
- * @(#)AffineTransformOp.java 1.55 00/02/02
- *
- * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
- *
- * This software is the proprietary information of Sun Microsystems, Inc.
- * Use is subject to license terms.
- *
- */
-
- package java.awt.image;
-
- import java.awt.geom.AffineTransform;
- import java.awt.geom.Rectangle2D;
- import java.awt.geom.Point2D;
- import java.awt.GraphicsEnvironment;
- import java.awt.Rectangle;
- import java.awt.RenderingHints;
- import java.awt.Transparency;
- import sun.awt.image.ImagingLib;
-
- /**
- * This class uses an affine transform to perform a linear mapping from
- * 2D coordinates in the source image or <CODE>Raster</CODE> to 2D coordinates
- * in the destination image or <CODE>Raster</CODE>.
- * The type of interpolation that is used is specified through a constructor,
- * either by a <CODE>RenderingHints</CODE> object or by one of the integer
- * interpolation types defined in this class.
- * <p>
- * If a <CODE>RenderingHints</CODE> object is specified in the constructor, the
- * interpolation hint and the rendering quality hint are used to set
- * the interpolation type for this operation. The color rendering hint
- * and the dithering hint can be used when color conversion is required.
- * <p>
- * Note that the following constraints have to be met:
- * <ul>
- * <li>The source and destination must be different.
- * <li>For <CODE>Raster</CODE> objects, the number of bands in the source must
- * be equal to the number of bands in the destination.
- * </ul>
- * @see AffineTransform
- * @see BufferedImageFilter
- * @see java.awt.RenderingHints#KEY_INTERPOLATION
- * @see java.awt.RenderingHints#KEY_RENDERING
- * @see java.awt.RenderingHints#KEY_COLOR_RENDERING
- * @see java.awt.RenderingHints#KEY_DITHERING
- * @version 16 Apr 1998
- */
- public class AffineTransformOp implements BufferedImageOp, RasterOp {
- private AffineTransform xform;
- RenderingHints hints;
-
- /**
- * Nearest-neighbor interpolation type.
- */
- public static final int TYPE_NEAREST_NEIGHBOR = 1;
- /**
- * Bilinear interpolation type.
- */
- public static final int TYPE_BILINEAR = 2;
- /**
- * Bicubic interpolation type.
- */
- private static final int TYPE_BICUBIC = 3;
-
- int interpolationType = TYPE_NEAREST_NEIGHBOR;
-
- /**
- * Constructs an <CODE>AffineTransformOp</CODE> given an affine transform.
- * The interpolation type is determined from the
- * <CODE>RenderingHints</CODE> object. If the interpolation hint is
- * defined, it will be used. Otherwise, if the rendering quality hint is
- * defined, the interpolation type is determined from its value. If no
- * hints are specified (<CODE>hints</CODE> is null),
- * the interpolation type is {@link #TYPE_NEAREST_NEIGHBOR
- * TYPE_NEAREST_NEIGHBOR}.
- *
- * @param xform The <CODE>AffineTransform</CODE> to use for the operation.
- *
- * @param hints The <CODE>RenderingHints</CODE> object used to specify the
- * interpolation type for the operation.
- *
- * @see java.awt.RenderingHints#KEY_INTERPOLATION
- * @see java.awt.RenderingHints#KEY_RENDERING
- */
- public AffineTransformOp(AffineTransform xform, RenderingHints hints) {
- this.xform = (AffineTransform) xform.clone();
- this.hints = hints;
-
- if (hints != null) {
- Object value = hints.get(hints.KEY_INTERPOLATION);
- if (value == null) {
- value = hints.get(hints.KEY_RENDERING);
- if (value == hints.VALUE_RENDER_SPEED) {
- interpolationType = TYPE_NEAREST_NEIGHBOR;
- }
- else if (value == hints.VALUE_RENDER_QUALITY) {
- interpolationType = TYPE_BILINEAR;
- }
- }
- else if (value == hints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR) {
- interpolationType = TYPE_NEAREST_NEIGHBOR;
- }
- else if (value == hints.VALUE_INTERPOLATION_BILINEAR) {
- interpolationType = TYPE_BILINEAR;
- }
- else if (value == hints.VALUE_INTERPOLATION_BICUBIC) {
- interpolationType = TYPE_BICUBIC;
- }
- }
- else {
- interpolationType = TYPE_NEAREST_NEIGHBOR;
- }
- }
-
- /**
- * Constructs an <CODE>AffineTransformOp</CODE> given an affine transform
- * and the interpolation type.
- *
- * @param xform The <CODE>AffineTransform</CODE> to use for the operation.
- * @param interpolationType One of the integer
- * interpolation type constants defined by this class:
- * {@link #TYPE_NEAREST_NEIGHBOR TYPE_NEAREST_NEIGHBOR},
- * {@link #TYPE_BILINEAR TYPE_BILINEAR}.
- */
- public AffineTransformOp(AffineTransform xform, int interpolationType) {
- this.xform = (AffineTransform)xform.clone();
- switch(interpolationType) {
- case TYPE_NEAREST_NEIGHBOR:
- case TYPE_BILINEAR:
- case TYPE_BICUBIC:
- break;
- default:
- throw new IllegalArgumentException("Unknown interpolation type: "+
- interpolationType);
- }
- this.interpolationType = interpolationType;
- }
-
- /**
- * Returns the interpolation type used by this op.
- * @see #TYPE_NEAREST_NEIGHBOR
- * @see #TYPE_BILINEAR
- */
- public final int getInterpolationType() {
- return interpolationType;
- }
-
- /**
- * Transforms the source <CODE>BufferedImage</CODE> and stores the results
- * in the destination <CODE>BufferedImage</CODE>.
- * If the color models for the two images do not match, a color
- * conversion into the destination color model is performed.
- * If the destination image is null,
- * a <CODE>BufferedImage</CODE> is created with the source
- * <CODE>ColorModel</CODE>.
- * <p>
- * The coordinates of the rectangle returned by
- * <code>getBounds2D(BufferedImage)</code>
- * are not necessarily the same as the coordinates of the
- * <code>BufferedImage</code> returned by this method. If the
- * upper-left corner coordinates of the rectangle are
- * negative then this part of the rectangle is not drawn. If the
- * upper-left corner coordinates of the rectangle are positive
- * then the filtered image is drawn at that position in the
- * destination <code>BufferedImage</code>.
- * <p>
- * An <CODE>IllegalArgumentException</CODE> is thrown if the source is
- * the same as the destination.
- *
- * @param src The <CODE>BufferedImage</CODE> to transform.
- * @param dst The <CODE>BufferedImage</CODE> in which to store the results
- * of the transformation.
- *
- * @return The filtered <CODE>BufferedImage</CODE>.
- * @throws IllegalArgumentException if <code>src</code> and
- * <code>dst</code> are the same
- * @throws ImagingOpException if the image cannot be transformed
- * because of a data-processing error that might be
- * caused by an invalid image format, tile format, or
- * image-processing operation, or any other unsupported
- * operation.
- */
- public final BufferedImage filter(BufferedImage src, BufferedImage dst) {
-
- if (src == null) {
- throw new NullPointerException("src image is null");
- }
- if (src == dst) {
- throw new IllegalArgumentException("src image cannot be the "+
- "same as the dst image");
- }
-
- boolean needToConvert = false;
- ColorModel srcCM = src.getColorModel();
- ColorModel dstCM;
- BufferedImage origDst = dst;
-
- if (dst == null) {
- dst = createCompatibleDestImage(src, null);
- dstCM = srcCM;
- origDst = dst;
- }
- else {
- dstCM = dst.getColorModel();
- if (srcCM.getColorSpace().getType() !=
- dstCM.getColorSpace().getType())
- {
- int type = xform.getType();
- boolean needTrans = ((type&
- (xform.TYPE_MASK_ROTATION|
- xform.TYPE_GENERAL_TRANSFORM))
- != 0);
- if (! needTrans && type != xform.TYPE_TRANSLATION && type != xform.TYPE_IDENTITY)
- {
- double[] mtx = new double[4];
- xform.getMatrix(mtx);
- // Check out the matrix. A non-integral scale will force ARGB
- // since the edge conditions can't be guaranteed.
- needTrans = (mtx[0] != (int)mtx[0] || mtx[3] != (int)mtx[3]);
- }
-
- if (needTrans &&
- srcCM.getTransparency() == Transparency.OPAQUE)
- {
- // Need to convert first
- ColorConvertOp ccop = new ColorConvertOp(hints);
- BufferedImage tmpSrc = null;
- int sw = src.getWidth();
- int sh = src.getHeight();
- if (dstCM.getTransparency() == Transparency.OPAQUE) {
- tmpSrc = new BufferedImage(sw, sh,
- BufferedImage.TYPE_INT_ARGB);
- }
- else {
- WritableRaster r =
- dstCM.createCompatibleWritableRaster(sw, sh);
- tmpSrc = new BufferedImage(dstCM, r,
- dstCM.isAlphaPremultiplied(),
- null);
- }
- src = ccop.filter(src, tmpSrc);
- }
- else {
- needToConvert = true;
- dst = createCompatibleDestImage(src, null);
- }
- }
-
- }
-
- if (interpolationType != TYPE_NEAREST_NEIGHBOR &&
- dst.getColorModel() instanceof IndexColorModel) {
- dst = new BufferedImage(dst.getWidth(), dst.getHeight(),
- BufferedImage.TYPE_INT_ARGB);
- }
- if (ImagingLib.filter(this, src, dst) == null) {
- throw new ImagingOpException ("Unable to transform src image");
- }
-
- if (needToConvert) {
- ColorConvertOp ccop = new ColorConvertOp(hints);
- ccop.filter(dst, origDst);
- }
- else if (origDst != dst) {
- java.awt.Graphics2D g = origDst.createGraphics();
- try {
- g.drawImage(dst, 0, 0, null);
- } finally {
- g.dispose();
- }
- }
-
- return origDst;
- }
-
- /**
- * Transforms the source <CODE>Raster</CODE> and stores the results in
- * the destination <CODE>Raster</CODE>. This operation performs the
- * transform band by band.
- * <p>
- * If the destination <CODE>Raster</CODE> is null, a new
- * <CODE>Raster</CODE> is created.
- * An <CODE>IllegalArgumentException</CODE> may be thrown if the source is
- * the same as the destination or if the number of bands in
- * the source is not equal to the number of bands in the
- * destination.
- * <p>
- * The coordinates of the rectangle returned by
- * <code>getBounds2D(Raster)</code>
- * are not necessarily the same as the coordinates of the
- * <code>WritableRaster</code> returned by this method. If the
- * upper-left corner coordinates of rectangle are negative then
- * this part of the rectangle is not drawn. If the coordinates
- * of the rectangle are positive then the filtered image is drawn at
- * that position in the destination <code>Raster</code>.
- * <p>
- * @param src The <CODE>Raster</CODE> to transform.
- * @param dst The <CODE>Raster</CODE> in which to store the results of the
- * transformation.
- *
- * @return The transformed <CODE>Raster</CODE>.
- *
- * @throws ImagingOpException if the raster cannot be transformed
- * because of a data-processing error that might be
- * caused by an invalid image format, tile format, or
- * image-processing operation, or any other unsupported
- * operation.
- */
- public final WritableRaster filter(Raster src, WritableRaster dst) {
- if (src == null) {
- throw new NullPointerException("src image is null");
- }
- if (dst == null) {
- dst = createCompatibleDestRaster(src);
- }
- if (src == dst) {
- throw new IllegalArgumentException("src image cannot be the "+
- "same as the dst image");
- }
- if (src.getNumBands() != dst.getNumBands()) {
- throw new IllegalArgumentException("Number of src bands ("+
- src.getNumBands()+
- ") does not match number of "+
- " dst bands ("+
- dst.getNumBands()+")");
- }
-
- if (ImagingLib.filter(this, src, dst) == null) {
- throw new ImagingOpException ("Unable to transform src image");
- }
- return dst;
- }
-
- /**
- * Returns the bounding box of the transformed destination. The
- * rectangle returned is the actual bounding box of the
- * transformed points. The coordinates of the upper-left corner
- * of the returned rectangle might not be (0, 0).
- *
- * @param src The <CODE>BufferedImage</CODE> to be transformed.
- *
- * @return The <CODE>Rectangle2D</CODE> representing the destination's
- * bounding box.
- */
- public final Rectangle2D getBounds2D (BufferedImage src) {
- return getBounds2D(src.getRaster());
- }
-
- /**
- * Returns the bounding box of the transformed destination. The
- * rectangle returned will be the actual bounding box of the
- * transformed points. The coordinates of the upper-left corner
- * of the returned rectangle might not be (0, 0).
- *
- * @param src The <CODE>Raster</CODE> to be transformed.
- *
- * @return The <CODE>Rectangle2D</CODE> representing the destination's
- * bounding box.
- */
- public final Rectangle2D getBounds2D (Raster src) {
- int w = src.getWidth();
- int h = src.getHeight();
-
- // Get the bounding box of the src and transform the corners
- float[] pts = {0, 0, w, 0, w, h, 0, h};
- xform.transform(pts, 0, pts, 0, 4);
-
- // Get the min, max of the dst
- float fmaxX = pts[0];
- float fmaxY = pts[1];
- float fminX = pts[0];
- float fminY = pts[1];
- int maxX;
- int maxY;
- for (int i=2; i < 8; i+=2) {
- if (pts[i] > fmaxX) {
- fmaxX = pts[i];
- }
- else if (pts[i] < fminX) {
- fminX = pts[i];
- }
- if (pts[i+1] > fmaxY) {
- fmaxY = pts[i+1];
- }
- else if (pts[i+1] < fminY) {
- fminY = pts[i+1];
- }
- }
-
- return new Rectangle2D.Float(fminX, fminY, fmaxX-fminX, fmaxY-fminY);
- }
-
- /**
- * Creates a zeroed destination image with the correct size and number of
- * bands. A <CODE>RasterFormatException</CODE> may be thrown if the
- * transformed width or height is equal to 0.
- * <p>
- * If <CODE>destCM</CODE> is null,
- * an appropriate <CODE>ColorModel</CODE> is used; this
- * <CODE>ColorModel</CODE> may have
- * an alpha channel even if the source <CODE>ColorModel</CODE> is opaque.
- *
- * @param src The <CODE>BufferedImage</CODE> to be transformed.
- * @param destCM <CODE>ColorModel</CODE> of the destination. If null,
- * an appropriate <CODE>ColorModel</CODE> is used.
- *
- * @return The zeroed destination image.
- */
- public BufferedImage createCompatibleDestImage (BufferedImage src,
- ColorModel destCM) {
- BufferedImage image;
- Rectangle r = getBounds2D(src).getBounds();
-
- // If r.x (or r.y) is < 0, then we want to only create an image
- // that is in the positive range.
- // If r.x (or r.y) is > 0, then we need to create an image that
- // includes the translation.
- int w = r.x + r.width;
- int h = r.y + r.height;
- if (w <= 0) {
- throw new RasterFormatException("Transformed width ("+w+
- ") is less than or equal to 0.");
- }
- if (h <= 0) {
- throw new RasterFormatException("Transformed height ("+h+
- ") is less than or equal to 0.");
- }
-
- if (destCM == null) {
- ColorModel cm = src.getColorModel();
- if (interpolationType != TYPE_NEAREST_NEIGHBOR &&
- (cm instanceof IndexColorModel ||
- cm.getTransparency() == Transparency.OPAQUE))
- {
- image = new BufferedImage(w, h,
- BufferedImage.TYPE_INT_ARGB);
- }
- else {
- image = new BufferedImage(cm,
- src.getRaster().createCompatibleWritableRaster(w,h),
- cm.isAlphaPremultiplied(), null);
- }
- }
- else {
- image = new BufferedImage(destCM,
- destCM.createCompatibleWritableRaster(w,h),
- destCM.isAlphaPremultiplied(), null);
- }
-
- return image;
- }
-
- /**
- * Creates a zeroed destination <CODE>Raster</CODE> with the correct size
- * and number of bands. A <CODE>RasterFormatException</CODE> may be thrown
- * if the transformed width or height is equal to 0.
- *
- * @param src The <CODE>Raster</CODE> to be transformed.
- *
- * @return The zeroed destination <CODE>Raster</CODE>.
- */
- public WritableRaster createCompatibleDestRaster (Raster src) {
- Rectangle2D r = getBounds2D(src);
-
- return src.createCompatibleWritableRaster((int)r.getX(),
- (int)r.getY(),
- (int)r.getWidth(),
- (int)r.getHeight());
- }
-
- /**
- * Returns the location of the corresponding destination point given a
- * point in the source. If <CODE>dstPt</CODE> is specified, it
- * is used to hold the return value.
- *
- * @param dstPt The <CODE>Point2D</CODE> in which to store the result.
- *
- * @return The <CODE>Point2D</CODE> in the destination that corresponds to
- * the specified point in the source.
- */
- public final Point2D getPoint2D (Point2D srcPt, Point2D dstPt) {
- return xform.transform (srcPt, dstPt);
- }
-
- /**
- * Returns the affine transform used by this transform operation.
- *
- * @return The <CODE>AffineTransform</CODE> associated with this op.
- */
- public final AffineTransform getTransform() {
- return (AffineTransform) xform.clone();
- }
-
- /**
- * Returns the rendering hints used by this transform operation.
- *
- * @return The <CODE>RenderingHints</CODE> object associated with this op.
- */
- public final RenderingHints getRenderingHints() {
- if (hints == null) {
- Object val;
- switch(interpolationType) {
- case TYPE_NEAREST_NEIGHBOR:
- val = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
- break;
- case TYPE_BILINEAR:
- val = RenderingHints.VALUE_INTERPOLATION_BILINEAR;
- break;
- case TYPE_BICUBIC:
- val = RenderingHints.VALUE_INTERPOLATION_BICUBIC;
- break;
- default:
- // Should never get here
- throw new InternalError("Unknown interpolation type "+
- interpolationType);
-
- }
- hints = new RenderingHints(RenderingHints.KEY_INTERPOLATION, val);
- }
-
- return hints;
- }
- }