/*******************************************************************************
* Copyright (c) 2011, 2016 itemis AG and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Alexander Nyßen (itemis AG) - initial API and implementation
* Matthias Wienand (itemis AG) - javadoc comment enhancements
*
*******************************************************************************/
package org.eclipse.gef.geometry.planar;
import java.awt.geom.NoninvertibleTransformException;
import org.eclipse.gef.geometry.convert.awt.AWT2Geometry;
import org.eclipse.gef.geometry.convert.awt.Geometry2AWT;
import org.eclipse.gef.geometry.euclidean.Angle;
import org.eclipse.gef.geometry.euclidean.Vector;
/**
* <p>
* The {@link AffineTransform} class provides methods to create and modify
* 2-dimensional affine transformations.
* </p>
* <p>
* It delegates to the {@link java.awt.geom.AffineTransform} functionality.
* </p>
*
* @author anyssen
* @author mwienand
*
*/
public class AffineTransform implements Cloneable {
// TODO: implement affine transform locally to get rid of dependency on
// awt.geom.
private java.awt.geom.AffineTransform delegate = new java.awt.geom.AffineTransform();
/**
* Creates a new {@link AffineTransform} with its transformation matrix set
* to the identity matrix.
*/
public AffineTransform() {
}
/**
* Creates a new {@link AffineTransform} with its transformation matrix set
* to the specified values. Note that rotation is a combination of shearing
* and scaling.
*
* @param m00
* the value of the transformation matrix in row 0 and column 0
* (x coordinate scaling)
* @param m10
* the value of the transformation matrix in row 1 and column 0
* (y coordinate shearing)
* @param m01
* the value of the transformation matrix in row 0 and column 1
* (x coordinate shearing)
* @param m11
* the value of the transformation matrix in row 1 and column 1
* (y coordinate scaling)
* @param m02
* the value of the transformation matrix in row 0 and column 2
* (x coordinate translation)
* @param m12
* the value of the transformation matrix in row 1 and column 2
* (y coordinate translation)
*/
public AffineTransform(double m00, double m10, double m01, double m11,
double m02, double m12) {
delegate = new java.awt.geom.AffineTransform(m00, m10, m01, m11, m02,
m12);
}
/**
* Creates a new {@link AffineTransform} with its transformation matrix set
* to the values of the passed-in array. See the
* {@link AffineTransform#AffineTransform(double, double, double, double, double, double)}
* or the {@link java.awt.geom.AffineTransform#AffineTransform(double[])}
* method for a specification of the values in the array.
*
* @param flatmatrix
* the values for the transformation matrix
* @see AffineTransform#AffineTransform(double, double, double, double,
* double, double)
*/
public AffineTransform(double[] flatmatrix) {
delegate = new java.awt.geom.AffineTransform(flatmatrix);
}
private AffineTransform(java.awt.geom.AffineTransform delegate) {
this.delegate = delegate;
}
@Override
public Object clone() {
return delegate.clone();
}
/**
* Concatenates this {@link AffineTransform} and the given
* {@link AffineTransform}, multiplying the transformation matrix of this
* {@link AffineTransform} from the left with the transformation matrix of
* the other {@link AffineTransform}.
*
* @param Tx
* the {@link AffineTransform} that is concatenated with this
* {@link AffineTransform}
* @return <code>this</code> for convenience
*/
public AffineTransform concatenate(AffineTransform Tx) {
delegate.concatenate(Tx.delegate);
return this;
}
/**
* Transforms an array of {@link Point}s specified by their coordinate
* values with this {@link AffineTransform} without applying the translation
* components of the transformation matrix of this {@link AffineTransform}.
*
* @param srcPts
* the array of x and y coordinates specifying the {@link Point}s
* that are transformed
* @param srcOff
* the index of the <i>srcPts</i> array where the x coordinate of
* the first {@link Point} to transform is found
* @param dstPts
* the destination array of x and y coordinates for the result of
* the transformation
* @param dstOff
* the index of the <i>dstPts</i> array where the x coordinate of
* the first transformed {@link Point} is stored
* @param numPts
* the number of {@link Point}s to transform
*/
public void deltaTransform(double[] srcPts, int srcOff, double[] dstPts,
int dstOff, int numPts) {
delegate.deltaTransform(srcPts, srcOff, dstPts, dstOff, numPts);
}
/**
* Transforms the given {@link Point} with this {@link AffineTransform}
* without applying the translation components of the transformation matrix
* of this {@link AffineTransform}.
*
* @param pt
* the {@link Point} to transform
* @return a new, transformed {@link Point}
*/
public Point deltaTransform(Point pt) {
return AWT2Geometry.toPoint(
delegate.deltaTransform(Geometry2AWT.toAWTPoint(pt), null));
}
@Override
public boolean equals(Object obj) {
if (obj instanceof AffineTransform) {
return delegate.equals(((AffineTransform) obj).delegate);
}
return false;
}
/**
* Returns a copy of this {@link AffineTransform}.
*
* @return a copy of this {@link AffineTransform}
*/
public AffineTransform getCopy() {
return new AffineTransform(getMatrix());
}
/**
* Computes the determinant of the transformation matrix of this
* {@link AffineTransform}.
*
* @return the determinant of the transformation matrix of this
* {@link AffineTransform}
*/
public double getDeterminant() {
return delegate.getDeterminant();
}
/**
* Creates a new {@link AffineTransform} that represents the inverse
* transformation of this {@link AffineTransform}.
*
* @return a new {@link AffineTransform} that represents the inverse
* transformation of this {@link AffineTransform}
*/
public AffineTransform getInverse() {
try {
return new AffineTransform(delegate.createInverse());
} catch (NoninvertibleTransformException e) {
throw new IllegalArgumentException(e);
}
}
/**
* Returns the matrix component in the first row and first column.
*
* @return The matrix component in the first row and first column.
*/
public double getM00() {
return delegate.getScaleX();
}
/**
* Returns the matrix component in the first row and second column.
*
* @return The matrix component in the first row and second column.
*/
public double getM01() {
return delegate.getShearX();
}
/**
* Returns the matrix component in the second row and first column.
*
* @return The matrix component in the second row and first column.
*/
public double getM10() {
return delegate.getShearY();
}
/**
* Returns the matrix component in the second row and second column.
*
* @return The matrix component in the second row and second column.
*/
public double getM11() {
return delegate.getScaleY();
}
/**
* Returns the 6 specifiable elements of the transformation matrix of this
* {@link AffineTransform}.
*
* @return the 6 specifiable elements of the transformation matrix of this
* {@link AffineTransform}
*/
public double[] getMatrix() {
double[] flatmatrix = new double[6];
delegate.getMatrix(flatmatrix);
return flatmatrix;
}
/**
* Returns the rotation component of this {@link AffineTransform}.
*
* @return The rotation component of this {@link AffineTransform}.
*/
public Angle getRotation() {
double rad = Math.atan2(getM01(), getM00());
return Angle.fromRad(rad);
}
/**
* Returns the x coordinate scaling of this {@link AffineTransform}'s
* transformation matrix.
*
* @return the x coordinate scaling of this {@link AffineTransform}'s
* transformation matrix
*/
public double getScaleX() {
return Math.sqrt(getM00() * getM00() + getM10() * getM10());
}
/**
* Returns the y coordinate scaling of this {@link AffineTransform}'s
* transformation matrix.
*
* @return the y coordinate scaling of this {@link AffineTransform}'s
* transformation matrix
*/
public double getScaleY() {
return Math.sqrt(getM01() * getM01() + getM11() * getM11());
}
/**
* Transforms the given {@link Point} with this {@link AffineTransform} by
* multiplying the transformation matrix of this {@link AffineTransform}
* with the given {@link Point}.
*
* @param ptSrc
* the {@link Point} to transform
* @return a new, transformed {@link Point}
*/
public Point getTransformed(Point ptSrc) {
return AWT2Geometry.toPoint(
delegate.transform(Geometry2AWT.toAWTPoint(ptSrc), null));
}
/**
* Transforms the given array of {@link Point}s with this
* {@link AffineTransform} by multiplying the transformation matrix of this
* {@link AffineTransform} individually with each of the given {@link Point}
* s.
*
* @param points
* array of {@link Point}s to transform
* @return an array of new, transformed {@link Point}s
*/
public Point[] getTransformed(Point[] points) {
Point[] result = new Point[points.length];
for (int i = 0; i < points.length; i++) {
result[i] = getTransformed(points[i]);
}
return result;
}
/**
* Returns the x coordinate translation of this {@link AffineTransform}'s
* transformation matrix.
*
* @return the x coordinate translation of this {@link AffineTransform}'s
* transformation matrix
*/
public double getTranslateX() {
return delegate.getTranslateX();
}
/**
* Returns the y coordinate translation of this {@link AffineTransform}'s
* transformation matrix.
*
* @return the y coordinate translation of this {@link AffineTransform}'s
* transformation matrix
*/
public double getTranslateY() {
return delegate.getTranslateY();
}
/**
* Returns the type of transformation represented by this
* {@link AffineTransform}. See the
* {@link java.awt.geom.AffineTransform#getType()} method for a
* specification of the return type of this method.
*
* @return the type of transformation represented by this
* {@link AffineTransform}
*/
public int getType() {
return delegate.getType();
}
@Override
public int hashCode() {
return delegate.hashCode();
}
/**
* Inverse transforms an array of {@link Point}s specified by their
* coordinate values with this {@link AffineTransform}.
*
* @param srcPts
* the array of x and y coordinates specifying the {@link Point}s
* that are inverse transformed
* @param srcOff
* the index of the <i>srcPts</i> array where the x coordinate of
* the first {@link Point} to inverse transform is found
* @param dstPts
* the destination array of x and y coordinates for the result of
* the inverse transformation
* @param dstOff
* the index of the <i>dstPts</i> array where the x coordinate of
* the first inverse transformed {@link Point} is stored
* @param numPts
* the number of {@link Point}s to inverse transform
* @throws NoninvertibleTransformException
* when this {@link AffineTransform} is not invertible.
*/
public void inverseTransform(double[] srcPts, int srcOff, double[] dstPts,
int dstOff, int numPts) throws NoninvertibleTransformException {
delegate.inverseTransform(srcPts, srcOff, dstPts, dstOff, numPts);
}
/**
* Inverse transforms the given {@link Point} with this
* {@link AffineTransform}.
*
* @param pt
* the {@link Point} to inverse transform
* @return a new, inverse transformed {@link Point}
* @throws NoninvertibleTransformException
* when this {@link AffineTransform} is not invertible.
*/
public Point inverseTransform(Point pt)
throws NoninvertibleTransformException {
return AWT2Geometry.toPoint(
delegate.inverseTransform(Geometry2AWT.toAWTPoint(pt), null));
}
/**
* Inverts this {@link AffineTransform}.
*
* @return <code>this</code> for convenience
* @throws NoninvertibleTransformException
* when this {@link AffineTransform} is not invertible.
*/
public AffineTransform invert() throws NoninvertibleTransformException {
delegate.invert();
return this;
}
/**
* Checks if the transformation matrix of this {@link AffineTransform}
* equals the identity matrix.
*
* @return <code>true</code> if the transformation matrix of this
* {@link AffineTransform} equals the identity matrix, otherwise
* <code>false</code>
*/
public boolean isIdentity() {
return delegate.isIdentity();
}
/**
* Concatenates this {@link AffineTransform} and the given
* {@link AffineTransform} in reverse order, multiplying the transformation
* matrix of this {@link AffineTransform} from the right with the
* transformation matrix of the other {@link AffineTransform}.
*
* @param Tx
* the {@link AffineTransform} that is concatenated with this
* {@link AffineTransform} in reverse order
* @return <code>this</code> for convenience
*/
public AffineTransform preConcatenate(AffineTransform Tx) {
delegate.preConcatenate(Tx.delegate);
return this;
}
/**
* Adds a rotation by an integer multiple of 90deg to the transformation
* matrix of this {@link AffineTransform}. The integer multiple of 90deg is
* specified by the given number of quadrants.
*
* @param numquadrants
* the integer that defines the number of quadrants to rotate by
* @return <code>this</code> for convenience
*/
public AffineTransform quadrantRotate(int numquadrants) {
delegate.quadrantRotate(numquadrants);
return this;
}
/**
* Adds a rotation by an integer multiple of 90deg around the {@link Point}
* specified by the given x and y coordinates to the transformation matrix
* of this {@link AffineTransform}.
*
* @param numquadrants
* the integer that defines the number of quadrants to rotate by
* @param anchorx
* the x coordinate of the {@link Point} to rotate around
* @param anchory
* the y coordinate of the {@link Point} to rotate around
* @return <code>this</code> for convenience
*/
public AffineTransform quadrantRotate(int numquadrants, double anchorx,
double anchory) {
delegate.quadrantRotate(numquadrants, anchorx, anchory);
return this;
}
/**
* Adds a rotation with the given angle (in radians) to the transformation
* matrix of this {@link AffineTransform}.
*
* @param theta
* the rotation angle in radians
* @return <code>this</code> for convenience
*/
public AffineTransform rotate(double theta) {
delegate.rotate(theta);
return this;
}
/**
* Adds a rotation to the transformation matrix of this
* {@link AffineTransform}. The given coordinates specify a {@link Vector}
* whose {@link Angle} to the x-axis is the applied rotation {@link Angle}.
*
* @param vecx
* the x coordinate of the {@link Vector} specifying the rotation
* {@link Angle}
* @param vecy
* the y coordinate of the {@link Vector} specifying the rotation
* {@link Angle}
* @return <code>this</code> for convenience
*/
public AffineTransform rotate(double vecx, double vecy) {
delegate.rotate(vecx, vecy);
return this;
}
/**
* Adds a rotation with the given angle (in radians) around the
* {@link Point} specified by the given x and y coordinates to the
* transformation matrix of this {@link AffineTransform}.
*
* @param theta
* the rotation angle in radians
* @param anchorx
* the x coordinate of the {@link Point} to rotate around
* @param anchory
* the y coordinate of the {@link Point} to rotate around
* @return <code>this</code> for convenience
*/
public AffineTransform rotate(double theta, double anchorx,
double anchory) {
delegate.rotate(theta, anchorx, anchory);
return this;
}
// TODO: Add the possibility to pass Angle objects instead of simple double
// values.
/**
* Adds a rotation around a {@link Point} to the transformation matrix of
* this {@link AffineTransform}. The given coordinates specify a
* {@link Vector} whose {@link Angle} to the x-axis is the applied rotation
* {@link Angle} and the anchor {@link Point} for the rotation.
*
* @param vecx
* the x coordinate of the {@link Vector} specifying the rotation
* {@link Angle}
* @param vecy
* the y coordinate of the {@link Vector} specifying the rotation
* {@link Angle}
* @param anchorx
* the x coordinate of the {@link Point} to rotate around
* @param anchory
* the y coordinate of the {@link Point} to rotate around
* @return <code>this</code> for convenience
*/
public AffineTransform rotate(double vecx, double vecy, double anchorx,
double anchory) {
delegate.rotate(vecx, vecy, anchorx, anchory);
return this;
}
/**
* Adds an x and y scaling to the transformation matrix of this
* {@link AffineTransform}.
*
* @param sx
* the x scaling factor added to the transformation matrix of
* this {@link AffineTransform}
* @param sy
* the y scaling factor added to the transformation matrix of
* this {@link AffineTransform}
* @return <code>this</code> for convenience
*/
public AffineTransform scale(double sx, double sy) {
delegate.scale(sx, sy);
return this;
}
/**
* Sets the transformation matrix of this {@link AffineTransform} to the
* identity matrix.
*
* @return <code>this</code> for convenience
*/
public AffineTransform setToIdentity() {
delegate.setToIdentity();
return this;
}
/**
* Sets the transformation matrix of this {@link AffineTransform} to a pure
* rotation matrix where the rotation angle is an integer multiple of 90deg.
*
* @param numquadrants
* the integer that defines the number of quadrants to rotate by
* @return <code>this</code> for convenience
*/
public AffineTransform setToQuadrantRotation(int numquadrants) {
delegate.setToQuadrantRotation(numquadrants);
return this;
}
/**
* Sets the transformation matrix of this {@link AffineTransform} to a pure
* rotation and translation matrix where the rotation angle is an integer
* multiple of 90deg and the rotation is around the {@link Point} specified
* by the given x and y coordinates.
*
* @param numquadrants
* the integer that defines the number of quadrants to rotate by
* @param anchorx
* the x coordinate of the {@link Point} to rotate around
* @param anchory
* the y coordinate of the {@link Point} to rotate around
* @return <code>this</code> for convenience
*/
public AffineTransform setToQuadrantRotation(int numquadrants,
double anchorx, double anchory) {
delegate.setToQuadrantRotation(numquadrants, anchorx, anchory);
return this;
}
/**
* Sets the transformation matrix of this {@link AffineTransform} to a pure
* rotation matrix by the given angle specified in radians.
*
* @param theta
* the rotation angle (in radians)
* @return <code>this</code> for convenience
*/
public AffineTransform setToRotation(double theta) {
delegate.setToRotation(theta);
return this;
}
/**
* Sets the transformation matrix of this {@link AffineTransform} to a pure
* rotation matrix. The given x and y coordinates specify a {@link Vector}
* whose {@link Angle} to the x-axis defines the rotation {@link Angle}.
*
* @param vecx
* the x coordinate of the {@link Vector} whose {@link Angle} to
* the x-axis defines the rotation {@link Angle}
* @param vecy
* the y coordinate of the {@link Vector} whose {@link Angle} to
* the x-axis defines the rotation {@link Angle}
* @return <code>this</code> for convenience
*/
public AffineTransform setToRotation(double vecx, double vecy) {
delegate.setToRotation(vecx, vecy);
return this;
}
/**
* Sets the transformation matrix of this {@link AffineTransform} to a pure
* rotation and translation matrix. Thus, the resulting transformation
* matrix rotates {@link Point}s by the given angle (in radians) around the
* {@link Point} specified by the given x and y coordinates.
*
* @param theta
* the rotation angle (in radians)
* @param anchorx
* the x coordinate of the {@link Point} to rotate around
* @param anchory
* the y coordinate of the {@link Point} to rotate around
* @return <code>this</code> for convenience
*/
public AffineTransform setToRotation(double theta, double anchorx,
double anchory) {
delegate.setToRotation(theta, anchorx, anchory);
return this;
}
/**
* Sets the transformation matrix of this {@link AffineTransform} to a pure
* rotation and translation matrix. The firstly given x and y coordinates
* specify a {@link Vector} whose {@link Angle} to the x-axis defines the
* rotation {@link Angle}. The secondly given x and y coordinates specify
* the {@link Point} to rotate around.
*
* @param vecx
* the x coordinate of the {@link Vector} whose {@link Angle} to
* the x-axis defines the rotation {@link Angle}
* @param vecy
* the y coordinate of the {@link Vector} whose {@link Angle} to
* the x-axis defines the rotation {@link Angle}
* @param anchorx
* the x coordinate of the {@link Point} to rotate around
* @param anchory
* the y coordinate of the {@link Point} to rotate around
* @return <code>this</code> for convenience
*/
public AffineTransform setToRotation(double vecx, double vecy,
double anchorx, double anchory) {
delegate.setToRotation(vecx, vecy, anchorx, anchory);
return this;
}
/**
* Sets the transformation matrix of this {@link AffineTransform} to a pure
* scaling matrix.
*
* @param sx
* the x scaling factor
* @param sy
* the y scaling factor
* @return <code>this</code> for convenience
*/
public AffineTransform setToScale(double sx, double sy) {
delegate.setToScale(sx, sy);
return this;
}
/**
* Sets the transformation matrix of this {@link AffineTransform} to a pure
* shearing matrix.
*
* @param shx
* the x shearing factor
* @param shy
* the y shearing factor
* @return <code>this</code> for convenience
*/
public AffineTransform setToShear(double shx, double shy) {
delegate.setToShear(shx, shy);
return this;
}
/**
* Sets the transformation matrix of this {@link AffineTransform} to a pure
* translation matrix that translates {@link Point}s by the given x and y
* values.
*
* @param tx
* the x translation value
* @param ty
* the y translation value
* @return <code>this</code> for convenience
*/
public AffineTransform setToTranslation(double tx, double ty) {
delegate.setToTranslation(tx, ty);
return this;
}
/**
* Sets the transformation matrix of this {@link AffineTransform} to the
* transformation matrix of the given {@link AffineTransform}.
*
* @param Tx
* the {@link AffineTransform} specifying the new transformation
* matrix of this {@link AffineTransform}
* @return <code>this</code> for convenience
*/
public AffineTransform setTransform(AffineTransform Tx) {
delegate.setTransform(Tx.delegate);
return this;
}
/**
* Sets the respective values of the transformation matrix of this
* {@link AffineTransform} to the supplied ones. Note that rotation is a
* combination of shearing and scaling.
*
* @param m00
* the value of the transformation matrix in row 0 and column 0
* (x coordinate scaling)
* @param m10
* the value of the transformation matrix in row 1 and column 0
* (y coordinate shearing)
* @param m01
* the value of the transformation matrix in row 0 and column 1
* (x coordinate shearing)
* @param m11
* the value of the transformation matrix in row 1 and column 1
* (y coordinate scaling)
* @param m02
* the value of the transformation matrix in row 0 and column 2
* (x coordinate translation)
* @param m12
* the value of the transformation matrix in row 1 and column 2
* (y coordinate translation)
* @return <code>this</code> for convenience
*/
public AffineTransform setTransform(double m00, double m10, double m01,
double m11, double m02, double m12) {
delegate.setTransform(m00, m10, m01, m11, m02, m12);
return this;
}
/**
* Adds an x and y shearing to the transformation matrix of this
* {@link AffineTransform}.
*
* @param shx
* the x shearing factor added to the transformation matrix of
* this {@link AffineTransform}
* @param shy
* the y shearing factor added to the transformation matrix of
* this {@link AffineTransform}
* @return <code>this</code> for convenience
*/
public AffineTransform shear(double shx, double shy) {
delegate.shear(shx, shy);
return this;
}
@Override
public String toString() {
return delegate.toString();
}
/**
* Sets the translation values of the x and y coordinates of the
* transformation matrix of this {@link AffineTransform}.
*
* @param tx
* the x coordinate translation
* @param ty
* the y coordinate translation
* @return <code>this</code> for convenience
*/
public AffineTransform translate(double tx, double ty) {
delegate.translate(tx, ty);
return this;
}
}