/* |
|
* Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved. |
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
* |
|
* This code is free software; you can redistribute it and/or modify it |
|
* under the terms of the GNU General Public License version 2 only, as |
|
* published by the Free Software Foundation. Oracle designates this |
|
* particular file as subject to the "Classpath" exception as provided |
|
* by Oracle in the LICENSE file that accompanied this code. |
|
* |
|
* This code is distributed in the hope that it will be useful, but WITHOUT |
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
* version 2 for more details (a copy is included in the LICENSE file that |
|
* accompanied this code). |
|
* |
|
* You should have received a copy of the GNU General Public License version |
|
* 2 along with this work; if not, write to the Free Software Foundation, |
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
* |
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
* or visit www.oracle.com if you need additional information or have any |
|
* questions. |
|
*/ |
|
package java.awt.geom; |
|
import java.awt.Shape; |
|
import java.beans.ConstructorProperties; |
|
/** |
|
* The <code>AffineTransform</code> class represents a 2D affine transform |
|
* that performs a linear mapping from 2D coordinates to other 2D |
|
* coordinates that preserves the "straightness" and |
|
* "parallelness" of lines. Affine transformations can be constructed |
|
* using sequences of translations, scales, flips, rotations, and shears. |
|
* <p> |
|
* Such a coordinate transformation can be represented by a 3 row by |
|
* 3 column matrix with an implied last row of [ 0 0 1 ]. This matrix |
|
* transforms source coordinates {@code (x,y)} into |
|
* destination coordinates {@code (x',y')} by considering |
|
* them to be a column vector and multiplying the coordinate vector |
|
* by the matrix according to the following process: |
|
* <pre> |
|
* [ x'] [ m00 m01 m02 ] [ x ] [ m00x + m01y + m02 ] |
|
* [ y'] = [ m10 m11 m12 ] [ y ] = [ m10x + m11y + m12 ] |
|
* [ 1 ] [ 0 0 1 ] [ 1 ] [ 1 ] |
|
* </pre> |
|
* <h3><a name="quadrantapproximation">Handling 90-Degree Rotations</a></h3> |
|
* <p> |
|
* In some variations of the <code>rotate</code> methods in the |
|
* <code>AffineTransform</code> class, a double-precision argument |
|
* specifies the angle of rotation in radians. |
|
* These methods have special handling for rotations of approximately |
|
* 90 degrees (including multiples such as 180, 270, and 360 degrees), |
|
* so that the common case of quadrant rotation is handled more |
|
* efficiently. |
|
* This special handling can cause angles very close to multiples of |
|
* 90 degrees to be treated as if they were exact multiples of |
|
* 90 degrees. |
|
* For small multiples of 90 degrees the range of angles treated |
|
* as a quadrant rotation is approximately 0.00000121 degrees wide. |
|
* This section explains why such special care is needed and how |
|
* it is implemented. |
|
* <p> |
|
* Since 90 degrees is represented as <code>PI/2</code> in radians, |
|
* and since PI is a transcendental (and therefore irrational) number, |
|
* it is not possible to exactly represent a multiple of 90 degrees as |
|
* an exact double precision value measured in radians. |
|
* As a result it is theoretically impossible to describe quadrant |
|
* rotations (90, 180, 270 or 360 degrees) using these values. |
|
* Double precision floating point values can get very close to |
|
* non-zero multiples of <code>PI/2</code> but never close enough |
|
* for the sine or cosine to be exactly 0.0, 1.0 or -1.0. |
|
* The implementations of <code>Math.sin()</code> and |
|
* <code>Math.cos()</code> correspondingly never return 0.0 |
|
* for any case other than <code>Math.sin(0.0)</code>. |
|
* These same implementations do, however, return exactly 1.0 and |
|
* -1.0 for some range of numbers around each multiple of 90 |
|
* degrees since the correct answer is so close to 1.0 or -1.0 that |
|
* the double precision significand cannot represent the difference |
|
* as accurately as it can for numbers that are near 0.0. |
|
* <p> |
|
* The net result of these issues is that if the |
|
* <code>Math.sin()</code> and <code>Math.cos()</code> methods |
|
* are used to directly generate the values for the matrix modifications |
|
* during these radian-based rotation operations then the resulting |
|
* transform is never strictly classifiable as a quadrant rotation |
|
* even for a simple case like <code>rotate(Math.PI/2.0)</code>, |
|
* due to minor variations in the matrix caused by the non-0.0 values |
|
* obtained for the sine and cosine. |
|
* If these transforms are not classified as quadrant rotations then |
|
* subsequent code which attempts to optimize further operations based |
|
* upon the type of the transform will be relegated to its most general |
|
* implementation. |
|
* <p> |
|
* Because quadrant rotations are fairly common, |
|
* this class should handle these cases reasonably quickly, both in |
|
* applying the rotations to the transform and in applying the resulting |
|
* transform to the coordinates. |
|
* To facilitate this optimal handling, the methods which take an angle |
|
* of rotation measured in radians attempt to detect angles that are |
|
* intended to be quadrant rotations and treat them as such. |
|
* These methods therefore treat an angle <em>theta</em> as a quadrant |
|
* rotation if either <code>Math.sin(<em>theta</em>)</code> or |
|
* <code>Math.cos(<em>theta</em>)</code> returns exactly 1.0 or -1.0. |
|
* As a rule of thumb, this property holds true for a range of |
|
* approximately 0.0000000211 radians (or 0.00000121 degrees) around |
|
* small multiples of <code>Math.PI/2.0</code>. |
|
* |
|
* @author Jim Graham |
|
* @since 1.2 |
|
*/ |
|
public class AffineTransform implements Cloneable, java.io.Serializable { |
|
/* |
|
* This constant is only useful for the cached type field. |
|
* It indicates that the type has been decached and must be recalculated. |
|
*/ |
|
private static final int TYPE_UNKNOWN = -1; |
|
/** |
|
* This constant indicates that the transform defined by this object |
|
* is an identity transform. |
|
* An identity transform is one in which the output coordinates are |
|
* always the same as the input coordinates. |
|
* If this transform is anything other than the identity transform, |
|
* the type will either be the constant GENERAL_TRANSFORM or a |
|
* combination of the appropriate flag bits for the various coordinate |
|
* conversions that this transform performs. |
|
* @see #TYPE_TRANSLATION |
|
* @see #TYPE_UNIFORM_SCALE |
|
* @see #TYPE_GENERAL_SCALE |
|
* @see #TYPE_FLIP |
|
* @see #TYPE_QUADRANT_ROTATION |
|
* @see #TYPE_GENERAL_ROTATION |
|
* @see #TYPE_GENERAL_TRANSFORM |
|
* @see #getType |
|
* @since 1.2 |
|
*/ |
|
public static final int TYPE_IDENTITY = 0; |
|
/** |
|
* This flag bit indicates that the transform defined by this object |
|
* performs a translation in addition to the conversions indicated |
|
* by other flag bits. |
|
* A translation moves the coordinates by a constant amount in x |
|
* and y without changing the length or angle of vectors. |
|
* @see #TYPE_IDENTITY |
|
* @see #TYPE_UNIFORM_SCALE |
|
* @see #TYPE_GENERAL_SCALE |
|
* @see #TYPE_FLIP |
|
* @see #TYPE_QUADRANT_ROTATION |
|
* @see #TYPE_GENERAL_ROTATION |
|
* @see #TYPE_GENERAL_TRANSFORM |
|
* @see #getType |
|
* @since 1.2 |
|
*/ |
|
public static final int TYPE_TRANSLATION = 1; |
|
/** |
|
* This flag bit indicates that the transform defined by this object |
|
* performs a uniform scale in addition to the conversions indicated |
|
* by other flag bits. |
|
* A uniform scale multiplies the length of vectors by the same amount |
|
* in both the x and y directions without changing the angle between |
|
* vectors. |
|
* This flag bit is mutually exclusive with the TYPE_GENERAL_SCALE flag. |
|
* @see #TYPE_IDENTITY |
|
* @see #TYPE_TRANSLATION |
|
* @see #TYPE_GENERAL_SCALE |
|
* @see #TYPE_FLIP |
|
* @see #TYPE_QUADRANT_ROTATION |
|
* @see #TYPE_GENERAL_ROTATION |
|
* @see #TYPE_GENERAL_TRANSFORM |
|
* @see #getType |
|
* @since 1.2 |
|
*/ |
|
public static final int TYPE_UNIFORM_SCALE = 2; |
|
/** |
|
* This flag bit indicates that the transform defined by this object |
|
* performs a general scale in addition to the conversions indicated |
|
* by other flag bits. |
|
* A general scale multiplies the length of vectors by different |
|
* amounts in the x and y directions without changing the angle |
|
* between perpendicular vectors. |
|
* This flag bit is mutually exclusive with the TYPE_UNIFORM_SCALE flag. |
|
* @see #TYPE_IDENTITY |
|
* @see #TYPE_TRANSLATION |
|
* @see #TYPE_UNIFORM_SCALE |
|
* @see #TYPE_FLIP |
|
* @see #TYPE_QUADRANT_ROTATION |
|
* @see #TYPE_GENERAL_ROTATION |
|
* @see #TYPE_GENERAL_TRANSFORM |
|
* @see #getType |
|
* @since 1.2 |
|
*/ |
|
public static final int TYPE_GENERAL_SCALE = 4; |
|
/** |
|
* This constant is a bit mask for any of the scale flag bits. |
|
* @see #TYPE_UNIFORM_SCALE |
|
* @see #TYPE_GENERAL_SCALE |
|
* @since 1.2 |
|
*/ |
|
public static final int TYPE_MASK_SCALE = (TYPE_UNIFORM_SCALE | |
|
TYPE_GENERAL_SCALE); |
|
/** |
|
* This flag bit indicates that the transform defined by this object |
|
* performs a mirror image flip about some axis which changes the |
|
* normally right handed coordinate system into a left handed |
|
* system in addition to the conversions indicated by other flag bits. |
|
* A right handed coordinate system is one where the positive X |
|
* axis rotates counterclockwise to overlay the positive Y axis |
|
* similar to the direction that the fingers on your right hand |
|
* curl when you stare end on at your thumb. |
|
* A left handed coordinate system is one where the positive X |
|
* axis rotates clockwise to overlay the positive Y axis similar |
|
* to the direction that the fingers on your left hand curl. |
|
* There is no mathematical way to determine the angle of the |
|
* original flipping or mirroring transformation since all angles |
|
* of flip are identical given an appropriate adjusting rotation. |
|
* @see #TYPE_IDENTITY |
|
* @see #TYPE_TRANSLATION |
|
* @see #TYPE_UNIFORM_SCALE |
|
* @see #TYPE_GENERAL_SCALE |
|
* @see #TYPE_QUADRANT_ROTATION |
|
* @see #TYPE_GENERAL_ROTATION |
|
* @see #TYPE_GENERAL_TRANSFORM |
|
* @see #getType |
|
* @since 1.2 |
|
*/ |
|
public static final int TYPE_FLIP = 64; |
|
/* NOTE: TYPE_FLIP was added after GENERAL_TRANSFORM was in public |
|
* circulation and the flag bits could no longer be conveniently |
|
* renumbered without introducing binary incompatibility in outside |
|
* code. |
|
*/ |
|
/** |
|
* This flag bit indicates that the transform defined by this object |
|
* performs a quadrant rotation by some multiple of 90 degrees in |
|
* addition to the conversions indicated by other flag bits. |
|
* A rotation changes the angles of vectors by the same amount |
|
* regardless of the original direction of the vector and without |
|
* changing the length of the vector. |
|
* This flag bit is mutually exclusive with the TYPE_GENERAL_ROTATION flag. |
|
* @see #TYPE_IDENTITY |
|
* @see #TYPE_TRANSLATION |
|
* @see #TYPE_UNIFORM_SCALE |
|
* @see #TYPE_GENERAL_SCALE |
|
* @see #TYPE_FLIP |
|
* @see #TYPE_GENERAL_ROTATION |
|
* @see #TYPE_GENERAL_TRANSFORM |
|
* @see #getType |
|
* @since 1.2 |
|
*/ |
|
public static final int TYPE_QUADRANT_ROTATION = 8; |
|
/** |
|
* This flag bit indicates that the transform defined by this object |
|
* performs a rotation by an arbitrary angle in addition to the |
|
* conversions indicated by other flag bits. |
|
* A rotation changes the angles of vectors by the same amount |
|
* regardless of the original direction of the vector and without |
|
* changing the length of the vector. |
|
* This flag bit is mutually exclusive with the |
|
* TYPE_QUADRANT_ROTATION flag. |
|
* @see #TYPE_IDENTITY |
|
* @see #TYPE_TRANSLATION |
|
* @see #TYPE_UNIFORM_SCALE |
|
* @see #TYPE_GENERAL_SCALE |
|
* @see #TYPE_FLIP |
|
* @see #TYPE_QUADRANT_ROTATION |
|
* @see #TYPE_GENERAL_TRANSFORM |
|
* @see #getType |
|
* @since 1.2 |
|
*/ |
|
public static final int TYPE_GENERAL_ROTATION = 16; |
|
/** |
|
* This constant is a bit mask for any of the rotation flag bits. |
|
* @see #TYPE_QUADRANT_ROTATION |
|
* @see #TYPE_GENERAL_ROTATION |
|
* @since 1.2 |
|
*/ |
|
public static final int TYPE_MASK_ROTATION = (TYPE_QUADRANT_ROTATION | |
|
TYPE_GENERAL_ROTATION); |
|
/** |
|
* This constant indicates that the transform defined by this object |
|
* performs an arbitrary conversion of the input coordinates. |
|
* If this transform can be classified by any of the above constants, |
|
* the type will either be the constant TYPE_IDENTITY or a |
|
* combination of the appropriate flag bits for the various coordinate |
|
* conversions that this transform performs. |
|
* @see #TYPE_IDENTITY |
|
* @see #TYPE_TRANSLATION |
|
* @see #TYPE_UNIFORM_SCALE |
|
* @see #TYPE_GENERAL_SCALE |
|
* @see #TYPE_FLIP |
|
* @see #TYPE_QUADRANT_ROTATION |
|
* @see #TYPE_GENERAL_ROTATION |
|
* @see #getType |
|
* @since 1.2 |
|
*/ |
|
public static final int TYPE_GENERAL_TRANSFORM = 32; |
|
/** |
|
* This constant is used for the internal state variable to indicate |
|
* that no calculations need to be performed and that the source |
|
* coordinates only need to be copied to their destinations to |
|
* complete the transformation equation of this transform. |
|
* @see #APPLY_TRANSLATE |
|
* @see #APPLY_SCALE |
|
* @see #APPLY_SHEAR |
|
* @see #state |
|
*/ |
|
static final int APPLY_IDENTITY = 0; |
|
/** |
|
* This constant is used for the internal state variable to indicate |
|
* that the translation components of the matrix (m02 and m12) need |
|
* to be added to complete the transformation equation of this transform. |
|
* @see #APPLY_IDENTITY |
|
* @see #APPLY_SCALE |
|
* @see #APPLY_SHEAR |
|
* @see #state |
|
*/ |
|
static final int APPLY_TRANSLATE = 1; |
|
/** |
|
* This constant is used for the internal state variable to indicate |
|
* that the scaling components of the matrix (m00 and m11) need |
|
* to be factored in to complete the transformation equation of |
|
* this transform. If the APPLY_SHEAR bit is also set then it |
|
* indicates that the scaling components are not both 0.0. If the |
|
* APPLY_SHEAR bit is not also set then it indicates that the |
|
* scaling components are not both 1.0. If neither the APPLY_SHEAR |
|
* nor the APPLY_SCALE bits are set then the scaling components |
|
* are both 1.0, which means that the x and y components contribute |
|
* to the transformed coordinate, but they are not multiplied by |
|
* any scaling factor. |
|
* @see #APPLY_IDENTITY |
|
* @see #APPLY_TRANSLATE |
|
* @see #APPLY_SHEAR |
|
* @see #state |
|
*/ |
|
static final int APPLY_SCALE = 2; |
|
/** |
|
* This constant is used for the internal state variable to indicate |
|
* that the shearing components of the matrix (m01 and m10) need |
|
* to be factored in to complete the transformation equation of this |
|
* transform. The presence of this bit in the state variable changes |
|
* the interpretation of the APPLY_SCALE bit as indicated in its |
|
* documentation. |
|
* @see #APPLY_IDENTITY |
|
* @see #APPLY_TRANSLATE |
|
* @see #APPLY_SCALE |
|
* @see #state |
|
*/ |
|
static final int APPLY_SHEAR = 4; |
|
/* |
|
* For methods which combine together the state of two separate |
|
* transforms and dispatch based upon the combination, these constants |
|
* specify how far to shift one of the states so that the two states |
|
* are mutually non-interfering and provide constants for testing the |
|
* bits of the shifted (HI) state. The methods in this class use |
|
* the convention that the state of "this" transform is unshifted and |
|
* the state of the "other" or "argument" transform is shifted (HI). |
|
*/ |
|
private static final int HI_SHIFT = 3; |
|
private static final int HI_IDENTITY = APPLY_IDENTITY << HI_SHIFT; |
|
private static final int HI_TRANSLATE = APPLY_TRANSLATE << HI_SHIFT; |
|
private static final int HI_SCALE = APPLY_SCALE << HI_SHIFT; |
|
private static final int HI_SHEAR = APPLY_SHEAR << HI_SHIFT; |
|
/** |
|
* The X coordinate scaling element of the 3x3 |
|
* affine transformation matrix. |
|
* |
|
* @serial |
|
*/ |
|
double m00; |
|
/** |
|
* The Y coordinate shearing element of the 3x3 |
|
* affine transformation matrix. |
|
* |
|
* @serial |
|
*/ |
|
double m10; |
|
/** |
|
* The X coordinate shearing element of the 3x3 |
|
* affine transformation matrix. |
|
* |
|
* @serial |
|
*/ |
|
double m01; |
|
/** |
|
* The Y coordinate scaling element of the 3x3 |
|
* affine transformation matrix. |
|
* |
|
* @serial |
|
*/ |
|
double m11; |
|
/** |
|
* The X coordinate of the translation element of the |
|
* 3x3 affine transformation matrix. |
|
* |
|
* @serial |
|
*/ |
|
double m02; |
|
/** |
|
* The Y coordinate of the translation element of the |
|
* 3x3 affine transformation matrix. |
|
* |
|
* @serial |
|
*/ |
|
double m12; |
|
/** |
|
* This field keeps track of which components of the matrix need to |
|
* be applied when performing a transformation. |
|
* @see #APPLY_IDENTITY |
|
* @see #APPLY_TRANSLATE |
|
* @see #APPLY_SCALE |
|
* @see #APPLY_SHEAR |
|
*/ |
|
transient int state; |
|
/** |
|
* This field caches the current transformation type of the matrix. |
|
* @see #TYPE_IDENTITY |
|
* @see #TYPE_TRANSLATION |
|
* @see #TYPE_UNIFORM_SCALE |
|
* @see #TYPE_GENERAL_SCALE |
|
* @see #TYPE_FLIP |
|
* @see #TYPE_QUADRANT_ROTATION |
|
* @see #TYPE_GENERAL_ROTATION |
|
* @see #TYPE_GENERAL_TRANSFORM |
|
* @see #TYPE_UNKNOWN |
|
* @see #getType |
|
*/ |
|
private transient int type; |
|
private AffineTransform(double m00, double m10, |
|
double m01, double m11, |
|
double m02, double m12, |
|
int state) { |
|
this.m00 = m00; |
|
this.m10 = m10; |
|
this.m01 = m01; |
|
this.m11 = m11; |
|
this.m02 = m02; |
|
this.m12 = m12; |
|
this.state = state; |
|
this.type = TYPE_UNKNOWN; |
|
} |
|
/** |
|
* Constructs a new <code>AffineTransform</code> representing the |
|
* Identity transformation. |
|
* @since 1.2 |
|
*/ |
|
public AffineTransform() { |
|
m00 = m11 = 1.0; |
|
// m01 = m10 = m02 = m12 = 0.0; /* Not needed. */ |
|
// state = APPLY_IDENTITY; /* Not needed. */ |
|
// type = TYPE_IDENTITY; /* Not needed. */ |
|
} |
|
/** |
|
* Constructs a new <code>AffineTransform</code> that is a copy of |
|
* the specified <code>AffineTransform</code> object. |
|
* @param Tx the <code>AffineTransform</code> object to copy |
|
* @since 1.2 |
|
*/ |
|
public AffineTransform(AffineTransform Tx) { |
|
this.m00 = Tx.m00; |
|
this.m10 = Tx.m10; |
|
this.m01 = Tx.m01; |
|
this.m11 = Tx.m11; |
|
this.m02 = Tx.m02; |
|
this.m12 = Tx.m12; |
|
this.state = Tx.state; |
|
this.type = Tx.type; |
|
} |
|
/** |
|
* Constructs a new <code>AffineTransform</code> from 6 floating point |
|
* values representing the 6 specifiable entries of the 3x3 |
|
* transformation matrix. |
|
* |
|
* @param m00 the X coordinate scaling element of the 3x3 matrix |
|
* @param m10 the Y coordinate shearing element of the 3x3 matrix |
|
* @param m01 the X coordinate shearing element of the 3x3 matrix |
|
* @param m11 the Y coordinate scaling element of the 3x3 matrix |
|
* @param m02 the X coordinate translation element of the 3x3 matrix |
|
* @param m12 the Y coordinate translation element of the 3x3 matrix |
|
* @since 1.2 |
|
*/ |
|
@ConstructorProperties({ "scaleX", "shearY", "shearX", "scaleY", "translateX", "translateY" }) |
|
public AffineTransform(float m00, float m10, |
|
float m01, float m11, |
|
float m02, float m12) { |
|
this.m00 = m00; |
|
this.m10 = m10; |
|
this.m01 = m01; |
|
this.m11 = m11; |
|
this.m02 = m02; |
|
this.m12 = m12; |
|
updateState(); |
|
} |
|
/** |
|
* Constructs a new <code>AffineTransform</code> from an array of |
|
* floating point values representing either the 4 non-translation |
|
* entries or the 6 specifiable entries of the 3x3 transformation |
|
* matrix. The values are retrieved from the array as |
|
* { m00 m10 m01 m11 [m02 m12]}. |
|
* @param flatmatrix the float array containing the values to be set |
|
* in the new <code>AffineTransform</code> object. The length of the |
|
* array is assumed to be at least 4. If the length of the array is |
|
* less than 6, only the first 4 values are taken. If the length of |
|
* the array is greater than 6, the first 6 values are taken. |
|
* @since 1.2 |
|
*/ |
|
public AffineTransform(float[] flatmatrix) { |
|
m00 = flatmatrix[0]; |
|
m10 = flatmatrix[1]; |
|
m01 = flatmatrix[2]; |
|
m11 = flatmatrix[3]; |
|
if (flatmatrix.length > 5) { |
|
m02 = flatmatrix[4]; |
|
m12 = flatmatrix[5]; |
|
} |
|
updateState(); |
|
} |
|
/** |
|
* Constructs a new <code>AffineTransform</code> from 6 double |
|
* precision values representing the 6 specifiable entries of the 3x3 |
|
* transformation matrix. |
|
* |
|
* @param m00 the X coordinate scaling element of the 3x3 matrix |
|
* @param m10 the Y coordinate shearing element of the 3x3 matrix |
|
* @param m01 the X coordinate shearing element of the 3x3 matrix |
|
* @param m11 the Y coordinate scaling element of the 3x3 matrix |
|
* @param m02 the X coordinate translation element of the 3x3 matrix |
|
* @param m12 the Y coordinate translation element of the 3x3 matrix |
|
* @since 1.2 |
|
*/ |
|
public AffineTransform(double m00, double m10, |
|
double m01, double m11, |
|
double m02, double m12) { |
|
this.m00 = m00; |
|
this.m10 = m10; |
|
this.m01 = m01; |
|
this.m11 = m11; |
|
this.m02 = m02; |
|
this.m12 = m12; |
|
updateState(); |
|
} |
|
/** |
|
* Constructs a new <code>AffineTransform</code> from an array of |
|
* double precision values representing either the 4 non-translation |
|
* entries or the 6 specifiable entries of the 3x3 transformation |
|
* matrix. The values are retrieved from the array as |
|
* { m00 m10 m01 m11 [m02 m12]}. |
|
* @param flatmatrix the double array containing the values to be set |
|
* in the new <code>AffineTransform</code> object. The length of the |
|
* array is assumed to be at least 4. If the length of the array is |
|
* less than 6, only the first 4 values are taken. If the length of |
|
* the array is greater than 6, the first 6 values are taken. |
|
* @since 1.2 |
|
*/ |
|
public AffineTransform(double[] flatmatrix) { |
|
m00 = flatmatrix[0]; |
|
m10 = flatmatrix[1]; |
|
m01 = flatmatrix[2]; |
|
m11 = flatmatrix[3]; |
|
if (flatmatrix.length > 5) { |
|
m02 = flatmatrix[4]; |
|
m12 = flatmatrix[5]; |
|
} |
|
updateState(); |
|
} |
|
/** |
|
* Returns a transform representing a translation transformation. |
|
* The matrix representing the returned transform is: |
|
* <pre> |
|
* [ 1 0 tx ] |
|
* [ 0 1 ty ] |
|
* [ 0 0 1 ] |
|
* </pre> |
|
* @param tx the distance by which coordinates are translated in the |
|
* X axis direction |
|
* @param ty the distance by which coordinates are translated in the |
|
* Y axis direction |
|
* @return an <code>AffineTransform</code> object that represents a |
|
* translation transformation, created with the specified vector. |
|
* @since 1.2 |
|
*/ |
|
public static AffineTransform getTranslateInstance(double tx, double ty) { |
|
AffineTransform Tx = new AffineTransform(); |
|
Tx.setToTranslation(tx, ty); |
|
return Tx; |
|
} |
|
/** |
|
* Returns a transform representing a rotation transformation. |
|
* The matrix representing the returned transform is: |
|
* <pre> |
|
* [ cos(theta) -sin(theta) 0 ] |
|
* [ sin(theta) cos(theta) 0 ] |
|
* [ 0 0 1 ] |
|
* </pre> |
|
* Rotating by a positive angle theta rotates points on the positive |
|
* X axis toward the positive Y axis. |
|
* Note also the discussion of |
|
* <a href="#quadrantapproximation">Handling 90-Degree Rotations</a> |
|
* above. |
|
* @param theta the angle of rotation measured in radians |
|
* @return an <code>AffineTransform</code> object that is a rotation |
|
* transformation, created with the specified angle of rotation. |
|
* @since 1.2 |
|
*/ |
|
public static AffineTransform getRotateInstance(double theta) { |
|
AffineTransform Tx = new AffineTransform(); |
|
Tx.setToRotation(theta); |
|
return Tx; |
|
} |
|
/** |
|
* Returns a transform that rotates coordinates around an anchor point. |
|
* This operation is equivalent to translating the coordinates so |
|
* that the anchor point is at the origin (S1), then rotating them |
|
* about the new origin (S2), and finally translating so that the |
|
* intermediate origin is restored to the coordinates of the original |
|
* anchor point (S3). |
|
* <p> |
|
* This operation is equivalent to the following sequence of calls: |
|
* <pre> |
|
* AffineTransform Tx = new AffineTransform(); |
|
* Tx.translate(anchorx, anchory); // S3: final translation |
|
* Tx.rotate(theta); // S2: rotate around anchor |
|
* Tx.translate(-anchorx, -anchory); // S1: translate anchor to origin |
|
* </pre> |
|
* The matrix representing the returned transform is: |
|
* <pre> |
|
* [ cos(theta) -sin(theta) x-x*cos+y*sin ] |
|
* [ sin(theta) cos(theta) y-x*sin-y*cos ] |
|
* [ 0 0 1 ] |
|
* </pre> |
|
* Rotating by a positive angle theta rotates points on the positive |
|
* X axis toward the positive Y axis. |
|
* Note also the discussion of |
|
* <a href="#quadrantapproximation">Handling 90-Degree Rotations</a> |
|
* above. |
|
* |
|
* @param theta the angle of rotation measured in radians |
|
* @param anchorx the X coordinate of the rotation anchor point |
|
* @param anchory the Y coordinate of the rotation anchor point |
|
* @return an <code>AffineTransform</code> object that rotates |
|
* coordinates around the specified point by the specified angle of |
|
* rotation. |
|
* @since 1.2 |
|
*/ |
|
public static AffineTransform getRotateInstance(double theta, |
|
double anchorx, |
|
double anchory) |
|
{ |
|
AffineTransform Tx = new AffineTransform(); |
|
Tx.setToRotation(theta, anchorx, anchory); |
|
return Tx; |
|
} |
|
/** |
|
* Returns a transform that rotates coordinates according to |
|
* a rotation vector. |
|
* All coordinates rotate about the origin by the same amount. |
|
* The amount of rotation is such that coordinates along the former |
|
* positive X axis will subsequently align with the vector pointing |
|
* from the origin to the specified vector coordinates. |
|
* If both <code>vecx</code> and <code>vecy</code> are 0.0, |
|
* an identity transform is returned. |
|
* This operation is equivalent to calling: |
|
* <pre> |
|
* AffineTransform.getRotateInstance(Math.atan2(vecy, vecx)); |
|
* </pre> |
|
* |
|
* @param vecx the X coordinate of the rotation vector |
|
* @param vecy the Y coordinate of the rotation vector |
|
* @return an <code>AffineTransform</code> object that rotates |
|
* coordinates according to the specified rotation vector. |
|
* @since 1.6 |
|
*/ |
|
public static AffineTransform getRotateInstance(double vecx, double vecy) { |
|
AffineTransform Tx = new AffineTransform(); |
|
Tx.setToRotation(vecx, vecy); |
|
return Tx; |
|
} |
|
/** |
|
* Returns a transform that rotates coordinates around an anchor |
|
* point according to a rotation vector. |
|
* All coordinates rotate about the specified anchor coordinates |
|
* by the same amount. |
|
* The amount of rotation is such that coordinates along the former |
|
* positive X axis will subsequently align with the vector pointing |
|
* from the origin to the specified vector coordinates. |
|
* If both <code>vecx</code> and <code>vecy</code> are 0.0, |
|
* an identity transform is returned. |
|
* This operation is equivalent to calling: |
|
* <pre> |
|
* AffineTransform.getRotateInstance(Math.atan2(vecy, vecx), |
|
* anchorx, anchory); |
|
* </pre> |
|
* |
|
* @param vecx the X coordinate of the rotation vector |
|
* @param vecy the Y coordinate of the rotation vector |
|
* @param anchorx the X coordinate of the rotation anchor point |
|
* @param anchory the Y coordinate of the rotation anchor point |
|
* @return an <code>AffineTransform</code> object that rotates |
|
* coordinates around the specified point according to the |
|
* specified rotation vector. |
|
* @since 1.6 |
|
*/ |
|
public static AffineTransform getRotateInstance(double vecx, |
|
double vecy, |
|
double anchorx, |
|
double anchory) |
|
{ |
|
AffineTransform Tx = new AffineTransform(); |
|
Tx.setToRotation(vecx, vecy, anchorx, anchory); |
|
return Tx; |
|
} |
|
/** |
|
* Returns a transform that rotates coordinates by the specified |
|
* number of quadrants. |
|
* This operation is equivalent to calling: |
|
* <pre> |
|
* AffineTransform.getRotateInstance(numquadrants * Math.PI / 2.0); |
|
* </pre> |
|
* Rotating by a positive number of quadrants rotates points on |
|
* the positive X axis toward the positive Y axis. |
|
* @param numquadrants the number of 90 degree arcs to rotate by |
|
* @return an <code>AffineTransform</code> object that rotates |
|
* coordinates by the specified number of quadrants. |
|
* @since 1.6 |
|
*/ |
|
public static AffineTransform getQuadrantRotateInstance(int numquadrants) { |
|
AffineTransform Tx = new AffineTransform(); |
|
Tx.setToQuadrantRotation(numquadrants); |
|
return Tx; |
|
} |
|
/** |
|
* Returns a transform that rotates coordinates by the specified |
|
* number of quadrants around the specified anchor point. |
|
* This operation is equivalent to calling: |
|
* <pre> |
|
* AffineTransform.getRotateInstance(numquadrants * Math.PI / 2.0, |
|
* anchorx, anchory); |
|
* </pre> |
|
* Rotating by a positive number of quadrants rotates points on |
|
* the positive X axis toward the positive Y axis. |
|
* |
|
* @param numquadrants the number of 90 degree arcs to rotate by |
|
* @param anchorx the X coordinate of the rotation anchor point |
|
* @param anchory the Y coordinate of the rotation anchor point |
|
* @return an <code>AffineTransform</code> object that rotates |
|
* coordinates by the specified number of quadrants around the |
|
* specified anchor point. |
|
* @since 1.6 |
|
*/ |
|
public static AffineTransform getQuadrantRotateInstance(int numquadrants, |
|
double anchorx, |
|
double anchory) |
|
{ |
|
AffineTransform Tx = new AffineTransform(); |
|
Tx.setToQuadrantRotation(numquadrants, anchorx, anchory); |
|
return Tx; |
|
} |
|
/** |
|
* Returns a transform representing a scaling transformation. |
|
* The matrix representing the returned transform is: |
|
* <pre> |
|
* [ sx 0 0 ] |
|
* [ 0 sy 0 ] |
|
* [ 0 0 1 ] |
|
* </pre> |
|
* @param sx the factor by which coordinates are scaled along the |
|
* X axis direction |
|
* @param sy the factor by which coordinates are scaled along the |
|
* Y axis direction |
|
* @return an <code>AffineTransform</code> object that scales |
|
* coordinates by the specified factors. |
|
* @since 1.2 |
|
*/ |
|
public static AffineTransform getScaleInstance(double sx, double sy) { |
|
AffineTransform Tx = new AffineTransform(); |
|
Tx.setToScale(sx, sy); |
|
return Tx; |
|
} |
|
/** |
|
* Returns a transform representing a shearing transformation. |
|
* The matrix representing the returned transform is: |
|
* <pre> |
|
* [ 1 shx 0 ] |
|
* [ shy 1 0 ] |
|
* [ 0 0 1 ] |
|
* </pre> |
|
* @param shx the multiplier by which coordinates are shifted in the |
|
* direction of the positive X axis as a factor of their Y coordinate |
|
* @param shy the multiplier by which coordinates are shifted in the |
|
* direction of the positive Y axis as a factor of their X coordinate |
|
* @return an <code>AffineTransform</code> object that shears |
|
* coordinates by the specified multipliers. |
|
* @since 1.2 |
|
*/ |
|
public static AffineTransform getShearInstance(double shx, double shy) { |
|
AffineTransform Tx = new AffineTransform(); |
|
Tx.setToShear(shx, shy); |
|
return Tx; |
|
} |
|
/** |
|
* Retrieves the flag bits describing the conversion properties of |
|
* this transform. |
|
* The return value is either one of the constants TYPE_IDENTITY |
|
* or TYPE_GENERAL_TRANSFORM, or a combination of the |
|
* appropriate flag bits. |
|
* A valid combination of flag bits is an exclusive OR operation |
|
* that can combine |
|
* the TYPE_TRANSLATION flag bit |
|
* in addition to either of the |
|
* TYPE_UNIFORM_SCALE or TYPE_GENERAL_SCALE flag bits |
|
* as well as either of the |
|
* TYPE_QUADRANT_ROTATION or TYPE_GENERAL_ROTATION flag bits. |
|
* @return the OR combination of any of the indicated flags that |
|
* apply to this transform |
|
* @see #TYPE_IDENTITY |
|
* @see #TYPE_TRANSLATION |
|
* @see #TYPE_UNIFORM_SCALE |
|
* @see #TYPE_GENERAL_SCALE |
|
* @see #TYPE_QUADRANT_ROTATION |
|
* @see #TYPE_GENERAL_ROTATION |
|
* @see #TYPE_GENERAL_TRANSFORM |
|
* @since 1.2 |
|
*/ |
|
public int getType() { |
|
if (type == TYPE_UNKNOWN) { |
|
calculateType(); |
|
} |
|
return type; |
|
} |
|
/** |
|
* This is the utility function to calculate the flag bits when |
|
* they have not been cached. |
|
* @see #getType |
|
*/ |
|
@SuppressWarnings("fallthrough") |
|
private void calculateType() { |
|
int ret = TYPE_IDENTITY; |
|
boolean sgn0, sgn1; |
|
double M0, M1, M2, M3; |
|
updateState(); |
|
switch (state) { |
|
default: |
|
stateError(); |
|
/* NOTREACHED */ |
|
case (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
ret = TYPE_TRANSLATION; |
|
/* NOBREAK */ |
|
case (APPLY_SHEAR | APPLY_SCALE): |
|
if ((M0 = m00) * (M2 = m01) + (M3 = m10) * (M1 = m11) != 0) { |
|
// Transformed unit vectors are not perpendicular... |
|
this.type = TYPE_GENERAL_TRANSFORM; |
|
return; |
|
} |
|
sgn0 = (M0 >= 0.0); |
|
sgn1 = (M1 >= 0.0); |
|
if (sgn0 == sgn1) { |
|
// sgn(M0) == sgn(M1) therefore sgn(M2) == -sgn(M3) |
|
// This is the "unflipped" (right-handed) state |
|
if (M0 != M1 || M2 != -M3) { |
|
ret |= (TYPE_GENERAL_ROTATION | TYPE_GENERAL_SCALE); |
|
} else if (M0 * M1 - M2 * M3 != 1.0) { |
|
ret |= (TYPE_GENERAL_ROTATION | TYPE_UNIFORM_SCALE); |
|
} else { |
|
ret |= TYPE_GENERAL_ROTATION; |
|
} |
|
} else { |
|
// sgn(M0) == -sgn(M1) therefore sgn(M2) == sgn(M3) |
|
// This is the "flipped" (left-handed) state |
|
if (M0 != -M1 || M2 != M3) { |
|
ret |= (TYPE_GENERAL_ROTATION | |
|
TYPE_FLIP | |
|
TYPE_GENERAL_SCALE); |
|
} else if (M0 * M1 - M2 * M3 != 1.0) { |
|
ret |= (TYPE_GENERAL_ROTATION | |
|
TYPE_FLIP | |
|
TYPE_UNIFORM_SCALE); |
|
} else { |
|
ret |= (TYPE_GENERAL_ROTATION | TYPE_FLIP); |
|
} |
|
} |
|
break; |
|
case (APPLY_SHEAR | APPLY_TRANSLATE): |
|
ret = TYPE_TRANSLATION; |
|
/* NOBREAK */ |
|
case (APPLY_SHEAR): |
|
sgn0 = ((M0 = m01) >= 0.0); |
|
sgn1 = ((M1 = m10) >= 0.0); |
|
if (sgn0 != sgn1) { |
|
// Different signs - simple 90 degree rotation |
|
if (M0 != -M1) { |
|
ret |= (TYPE_QUADRANT_ROTATION | TYPE_GENERAL_SCALE); |
|
} else if (M0 != 1.0 && M0 != -1.0) { |
|
ret |= (TYPE_QUADRANT_ROTATION | TYPE_UNIFORM_SCALE); |
|
} else { |
|
ret |= TYPE_QUADRANT_ROTATION; |
|
} |
|
} else { |
|
// Same signs - 90 degree rotation plus an axis flip too |
|
if (M0 == M1) { |
|
ret |= (TYPE_QUADRANT_ROTATION | |
|
TYPE_FLIP | |
|
TYPE_UNIFORM_SCALE); |
|
} else { |
|
ret |= (TYPE_QUADRANT_ROTATION | |
|
TYPE_FLIP | |
|
TYPE_GENERAL_SCALE); |
|
} |
|
} |
|
break; |
|
case (APPLY_SCALE | APPLY_TRANSLATE): |
|
ret = TYPE_TRANSLATION; |
|
/* NOBREAK */ |
|
case (APPLY_SCALE): |
|
sgn0 = ((M0 = m00) >= 0.0); |
|
sgn1 = ((M1 = m11) >= 0.0); |
|
if (sgn0 == sgn1) { |
|
if (sgn0) { |
|
// Both scaling factors non-negative - simple scale |
|
// Note: APPLY_SCALE implies M0, M1 are not both 1 |
|
if (M0 == M1) { |
|
ret |= TYPE_UNIFORM_SCALE; |
|
} else { |
|
ret |= TYPE_GENERAL_SCALE; |
|
} |
|
} else { |
|
// Both scaling factors negative - 180 degree rotation |
|
if (M0 != M1) { |
|
ret |= (TYPE_QUADRANT_ROTATION | TYPE_GENERAL_SCALE); |
|
} else if (M0 != -1.0) { |
|
ret |= (TYPE_QUADRANT_ROTATION | TYPE_UNIFORM_SCALE); |
|
} else { |
|
ret |= TYPE_QUADRANT_ROTATION; |
|
} |
|
} |
|
} else { |
|
// Scaling factor signs different - flip about some axis |
|
if (M0 == -M1) { |
|
if (M0 == 1.0 || M0 == -1.0) { |
|
ret |= TYPE_FLIP; |
|
} else { |
|
ret |= (TYPE_FLIP | TYPE_UNIFORM_SCALE); |
|
} |
|
} else { |
|
ret |= (TYPE_FLIP | TYPE_GENERAL_SCALE); |
|
} |
|
} |
|
break; |
|
case (APPLY_TRANSLATE): |
|
ret = TYPE_TRANSLATION; |
|
break; |
|
case (APPLY_IDENTITY): |
|
break; |
|
} |
|
this.type = ret; |
|
} |
|
/** |
|
* Returns the determinant of the matrix representation of the transform. |
|
* The determinant is useful both to determine if the transform can |
|
* be inverted and to get a single value representing the |
|
* combined X and Y scaling of the transform. |
|
* <p> |
|
* If the determinant is non-zero, then this transform is |
|
* invertible and the various methods that depend on the inverse |
|
* transform do not need to throw a |
|
* {@link NoninvertibleTransformException}. |
|
* If the determinant is zero then this transform can not be |
|
* inverted since the transform maps all input coordinates onto |
|
* a line or a point. |
|
* If the determinant is near enough to zero then inverse transform |
|
* operations might not carry enough precision to produce meaningful |
|
* results. |
|
* <p> |
|
* If this transform represents a uniform scale, as indicated by |
|
* the <code>getType</code> method then the determinant also |
|
* represents the square of the uniform scale factor by which all of |
|
* the points are expanded from or contracted towards the origin. |
|
* If this transform represents a non-uniform scale or more general |
|
* transform then the determinant is not likely to represent a |
|
* value useful for any purpose other than determining if inverse |
|
* transforms are possible. |
|
* <p> |
|
* Mathematically, the determinant is calculated using the formula: |
|
* <pre> |
|
* | m00 m01 m02 | |
|
* | m10 m11 m12 | = m00 * m11 - m01 * m10 |
|
* | 0 0 1 | |
|
* </pre> |
|
* |
|
* @return the determinant of the matrix used to transform the |
|
* coordinates. |
|
* @see #getType |
|
* @see #createInverse |
|
* @see #inverseTransform |
|
* @see #TYPE_UNIFORM_SCALE |
|
* @since 1.2 |
|
*/ |
|
@SuppressWarnings("fallthrough") |
|
public double getDeterminant() { |
|
switch (state) { |
|
default: |
|
stateError(); |
|
/* NOTREACHED */ |
|
case (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
case (APPLY_SHEAR | APPLY_SCALE): |
|
return m00 * m11 - m01 * m10; |
|
case (APPLY_SHEAR | APPLY_TRANSLATE): |
|
case (APPLY_SHEAR): |
|
return -(m01 * m10); |
|
case (APPLY_SCALE | APPLY_TRANSLATE): |
|
case (APPLY_SCALE): |
|
return m00 * m11; |
|
case (APPLY_TRANSLATE): |
|
case (APPLY_IDENTITY): |
|
return 1.0; |
|
} |
|
} |
|
/** |
|
* Manually recalculates the state of the transform when the matrix |
|
* changes too much to predict the effects on the state. |
|
* The following table specifies what the various settings of the |
|
* state field say about the values of the corresponding matrix |
|
* element fields. |
|
* Note that the rules governing the SCALE fields are slightly |
|
* different depending on whether the SHEAR flag is also set. |
|
* <pre> |
|
* SCALE SHEAR TRANSLATE |
|
* m00/m11 m01/m10 m02/m12 |
|
* |
|
* IDENTITY 1.0 0.0 0.0 |
|
* TRANSLATE (TR) 1.0 0.0 not both 0.0 |
|
* SCALE (SC) not both 1.0 0.0 0.0 |
|
* TR | SC not both 1.0 0.0 not both 0.0 |
|
* SHEAR (SH) 0.0 not both 0.0 0.0 |
|
* TR | SH 0.0 not both 0.0 not both 0.0 |
|
* SC | SH not both 0.0 not both 0.0 0.0 |
|
* TR | SC | SH not both 0.0 not both 0.0 not both 0.0 |
|
* </pre> |
|
*/ |
|
void updateState() { |
|
if (m01 == 0.0 && m10 == 0.0) { |
|
if (m00 == 1.0 && m11 == 1.0) { |
|
if (m02 == 0.0 && m12 == 0.0) { |
|
state = APPLY_IDENTITY; |
|
type = TYPE_IDENTITY; |
|
} else { |
|
state = APPLY_TRANSLATE; |
|
type = TYPE_TRANSLATION; |
|
} |
|
} else { |
|
if (m02 == 0.0 && m12 == 0.0) { |
|
state = APPLY_SCALE; |
|
type = TYPE_UNKNOWN; |
|
} else { |
|
state = (APPLY_SCALE | APPLY_TRANSLATE); |
|
type = TYPE_UNKNOWN; |
|
} |
|
} |
|
} else { |
|
if (m00 == 0.0 && m11 == 0.0) { |
|
if (m02 == 0.0 && m12 == 0.0) { |
|
state = APPLY_SHEAR; |
|
type = TYPE_UNKNOWN; |
|
} else { |
|
state = (APPLY_SHEAR | APPLY_TRANSLATE); |
|
type = TYPE_UNKNOWN; |
|
} |
|
} else { |
|
if (m02 == 0.0 && m12 == 0.0) { |
|
state = (APPLY_SHEAR | APPLY_SCALE); |
|
type = TYPE_UNKNOWN; |
|
} else { |
|
state = (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE); |
|
type = TYPE_UNKNOWN; |
|
} |
|
} |
|
} |
|
} |
|
/* |
|
* Convenience method used internally to throw exceptions when |
|
* a case was forgotten in a switch statement. |
|
*/ |
|
private void stateError() { |
|
throw new InternalError("missing case in transform state switch"); |
|
} |
|
/** |
|
* Retrieves the 6 specifiable values in the 3x3 affine transformation |
|
* matrix and places them into an array of double precisions values. |
|
* The values are stored in the array as |
|
* { m00 m10 m01 m11 m02 m12 }. |
|
* An array of 4 doubles can also be specified, in which case only the |
|
* first four elements representing the non-transform |
|
* parts of the array are retrieved and the values are stored into |
|
* the array as { m00 m10 m01 m11 } |
|
* @param flatmatrix the double array used to store the returned |
|
* values. |
|
* @see #getScaleX |
|
* @see #getScaleY |
|
* @see #getShearX |
|
* @see #getShearY |
|
* @see #getTranslateX |
|
* @see #getTranslateY |
|
* @since 1.2 |
|
*/ |
|
public void getMatrix(double[] flatmatrix) { |
|
flatmatrix[0] = m00; |
|
flatmatrix[1] = m10; |
|
flatmatrix[2] = m01; |
|
flatmatrix[3] = m11; |
|
if (flatmatrix.length > 5) { |
|
flatmatrix[4] = m02; |
|
flatmatrix[5] = m12; |
|
} |
|
} |
|
/** |
|
* Returns the X coordinate scaling element (m00) of the 3x3 |
|
* affine transformation matrix. |
|
* @return a double value that is the X coordinate of the scaling |
|
* element of the affine transformation matrix. |
|
* @see #getMatrix |
|
* @since 1.2 |
|
*/ |
|
public double getScaleX() { |
|
return m00; |
|
} |
|
/** |
|
* Returns the Y coordinate scaling element (m11) of the 3x3 |
|
* affine transformation matrix. |
|
* @return a double value that is the Y coordinate of the scaling |
|
* element of the affine transformation matrix. |
|
* @see #getMatrix |
|
* @since 1.2 |
|
*/ |
|
public double getScaleY() { |
|
return m11; |
|
} |
|
/** |
|
* Returns the X coordinate shearing element (m01) of the 3x3 |
|
* affine transformation matrix. |
|
* @return a double value that is the X coordinate of the shearing |
|
* element of the affine transformation matrix. |
|
* @see #getMatrix |
|
* @since 1.2 |
|
*/ |
|
public double getShearX() { |
|
return m01; |
|
} |
|
/** |
|
* Returns the Y coordinate shearing element (m10) of the 3x3 |
|
* affine transformation matrix. |
|
* @return a double value that is the Y coordinate of the shearing |
|
* element of the affine transformation matrix. |
|
* @see #getMatrix |
|
* @since 1.2 |
|
*/ |
|
public double getShearY() { |
|
return m10; |
|
} |
|
/** |
|
* Returns the X coordinate of the translation element (m02) of the |
|
* 3x3 affine transformation matrix. |
|
* @return a double value that is the X coordinate of the translation |
|
* element of the affine transformation matrix. |
|
* @see #getMatrix |
|
* @since 1.2 |
|
*/ |
|
public double getTranslateX() { |
|
return m02; |
|
} |
|
/** |
|
* Returns the Y coordinate of the translation element (m12) of the |
|
* 3x3 affine transformation matrix. |
|
* @return a double value that is the Y coordinate of the translation |
|
* element of the affine transformation matrix. |
|
* @see #getMatrix |
|
* @since 1.2 |
|
*/ |
|
public double getTranslateY() { |
|
return m12; |
|
} |
|
/** |
|
* Concatenates this transform with a translation transformation. |
|
* This is equivalent to calling concatenate(T), where T is an |
|
* <code>AffineTransform</code> represented by the following matrix: |
|
* <pre> |
|
* [ 1 0 tx ] |
|
* [ 0 1 ty ] |
|
* [ 0 0 1 ] |
|
* </pre> |
|
* @param tx the distance by which coordinates are translated in the |
|
* X axis direction |
|
* @param ty the distance by which coordinates are translated in the |
|
* Y axis direction |
|
* @since 1.2 |
|
*/ |
|
public void translate(double tx, double ty) { |
|
switch (state) { |
|
default: |
|
stateError(); |
|
/* NOTREACHED */ |
|
return; |
|
case (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
m02 = tx * m00 + ty * m01 + m02; |
|
m12 = tx * m10 + ty * m11 + m12; |
|
if (m02 == 0.0 && m12 == 0.0) { |
|
state = APPLY_SHEAR | APPLY_SCALE; |
|
if (type != TYPE_UNKNOWN) { |
|
type -= TYPE_TRANSLATION; |
|
} |
|
} |
|
return; |
|
case (APPLY_SHEAR | APPLY_SCALE): |
|
m02 = tx * m00 + ty * m01; |
|
m12 = tx * m10 + ty * m11; |
|
if (m02 != 0.0 || m12 != 0.0) { |
|
state = APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE; |
|
type |= TYPE_TRANSLATION; |
|
} |
|
return; |
|
case (APPLY_SHEAR | APPLY_TRANSLATE): |
|
m02 = ty * m01 + m02; |
|
m12 = tx * m10 + m12; |
|
if (m02 == 0.0 && m12 == 0.0) { |
|
state = APPLY_SHEAR; |
|
if (type != TYPE_UNKNOWN) { |
|
type -= TYPE_TRANSLATION; |
|
} |
|
} |
|
return; |
|
case (APPLY_SHEAR): |
|
m02 = ty * m01; |
|
m12 = tx * m10; |
|
if (m02 != 0.0 || m12 != 0.0) { |
|
state = APPLY_SHEAR | APPLY_TRANSLATE; |
|
type |= TYPE_TRANSLATION; |
|
} |
|
return; |
|
case (APPLY_SCALE | APPLY_TRANSLATE): |
|
m02 = tx * m00 + m02; |
|
m12 = ty * m11 + m12; |
|
if (m02 == 0.0 && m12 == 0.0) { |
|
state = APPLY_SCALE; |
|
if (type != TYPE_UNKNOWN) { |
|
type -= TYPE_TRANSLATION; |
|
} |
|
} |
|
return; |
|
case (APPLY_SCALE): |
|
m02 = tx * m00; |
|
m12 = ty * m11; |
|
if (m02 != 0.0 || m12 != 0.0) { |
|
state = APPLY_SCALE | APPLY_TRANSLATE; |
|
type |= TYPE_TRANSLATION; |
|
} |
|
return; |
|
case (APPLY_TRANSLATE): |
|
m02 = tx + m02; |
|
m12 = ty + m12; |
|
if (m02 == 0.0 && m12 == 0.0) { |
|
state = APPLY_IDENTITY; |
|
type = TYPE_IDENTITY; |
|
} |
|
return; |
|
case (APPLY_IDENTITY): |
|
m02 = tx; |
|
m12 = ty; |
|
if (tx != 0.0 || ty != 0.0) { |
|
state = APPLY_TRANSLATE; |
|
type = TYPE_TRANSLATION; |
|
} |
|
return; |
|
} |
|
} |
|
// Utility methods to optimize rotate methods. |
|
// These tables translate the flags during predictable quadrant |
|
// rotations where the shear and scale values are swapped and negated. |
|
private static final int rot90conversion[] = { |
|
/* IDENTITY => */ APPLY_SHEAR, |
|
/* TRANSLATE (TR) => */ APPLY_SHEAR | APPLY_TRANSLATE, |
|
/* SCALE (SC) => */ APPLY_SHEAR, |
|
/* SC | TR => */ APPLY_SHEAR | APPLY_TRANSLATE, |
|
/* SHEAR (SH) => */ APPLY_SCALE, |
|
/* SH | TR => */ APPLY_SCALE | APPLY_TRANSLATE, |
|
/* SH | SC => */ APPLY_SHEAR | APPLY_SCALE, |
|
/* SH | SC | TR => */ APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE, |
|
}; |
|
private final void rotate90() { |
|
double M0 = m00; |
|
m00 = m01; |
|
m01 = -M0; |
|
M0 = m10; |
|
m10 = m11; |
|
m11 = -M0; |
|
int state = rot90conversion[this.state]; |
|
if ((state & (APPLY_SHEAR | APPLY_SCALE)) == APPLY_SCALE && |
|
m00 == 1.0 && m11 == 1.0) |
|
{ |
|
state -= APPLY_SCALE; |
|
} |
|
this.state = state; |
|
type = TYPE_UNKNOWN; |
|
} |
|
private final void rotate180() { |
|
m00 = -m00; |
|
m11 = -m11; |
|
int state = this.state; |
|
if ((state & (APPLY_SHEAR)) != 0) { |
|
// If there was a shear, then this rotation has no |
|
// effect on the state. |
|
m01 = -m01; |
|
m10 = -m10; |
|
} else { |
|
// No shear means the SCALE state may toggle when |
|
// m00 and m11 are negated. |
|
if (m00 == 1.0 && m11 == 1.0) { |
|
this.state = state & ~APPLY_SCALE; |
|
} else { |
|
this.state = state | APPLY_SCALE; |
|
} |
|
} |
|
type = TYPE_UNKNOWN; |
|
} |
|
private final void rotate270() { |
|
double M0 = m00; |
|
m00 = -m01; |
|
m01 = M0; |
|
M0 = m10; |
|
m10 = -m11; |
|
m11 = M0; |
|
int state = rot90conversion[this.state]; |
|
if ((state & (APPLY_SHEAR | APPLY_SCALE)) == APPLY_SCALE && |
|
m00 == 1.0 && m11 == 1.0) |
|
{ |
|
state -= APPLY_SCALE; |
|
} |
|
this.state = state; |
|
type = TYPE_UNKNOWN; |
|
} |
|
/** |
|
* Concatenates this transform with a rotation transformation. |
|
* This is equivalent to calling concatenate(R), where R is an |
|
* <code>AffineTransform</code> represented by the following matrix: |
|
* <pre> |
|
* [ cos(theta) -sin(theta) 0 ] |
|
* [ sin(theta) cos(theta) 0 ] |
|
* [ 0 0 1 ] |
|
* </pre> |
|
* Rotating by a positive angle theta rotates points on the positive |
|
* X axis toward the positive Y axis. |
|
* Note also the discussion of |
|
* <a href="#quadrantapproximation">Handling 90-Degree Rotations</a> |
|
* above. |
|
* @param theta the angle of rotation measured in radians |
|
* @since 1.2 |
|
*/ |
|
public void rotate(double theta) { |
|
double sin = Math.sin(theta); |
|
if (sin == 1.0) { |
|
rotate90(); |
|
} else if (sin == -1.0) { |
|
rotate270(); |
|
} else { |
|
double cos = Math.cos(theta); |
|
if (cos == -1.0) { |
|
rotate180(); |
|
} else if (cos != 1.0) { |
|
double M0, M1; |
|
M0 = m00; |
|
M1 = m01; |
|
m00 = cos * M0 + sin * M1; |
|
m01 = -sin * M0 + cos * M1; |
|
M0 = m10; |
|
M1 = m11; |
|
m10 = cos * M0 + sin * M1; |
|
m11 = -sin * M0 + cos * M1; |
|
updateState(); |
|
} |
|
} |
|
} |
|
/** |
|
* Concatenates this transform with a transform that rotates |
|
* coordinates around an anchor point. |
|
* This operation is equivalent to translating the coordinates so |
|
* that the anchor point is at the origin (S1), then rotating them |
|
* about the new origin (S2), and finally translating so that the |
|
* intermediate origin is restored to the coordinates of the original |
|
* anchor point (S3). |
|
* <p> |
|
* This operation is equivalent to the following sequence of calls: |
|
* <pre> |
|
* translate(anchorx, anchory); // S3: final translation |
|
* rotate(theta); // S2: rotate around anchor |
|
* translate(-anchorx, -anchory); // S1: translate anchor to origin |
|
* </pre> |
|
* Rotating by a positive angle theta rotates points on the positive |
|
* X axis toward the positive Y axis. |
|
* Note also the discussion of |
|
* <a href="#quadrantapproximation">Handling 90-Degree Rotations</a> |
|
* above. |
|
* |
|
* @param theta the angle of rotation measured in radians |
|
* @param anchorx the X coordinate of the rotation anchor point |
|
* @param anchory the Y coordinate of the rotation anchor point |
|
* @since 1.2 |
|
*/ |
|
public void rotate(double theta, double anchorx, double anchory) { |
|
// REMIND: Simple for now - optimize later |
|
translate(anchorx, anchory); |
|
rotate(theta); |
|
translate(-anchorx, -anchory); |
|
} |
|
/** |
|
* Concatenates this transform with a transform that rotates |
|
* coordinates according to a rotation vector. |
|
* All coordinates rotate about the origin by the same amount. |
|
* The amount of rotation is such that coordinates along the former |
|
* positive X axis will subsequently align with the vector pointing |
|
* from the origin to the specified vector coordinates. |
|
* If both <code>vecx</code> and <code>vecy</code> are 0.0, |
|
* no additional rotation is added to this transform. |
|
* This operation is equivalent to calling: |
|
* <pre> |
|
* rotate(Math.atan2(vecy, vecx)); |
|
* </pre> |
|
* |
|
* @param vecx the X coordinate of the rotation vector |
|
* @param vecy the Y coordinate of the rotation vector |
|
* @since 1.6 |
|
*/ |
|
public void rotate(double vecx, double vecy) { |
|
if (vecy == 0.0) { |
|
if (vecx < 0.0) { |
|
rotate180(); |
|
} |
|
// If vecx > 0.0 - no rotation |
|
// If vecx == 0.0 - undefined rotation - treat as no rotation |
|
} else if (vecx == 0.0) { |
|
if (vecy > 0.0) { |
|
rotate90(); |
|
} else { // vecy must be < 0.0 |
|
rotate270(); |
|
} |
|
} else { |
|
double len = Math.sqrt(vecx * vecx + vecy * vecy); |
|
double sin = vecy / len; |
|
double cos = vecx / len; |
|
double M0, M1; |
|
M0 = m00; |
|
M1 = m01; |
|
m00 = cos * M0 + sin * M1; |
|
m01 = -sin * M0 + cos * M1; |
|
M0 = m10; |
|
M1 = m11; |
|
m10 = cos * M0 + sin * M1; |
|
m11 = -sin * M0 + cos * M1; |
|
updateState(); |
|
} |
|
} |
|
/** |
|
* Concatenates this transform with a transform that rotates |
|
* coordinates around an anchor point according to a rotation |
|
* vector. |
|
* All coordinates rotate about the specified anchor coordinates |
|
* by the same amount. |
|
* The amount of rotation is such that coordinates along the former |
|
* positive X axis will subsequently align with the vector pointing |
|
* from the origin to the specified vector coordinates. |
|
* If both <code>vecx</code> and <code>vecy</code> are 0.0, |
|
* the transform is not modified in any way. |
|
* This method is equivalent to calling: |
|
* <pre> |
|
* rotate(Math.atan2(vecy, vecx), anchorx, anchory); |
|
* </pre> |
|
* |
|
* @param vecx the X coordinate of the rotation vector |
|
* @param vecy the Y coordinate of the rotation vector |
|
* @param anchorx the X coordinate of the rotation anchor point |
|
* @param anchory the Y coordinate of the rotation anchor point |
|
* @since 1.6 |
|
*/ |
|
public void rotate(double vecx, double vecy, |
|
double anchorx, double anchory) |
|
{ |
|
// REMIND: Simple for now - optimize later |
|
translate(anchorx, anchory); |
|
rotate(vecx, vecy); |
|
translate(-anchorx, -anchory); |
|
} |
|
/** |
|
* Concatenates this transform with a transform that rotates |
|
* coordinates by the specified number of quadrants. |
|
* This is equivalent to calling: |
|
* <pre> |
|
* rotate(numquadrants * Math.PI / 2.0); |
|
* </pre> |
|
* Rotating by a positive number of quadrants rotates points on |
|
* the positive X axis toward the positive Y axis. |
|
* @param numquadrants the number of 90 degree arcs to rotate by |
|
* @since 1.6 |
|
*/ |
|
public void quadrantRotate(int numquadrants) { |
|
switch (numquadrants & 3) { |
|
case 0: |
|
break; |
|
case 1: |
|
rotate90(); |
|
break; |
|
case 2: |
|
rotate180(); |
|
break; |
|
case 3: |
|
rotate270(); |
|
break; |
|
} |
|
} |
|
/** |
|
* Concatenates this transform with a transform that rotates |
|
* coordinates by the specified number of quadrants around |
|
* the specified anchor point. |
|
* This method is equivalent to calling: |
|
* <pre> |
|
* rotate(numquadrants * Math.PI / 2.0, anchorx, anchory); |
|
* </pre> |
|
* Rotating by a positive number of quadrants rotates points on |
|
* the positive X axis toward the positive Y axis. |
|
* |
|
* @param numquadrants the number of 90 degree arcs to rotate by |
|
* @param anchorx the X coordinate of the rotation anchor point |
|
* @param anchory the Y coordinate of the rotation anchor point |
|
* @since 1.6 |
|
*/ |
|
public void quadrantRotate(int numquadrants, |
|
double anchorx, double anchory) |
|
{ |
|
switch (numquadrants & 3) { |
|
case 0: |
|
return; |
|
case 1: |
|
m02 += anchorx * (m00 - m01) + anchory * (m01 + m00); |
|
m12 += anchorx * (m10 - m11) + anchory * (m11 + m10); |
|
rotate90(); |
|
break; |
|
case 2: |
|
m02 += anchorx * (m00 + m00) + anchory * (m01 + m01); |
|
m12 += anchorx * (m10 + m10) + anchory * (m11 + m11); |
|
rotate180(); |
|
break; |
|
case 3: |
|
m02 += anchorx * (m00 + m01) + anchory * (m01 - m00); |
|
m12 += anchorx * (m10 + m11) + anchory * (m11 - m10); |
|
rotate270(); |
|
break; |
|
} |
|
if (m02 == 0.0 && m12 == 0.0) { |
|
state &= ~APPLY_TRANSLATE; |
|
} else { |
|
state |= APPLY_TRANSLATE; |
|
} |
|
} |
|
/** |
|
* Concatenates this transform with a scaling transformation. |
|
* This is equivalent to calling concatenate(S), where S is an |
|
* <code>AffineTransform</code> represented by the following matrix: |
|
* <pre> |
|
* [ sx 0 0 ] |
|
* [ 0 sy 0 ] |
|
* [ 0 0 1 ] |
|
* </pre> |
|
* @param sx the factor by which coordinates are scaled along the |
|
* X axis direction |
|
* @param sy the factor by which coordinates are scaled along the |
|
* Y axis direction |
|
* @since 1.2 |
|
*/ |
|
@SuppressWarnings("fallthrough") |
|
public void scale(double sx, double sy) { |
|
int state = this.state; |
|
switch (state) { |
|
default: |
|
stateError(); |
|
/* NOTREACHED */ |
|
case (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
case (APPLY_SHEAR | APPLY_SCALE): |
|
m00 *= sx; |
|
m11 *= sy; |
|
/* NOBREAK */ |
|
case (APPLY_SHEAR | APPLY_TRANSLATE): |
|
case (APPLY_SHEAR): |
|
m01 *= sy; |
|
m10 *= sx; |
|
if (m01 == 0 && m10 == 0) { |
|
state &= APPLY_TRANSLATE; |
|
if (m00 == 1.0 && m11 == 1.0) { |
|
this.type = (state == APPLY_IDENTITY |
|
? TYPE_IDENTITY |
|
: TYPE_TRANSLATION); |
|
} else { |
|
state |= APPLY_SCALE; |
|
this.type = TYPE_UNKNOWN; |
|
} |
|
this.state = state; |
|
} |
|
return; |
|
case (APPLY_SCALE | APPLY_TRANSLATE): |
|
case (APPLY_SCALE): |
|
m00 *= sx; |
|
m11 *= sy; |
|
if (m00 == 1.0 && m11 == 1.0) { |
|
this.state = (state &= APPLY_TRANSLATE); |
|
this.type = (state == APPLY_IDENTITY |
|
? TYPE_IDENTITY |
|
: TYPE_TRANSLATION); |
|
} else { |
|
this.type = TYPE_UNKNOWN; |
|
} |
|
return; |
|
case (APPLY_TRANSLATE): |
|
case (APPLY_IDENTITY): |
|
m00 = sx; |
|
m11 = sy; |
|
if (sx != 1.0 || sy != 1.0) { |
|
this.state = state | APPLY_SCALE; |
|
this.type = TYPE_UNKNOWN; |
|
} |
|
return; |
|
} |
|
} |
|
/** |
|
* Concatenates this transform with a shearing transformation. |
|
* This is equivalent to calling concatenate(SH), where SH is an |
|
* <code>AffineTransform</code> represented by the following matrix: |
|
* <pre> |
|
* [ 1 shx 0 ] |
|
* [ shy 1 0 ] |
|
* [ 0 0 1 ] |
|
* </pre> |
|
* @param shx the multiplier by which coordinates are shifted in the |
|
* direction of the positive X axis as a factor of their Y coordinate |
|
* @param shy the multiplier by which coordinates are shifted in the |
|
* direction of the positive Y axis as a factor of their X coordinate |
|
* @since 1.2 |
|
*/ |
|
public void shear(double shx, double shy) { |
|
int state = this.state; |
|
switch (state) { |
|
default: |
|
stateError(); |
|
/* NOTREACHED */ |
|
return; |
|
case (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
case (APPLY_SHEAR | APPLY_SCALE): |
|
double M0, M1; |
|
M0 = m00; |
|
M1 = m01; |
|
m00 = M0 + M1 * shy; |
|
m01 = M0 * shx + M1; |
|
M0 = m10; |
|
M1 = m11; |
|
m10 = M0 + M1 * shy; |
|
m11 = M0 * shx + M1; |
|
updateState(); |
|
return; |
|
case (APPLY_SHEAR | APPLY_TRANSLATE): |
|
case (APPLY_SHEAR): |
|
m00 = m01 * shy; |
|
m11 = m10 * shx; |
|
if (m00 != 0.0 || m11 != 0.0) { |
|
this.state = state | APPLY_SCALE; |
|
} |
|
this.type = TYPE_UNKNOWN; |
|
return; |
|
case (APPLY_SCALE | APPLY_TRANSLATE): |
|
case (APPLY_SCALE): |
|
m01 = m00 * shx; |
|
m10 = m11 * shy; |
|
if (m01 != 0.0 || m10 != 0.0) { |
|
this.state = state | APPLY_SHEAR; |
|
} |
|
this.type = TYPE_UNKNOWN; |
|
return; |
|
case (APPLY_TRANSLATE): |
|
case (APPLY_IDENTITY): |
|
m01 = shx; |
|
m10 = shy; |
|
if (m01 != 0.0 || m10 != 0.0) { |
|
this.state = state | APPLY_SCALE | APPLY_SHEAR; |
|
this.type = TYPE_UNKNOWN; |
|
} |
|
return; |
|
} |
|
} |
|
/** |
|
* Resets this transform to the Identity transform. |
|
* @since 1.2 |
|
*/ |
|
public void setToIdentity() { |
|
m00 = m11 = 1.0; |
|
m10 = m01 = m02 = m12 = 0.0; |
|
state = APPLY_IDENTITY; |
|
type = TYPE_IDENTITY; |
|
} |
|
/** |
|
* Sets this transform to a translation transformation. |
|
* The matrix representing this transform becomes: |
|
* <pre> |
|
* [ 1 0 tx ] |
|
* [ 0 1 ty ] |
|
* [ 0 0 1 ] |
|
* </pre> |
|
* @param tx the distance by which coordinates are translated in the |
|
* X axis direction |
|
* @param ty the distance by which coordinates are translated in the |
|
* Y axis direction |
|
* @since 1.2 |
|
*/ |
|
public void setToTranslation(double tx, double ty) { |
|
m00 = 1.0; |
|
m10 = 0.0; |
|
m01 = 0.0; |
|
m11 = 1.0; |
|
m02 = tx; |
|
m12 = ty; |
|
if (tx != 0.0 || ty != 0.0) { |
|
state = APPLY_TRANSLATE; |
|
type = TYPE_TRANSLATION; |
|
} else { |
|
state = APPLY_IDENTITY; |
|
type = TYPE_IDENTITY; |
|
} |
|
} |
|
/** |
|
* Sets this transform to a rotation transformation. |
|
* The matrix representing this transform becomes: |
|
* <pre> |
|
* [ cos(theta) -sin(theta) 0 ] |
|
* [ sin(theta) cos(theta) 0 ] |
|
* [ 0 0 1 ] |
|
* </pre> |
|
* Rotating by a positive angle theta rotates points on the positive |
|
* X axis toward the positive Y axis. |
|
* Note also the discussion of |
|
* <a href="#quadrantapproximation">Handling 90-Degree Rotations</a> |
|
* above. |
|
* @param theta the angle of rotation measured in radians |
|
* @since 1.2 |
|
*/ |
|
public void setToRotation(double theta) { |
|
double sin = Math.sin(theta); |
|
double cos; |
|
if (sin == 1.0 || sin == -1.0) { |
|
cos = 0.0; |
|
state = APPLY_SHEAR; |
|
type = TYPE_QUADRANT_ROTATION; |
|
} else { |
|
cos = Math.cos(theta); |
|
if (cos == -1.0) { |
|
sin = 0.0; |
|
state = APPLY_SCALE; |
|
type = TYPE_QUADRANT_ROTATION; |
|
} else if (cos == 1.0) { |
|
sin = 0.0; |
|
state = APPLY_IDENTITY; |
|
type = TYPE_IDENTITY; |
|
} else { |
|
state = APPLY_SHEAR | APPLY_SCALE; |
|
type = TYPE_GENERAL_ROTATION; |
|
} |
|
} |
|
m00 = cos; |
|
m10 = sin; |
|
m01 = -sin; |
|
m11 = cos; |
|
m02 = 0.0; |
|
m12 = 0.0; |
|
} |
|
/** |
|
* Sets this transform to a translated rotation transformation. |
|
* This operation is equivalent to translating the coordinates so |
|
* that the anchor point is at the origin (S1), then rotating them |
|
* about the new origin (S2), and finally translating so that the |
|
* intermediate origin is restored to the coordinates of the original |
|
* anchor point (S3). |
|
* <p> |
|
* This operation is equivalent to the following sequence of calls: |
|
* <pre> |
|
* setToTranslation(anchorx, anchory); // S3: final translation |
|
* rotate(theta); // S2: rotate around anchor |
|
* translate(-anchorx, -anchory); // S1: translate anchor to origin |
|
* </pre> |
|
* The matrix representing this transform becomes: |
|
* <pre> |
|
* [ cos(theta) -sin(theta) x-x*cos+y*sin ] |
|
* [ sin(theta) cos(theta) y-x*sin-y*cos ] |
|
* [ 0 0 1 ] |
|
* </pre> |
|
* Rotating by a positive angle theta rotates points on the positive |
|
* X axis toward the positive Y axis. |
|
* Note also the discussion of |
|
* <a href="#quadrantapproximation">Handling 90-Degree Rotations</a> |
|
* above. |
|
* |
|
* @param theta the angle of rotation measured in radians |
|
* @param anchorx the X coordinate of the rotation anchor point |
|
* @param anchory the Y coordinate of the rotation anchor point |
|
* @since 1.2 |
|
*/ |
|
public void setToRotation(double theta, double anchorx, double anchory) { |
|
setToRotation(theta); |
|
double sin = m10; |
|
double oneMinusCos = 1.0 - m00; |
|
m02 = anchorx * oneMinusCos + anchory * sin; |
|
m12 = anchory * oneMinusCos - anchorx * sin; |
|
if (m02 != 0.0 || m12 != 0.0) { |
|
state |= APPLY_TRANSLATE; |
|
type |= TYPE_TRANSLATION; |
|
} |
|
} |
|
/** |
|
* Sets this transform to a rotation transformation that rotates |
|
* coordinates according to a rotation vector. |
|
* All coordinates rotate about the origin by the same amount. |
|
* The amount of rotation is such that coordinates along the former |
|
* positive X axis will subsequently align with the vector pointing |
|
* from the origin to the specified vector coordinates. |
|
* If both <code>vecx</code> and <code>vecy</code> are 0.0, |
|
* the transform is set to an identity transform. |
|
* This operation is equivalent to calling: |
|
* <pre> |
|
* setToRotation(Math.atan2(vecy, vecx)); |
|
* </pre> |
|
* |
|
* @param vecx the X coordinate of the rotation vector |
|
* @param vecy the Y coordinate of the rotation vector |
|
* @since 1.6 |
|
*/ |
|
public void setToRotation(double vecx, double vecy) { |
|
double sin, cos; |
|
if (vecy == 0) { |
|
sin = 0.0; |
|
if (vecx < 0.0) { |
|
cos = -1.0; |
|
state = APPLY_SCALE; |
|
type = TYPE_QUADRANT_ROTATION; |
|
} else { |
|
cos = 1.0; |
|
state = APPLY_IDENTITY; |
|
type = TYPE_IDENTITY; |
|
} |
|
} else if (vecx == 0) { |
|
cos = 0.0; |
|
sin = (vecy > 0.0) ? 1.0 : -1.0; |
|
state = APPLY_SHEAR; |
|
type = TYPE_QUADRANT_ROTATION; |
|
} else { |
|
double len = Math.sqrt(vecx * vecx + vecy * vecy); |
|
cos = vecx / len; |
|
sin = vecy / len; |
|
state = APPLY_SHEAR | APPLY_SCALE; |
|
type = TYPE_GENERAL_ROTATION; |
|
} |
|
m00 = cos; |
|
m10 = sin; |
|
m01 = -sin; |
|
m11 = cos; |
|
m02 = 0.0; |
|
m12 = 0.0; |
|
} |
|
/** |
|
* Sets this transform to a rotation transformation that rotates |
|
* coordinates around an anchor point according to a rotation |
|
* vector. |
|
* All coordinates rotate about the specified anchor coordinates |
|
* by the same amount. |
|
* The amount of rotation is such that coordinates along the former |
|
* positive X axis will subsequently align with the vector pointing |
|
* from the origin to the specified vector coordinates. |
|
* If both <code>vecx</code> and <code>vecy</code> are 0.0, |
|
* the transform is set to an identity transform. |
|
* This operation is equivalent to calling: |
|
* <pre> |
|
* setToTranslation(Math.atan2(vecy, vecx), anchorx, anchory); |
|
* </pre> |
|
* |
|
* @param vecx the X coordinate of the rotation vector |
|
* @param vecy the Y coordinate of the rotation vector |
|
* @param anchorx the X coordinate of the rotation anchor point |
|
* @param anchory the Y coordinate of the rotation anchor point |
|
* @since 1.6 |
|
*/ |
|
public void setToRotation(double vecx, double vecy, |
|
double anchorx, double anchory) |
|
{ |
|
setToRotation(vecx, vecy); |
|
double sin = m10; |
|
double oneMinusCos = 1.0 - m00; |
|
m02 = anchorx * oneMinusCos + anchory * sin; |
|
m12 = anchory * oneMinusCos - anchorx * sin; |
|
if (m02 != 0.0 || m12 != 0.0) { |
|
state |= APPLY_TRANSLATE; |
|
type |= TYPE_TRANSLATION; |
|
} |
|
} |
|
/** |
|
* Sets this transform to a rotation transformation that rotates |
|
* coordinates by the specified number of quadrants. |
|
* This operation is equivalent to calling: |
|
* <pre> |
|
* setToRotation(numquadrants * Math.PI / 2.0); |
|
* </pre> |
|
* Rotating by a positive number of quadrants rotates points on |
|
* the positive X axis toward the positive Y axis. |
|
* @param numquadrants the number of 90 degree arcs to rotate by |
|
* @since 1.6 |
|
*/ |
|
public void setToQuadrantRotation(int numquadrants) { |
|
switch (numquadrants & 3) { |
|
case 0: |
|
m00 = 1.0; |
|
m10 = 0.0; |
|
m01 = 0.0; |
|
m11 = 1.0; |
|
m02 = 0.0; |
|
m12 = 0.0; |
|
state = APPLY_IDENTITY; |
|
type = TYPE_IDENTITY; |
|
break; |
|
case 1: |
|
m00 = 0.0; |
|
m10 = 1.0; |
|
m01 = -1.0; |
|
m11 = 0.0; |
|
m02 = 0.0; |
|
m12 = 0.0; |
|
state = APPLY_SHEAR; |
|
type = TYPE_QUADRANT_ROTATION; |
|
break; |
|
case 2: |
|
m00 = -1.0; |
|
m10 = 0.0; |
|
m01 = 0.0; |
|
m11 = -1.0; |
|
m02 = 0.0; |
|
m12 = 0.0; |
|
state = APPLY_SCALE; |
|
type = TYPE_QUADRANT_ROTATION; |
|
break; |
|
case 3: |
|
m00 = 0.0; |
|
m10 = -1.0; |
|
m01 = 1.0; |
|
m11 = 0.0; |
|
m02 = 0.0; |
|
m12 = 0.0; |
|
state = APPLY_SHEAR; |
|
type = TYPE_QUADRANT_ROTATION; |
|
break; |
|
} |
|
} |
|
/** |
|
* Sets this transform to a translated rotation transformation |
|
* that rotates coordinates by the specified number of quadrants |
|
* around the specified anchor point. |
|
* This operation is equivalent to calling: |
|
* <pre> |
|
* setToRotation(numquadrants * Math.PI / 2.0, anchorx, anchory); |
|
* </pre> |
|
* Rotating by a positive number of quadrants rotates points on |
|
* the positive X axis toward the positive Y axis. |
|
* |
|
* @param numquadrants the number of 90 degree arcs to rotate by |
|
* @param anchorx the X coordinate of the rotation anchor point |
|
* @param anchory the Y coordinate of the rotation anchor point |
|
* @since 1.6 |
|
*/ |
|
public void setToQuadrantRotation(int numquadrants, |
|
double anchorx, double anchory) |
|
{ |
|
switch (numquadrants & 3) { |
|
case 0: |
|
m00 = 1.0; |
|
m10 = 0.0; |
|
m01 = 0.0; |
|
m11 = 1.0; |
|
m02 = 0.0; |
|
m12 = 0.0; |
|
state = APPLY_IDENTITY; |
|
type = TYPE_IDENTITY; |
|
break; |
|
case 1: |
|
m00 = 0.0; |
|
m10 = 1.0; |
|
m01 = -1.0; |
|
m11 = 0.0; |
|
m02 = anchorx + anchory; |
|
m12 = anchory - anchorx; |
|
if (m02 == 0.0 && m12 == 0.0) { |
|
state = APPLY_SHEAR; |
|
type = TYPE_QUADRANT_ROTATION; |
|
} else { |
|
state = APPLY_SHEAR | APPLY_TRANSLATE; |
|
type = TYPE_QUADRANT_ROTATION | TYPE_TRANSLATION; |
|
} |
|
break; |
|
case 2: |
|
m00 = -1.0; |
|
m10 = 0.0; |
|
m01 = 0.0; |
|
m11 = -1.0; |
|
m02 = anchorx + anchorx; |
|
m12 = anchory + anchory; |
|
if (m02 == 0.0 && m12 == 0.0) { |
|
state = APPLY_SCALE; |
|
type = TYPE_QUADRANT_ROTATION; |
|
} else { |
|
state = APPLY_SCALE | APPLY_TRANSLATE; |
|
type = TYPE_QUADRANT_ROTATION | TYPE_TRANSLATION; |
|
} |
|
break; |
|
case 3: |
|
m00 = 0.0; |
|
m10 = -1.0; |
|
m01 = 1.0; |
|
m11 = 0.0; |
|
m02 = anchorx - anchory; |
|
m12 = anchory + anchorx; |
|
if (m02 == 0.0 && m12 == 0.0) { |
|
state = APPLY_SHEAR; |
|
type = TYPE_QUADRANT_ROTATION; |
|
} else { |
|
state = APPLY_SHEAR | APPLY_TRANSLATE; |
|
type = TYPE_QUADRANT_ROTATION | TYPE_TRANSLATION; |
|
} |
|
break; |
|
} |
|
} |
|
/** |
|
* Sets this transform to a scaling transformation. |
|
* The matrix representing this transform becomes: |
|
* <pre> |
|
* [ sx 0 0 ] |
|
* [ 0 sy 0 ] |
|
* [ 0 0 1 ] |
|
* </pre> |
|
* @param sx the factor by which coordinates are scaled along the |
|
* X axis direction |
|
* @param sy the factor by which coordinates are scaled along the |
|
* Y axis direction |
|
* @since 1.2 |
|
*/ |
|
public void setToScale(double sx, double sy) { |
|
m00 = sx; |
|
m10 = 0.0; |
|
m01 = 0.0; |
|
m11 = sy; |
|
m02 = 0.0; |
|
m12 = 0.0; |
|
if (sx != 1.0 || sy != 1.0) { |
|
state = APPLY_SCALE; |
|
type = TYPE_UNKNOWN; |
|
} else { |
|
state = APPLY_IDENTITY; |
|
type = TYPE_IDENTITY; |
|
} |
|
} |
|
/** |
|
* Sets this transform to a shearing transformation. |
|
* The matrix representing this transform becomes: |
|
* <pre> |
|
* [ 1 shx 0 ] |
|
* [ shy 1 0 ] |
|
* [ 0 0 1 ] |
|
* </pre> |
|
* @param shx the multiplier by which coordinates are shifted in the |
|
* direction of the positive X axis as a factor of their Y coordinate |
|
* @param shy the multiplier by which coordinates are shifted in the |
|
* direction of the positive Y axis as a factor of their X coordinate |
|
* @since 1.2 |
|
*/ |
|
public void setToShear(double shx, double shy) { |
|
m00 = 1.0; |
|
m01 = shx; |
|
m10 = shy; |
|
m11 = 1.0; |
|
m02 = 0.0; |
|
m12 = 0.0; |
|
if (shx != 0.0 || shy != 0.0) { |
|
state = (APPLY_SHEAR | APPLY_SCALE); |
|
type = TYPE_UNKNOWN; |
|
} else { |
|
state = APPLY_IDENTITY; |
|
type = TYPE_IDENTITY; |
|
} |
|
} |
|
/** |
|
* Sets this transform to a copy of the transform in the specified |
|
* <code>AffineTransform</code> object. |
|
* @param Tx the <code>AffineTransform</code> object from which to |
|
* copy the transform |
|
* @since 1.2 |
|
*/ |
|
public void setTransform(AffineTransform Tx) { |
|
this.m00 = Tx.m00; |
|
this.m10 = Tx.m10; |
|
this.m01 = Tx.m01; |
|
this.m11 = Tx.m11; |
|
this.m02 = Tx.m02; |
|
this.m12 = Tx.m12; |
|
this.state = Tx.state; |
|
this.type = Tx.type; |
|
} |
|
/** |
|
* Sets this transform to the matrix specified by the 6 |
|
* double precision values. |
|
* |
|
* @param m00 the X coordinate scaling element of the 3x3 matrix |
|
* @param m10 the Y coordinate shearing element of the 3x3 matrix |
|
* @param m01 the X coordinate shearing element of the 3x3 matrix |
|
* @param m11 the Y coordinate scaling element of the 3x3 matrix |
|
* @param m02 the X coordinate translation element of the 3x3 matrix |
|
* @param m12 the Y coordinate translation element of the 3x3 matrix |
|
* @since 1.2 |
|
*/ |
|
public void setTransform(double m00, double m10, |
|
double m01, double m11, |
|
double m02, double m12) { |
|
this.m00 = m00; |
|
this.m10 = m10; |
|
this.m01 = m01; |
|
this.m11 = m11; |
|
this.m02 = m02; |
|
this.m12 = m12; |
|
updateState(); |
|
} |
|
/** |
|
* Concatenates an <code>AffineTransform</code> <code>Tx</code> to |
|
* this <code>AffineTransform</code> Cx in the most commonly useful |
|
* way to provide a new user space |
|
* that is mapped to the former user space by <code>Tx</code>. |
|
* Cx is updated to perform the combined transformation. |
|
* Transforming a point p by the updated transform Cx' is |
|
* equivalent to first transforming p by <code>Tx</code> and then |
|
* transforming the result by the original transform Cx like this: |
|
* Cx'(p) = Cx(Tx(p)) |
|
* In matrix notation, if this transform Cx is |
|
* represented by the matrix [this] and <code>Tx</code> is represented |
|
* by the matrix [Tx] then this method does the following: |
|
* <pre> |
|
* [this] = [this] x [Tx] |
|
* </pre> |
|
* @param Tx the <code>AffineTransform</code> object to be |
|
* concatenated with this <code>AffineTransform</code> object. |
|
* @see #preConcatenate |
|
* @since 1.2 |
|
*/ |
|
@SuppressWarnings("fallthrough") |
|
public void concatenate(AffineTransform Tx) { |
|
double M0, M1; |
|
double T00, T01, T10, T11; |
|
double T02, T12; |
|
int mystate = state; |
|
int txstate = Tx.state; |
|
switch ((txstate << HI_SHIFT) | mystate) { |
|
/* ---------- Tx == IDENTITY cases ---------- */ |
|
case (HI_IDENTITY | APPLY_IDENTITY): |
|
case (HI_IDENTITY | APPLY_TRANSLATE): |
|
case (HI_IDENTITY | APPLY_SCALE): |
|
case (HI_IDENTITY | APPLY_SCALE | APPLY_TRANSLATE): |
|
case (HI_IDENTITY | APPLY_SHEAR): |
|
case (HI_IDENTITY | APPLY_SHEAR | APPLY_TRANSLATE): |
|
case (HI_IDENTITY | APPLY_SHEAR | APPLY_SCALE): |
|
case (HI_IDENTITY | APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
return; |
|
/* ---------- this == IDENTITY cases ---------- */ |
|
case (HI_SHEAR | HI_SCALE | HI_TRANSLATE | APPLY_IDENTITY): |
|
m01 = Tx.m01; |
|
m10 = Tx.m10; |
|
/* NOBREAK */ |
|
case (HI_SCALE | HI_TRANSLATE | APPLY_IDENTITY): |
|
m00 = Tx.m00; |
|
m11 = Tx.m11; |
|
/* NOBREAK */ |
|
case (HI_TRANSLATE | APPLY_IDENTITY): |
|
m02 = Tx.m02; |
|
m12 = Tx.m12; |
|
state = txstate; |
|
type = Tx.type; |
|
return; |
|
case (HI_SHEAR | HI_SCALE | APPLY_IDENTITY): |
|
m01 = Tx.m01; |
|
m10 = Tx.m10; |
|
/* NOBREAK */ |
|
case (HI_SCALE | APPLY_IDENTITY): |
|
m00 = Tx.m00; |
|
m11 = Tx.m11; |
|
state = txstate; |
|
type = Tx.type; |
|
return; |
|
case (HI_SHEAR | HI_TRANSLATE | APPLY_IDENTITY): |
|
m02 = Tx.m02; |
|
m12 = Tx.m12; |
|
/* NOBREAK */ |
|
case (HI_SHEAR | APPLY_IDENTITY): |
|
m01 = Tx.m01; |
|
m10 = Tx.m10; |
|
m00 = m11 = 0.0; |
|
state = txstate; |
|
type = Tx.type; |
|
return; |
|
/* ---------- Tx == TRANSLATE cases ---------- */ |
|
case (HI_TRANSLATE | APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
case (HI_TRANSLATE | APPLY_SHEAR | APPLY_SCALE): |
|
case (HI_TRANSLATE | APPLY_SHEAR | APPLY_TRANSLATE): |
|
case (HI_TRANSLATE | APPLY_SHEAR): |
|
case (HI_TRANSLATE | APPLY_SCALE | APPLY_TRANSLATE): |
|
case (HI_TRANSLATE | APPLY_SCALE): |
|
case (HI_TRANSLATE | APPLY_TRANSLATE): |
|
translate(Tx.m02, Tx.m12); |
|
return; |
|
/* ---------- Tx == SCALE cases ---------- */ |
|
case (HI_SCALE | APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
case (HI_SCALE | APPLY_SHEAR | APPLY_SCALE): |
|
case (HI_SCALE | APPLY_SHEAR | APPLY_TRANSLATE): |
|
case (HI_SCALE | APPLY_SHEAR): |
|
case (HI_SCALE | APPLY_SCALE | APPLY_TRANSLATE): |
|
case (HI_SCALE | APPLY_SCALE): |
|
case (HI_SCALE | APPLY_TRANSLATE): |
|
scale(Tx.m00, Tx.m11); |
|
return; |
|
/* ---------- Tx == SHEAR cases ---------- */ |
|
case (HI_SHEAR | APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
case (HI_SHEAR | APPLY_SHEAR | APPLY_SCALE): |
|
T01 = Tx.m01; T10 = Tx.m10; |
|
M0 = m00; |
|
m00 = m01 * T10; |
|
m01 = M0 * T01; |
|
M0 = m10; |
|
m10 = m11 * T10; |
|
m11 = M0 * T01; |
|
type = TYPE_UNKNOWN; |
|
return; |
|
case (HI_SHEAR | APPLY_SHEAR | APPLY_TRANSLATE): |
|
case (HI_SHEAR | APPLY_SHEAR): |
|
m00 = m01 * Tx.m10; |
|
m01 = 0.0; |
|
m11 = m10 * Tx.m01; |
|
m10 = 0.0; |
|
state = mystate ^ (APPLY_SHEAR | APPLY_SCALE); |
|
type = TYPE_UNKNOWN; |
|
return; |
|
case (HI_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
case (HI_SHEAR | APPLY_SCALE): |
|
m01 = m00 * Tx.m01; |
|
m00 = 0.0; |
|
m10 = m11 * Tx.m10; |
|
m11 = 0.0; |
|
state = mystate ^ (APPLY_SHEAR | APPLY_SCALE); |
|
type = TYPE_UNKNOWN; |
|
return; |
|
case (HI_SHEAR | APPLY_TRANSLATE): |
|
m00 = 0.0; |
|
m01 = Tx.m01; |
|
m10 = Tx.m10; |
|
m11 = 0.0; |
|
state = APPLY_TRANSLATE | APPLY_SHEAR; |
|
type = TYPE_UNKNOWN; |
|
return; |
|
} |
|
// If Tx has more than one attribute, it is not worth optimizing |
|
// all of those cases... |
|
T00 = Tx.m00; T01 = Tx.m01; T02 = Tx.m02; |
|
T10 = Tx.m10; T11 = Tx.m11; T12 = Tx.m12; |
|
switch (mystate) { |
|
default: |
|
stateError(); |
|
/* NOTREACHED */ |
|
case (APPLY_SHEAR | APPLY_SCALE): |
|
state = mystate | txstate; |
|
/* NOBREAK */ |
|
case (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
M0 = m00; |
|
M1 = m01; |
|
m00 = T00 * M0 + T10 * M1; |
|
m01 = T01 * M0 + T11 * M1; |
|
m02 += T02 * M0 + T12 * M1; |
|
M0 = m10; |
|
M1 = m11; |
|
m10 = T00 * M0 + T10 * M1; |
|
m11 = T01 * M0 + T11 * M1; |
|
m12 += T02 * M0 + T12 * M1; |
|
type = TYPE_UNKNOWN; |
|
return; |
|
case (APPLY_SHEAR | APPLY_TRANSLATE): |
|
case (APPLY_SHEAR): |
|
M0 = m01; |
|
m00 = T10 * M0; |
|
m01 = T11 * M0; |
|
m02 += T12 * M0; |
|
M0 = m10; |
|
m10 = T00 * M0; |
|
m11 = T01 * M0; |
|
m12 += T02 * M0; |
|
break; |
|
case (APPLY_SCALE | APPLY_TRANSLATE): |
|
case (APPLY_SCALE): |
|
M0 = m00; |
|
m00 = T00 * M0; |
|
m01 = T01 * M0; |
|
m02 += T02 * M0; |
|
M0 = m11; |
|
m10 = T10 * M0; |
|
m11 = T11 * M0; |
|
m12 += T12 * M0; |
|
break; |
|
case (APPLY_TRANSLATE): |
|
m00 = T00; |
|
m01 = T01; |
|
m02 += T02; |
|
m10 = T10; |
|
m11 = T11; |
|
m12 += T12; |
|
state = txstate | APPLY_TRANSLATE; |
|
type = TYPE_UNKNOWN; |
|
return; |
|
} |
|
updateState(); |
|
} |
|
/** |
|
* Concatenates an <code>AffineTransform</code> <code>Tx</code> to |
|
* this <code>AffineTransform</code> Cx |
|
* in a less commonly used way such that <code>Tx</code> modifies the |
|
* coordinate transformation relative to the absolute pixel |
|
* space rather than relative to the existing user space. |
|
* Cx is updated to perform the combined transformation. |
|
* Transforming a point p by the updated transform Cx' is |
|
* equivalent to first transforming p by the original transform |
|
* Cx and then transforming the result by |
|
* <code>Tx</code> like this: |
|
* Cx'(p) = Tx(Cx(p)) |
|
* In matrix notation, if this transform Cx |
|
* is represented by the matrix [this] and <code>Tx</code> is |
|
* represented by the matrix [Tx] then this method does the |
|
* following: |
|
* <pre> |
|
* [this] = [Tx] x [this] |
|
* </pre> |
|
* @param Tx the <code>AffineTransform</code> object to be |
|
* concatenated with this <code>AffineTransform</code> object. |
|
* @see #concatenate |
|
* @since 1.2 |
|
*/ |
|
@SuppressWarnings("fallthrough") |
|
public void preConcatenate(AffineTransform Tx) { |
|
double M0, M1; |
|
double T00, T01, T10, T11; |
|
double T02, T12; |
|
int mystate = state; |
|
int txstate = Tx.state; |
|
switch ((txstate << HI_SHIFT) | mystate) { |
|
case (HI_IDENTITY | APPLY_IDENTITY): |
|
case (HI_IDENTITY | APPLY_TRANSLATE): |
|
case (HI_IDENTITY | APPLY_SCALE): |
|
case (HI_IDENTITY | APPLY_SCALE | APPLY_TRANSLATE): |
|
case (HI_IDENTITY | APPLY_SHEAR): |
|
case (HI_IDENTITY | APPLY_SHEAR | APPLY_TRANSLATE): |
|
case (HI_IDENTITY | APPLY_SHEAR | APPLY_SCALE): |
|
case (HI_IDENTITY | APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
// Tx is IDENTITY... |
|
return; |
|
case (HI_TRANSLATE | APPLY_IDENTITY): |
|
case (HI_TRANSLATE | APPLY_SCALE): |
|
case (HI_TRANSLATE | APPLY_SHEAR): |
|
case (HI_TRANSLATE | APPLY_SHEAR | APPLY_SCALE): |
|
// Tx is TRANSLATE, this has no TRANSLATE |
|
m02 = Tx.m02; |
|
m12 = Tx.m12; |
|
state = mystate | APPLY_TRANSLATE; |
|
type |= TYPE_TRANSLATION; |
|
return; |
|
case (HI_TRANSLATE | APPLY_TRANSLATE): |
|
case (HI_TRANSLATE | APPLY_SCALE | APPLY_TRANSLATE): |
|
case (HI_TRANSLATE | APPLY_SHEAR | APPLY_TRANSLATE): |
|
case (HI_TRANSLATE | APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
// Tx is TRANSLATE, this has one too |
|
m02 = m02 + Tx.m02; |
|
m12 = m12 + Tx.m12; |
|
return; |
|
case (HI_SCALE | APPLY_TRANSLATE): |
|
case (HI_SCALE | APPLY_IDENTITY): |
|
// Only these two existing states need a new state |
|
state = mystate | APPLY_SCALE; |
|
/* NOBREAK */ |
|
case (HI_SCALE | APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
case (HI_SCALE | APPLY_SHEAR | APPLY_SCALE): |
|
case (HI_SCALE | APPLY_SHEAR | APPLY_TRANSLATE): |
|
case (HI_SCALE | APPLY_SHEAR): |
|
case (HI_SCALE | APPLY_SCALE | APPLY_TRANSLATE): |
|
case (HI_SCALE | APPLY_SCALE): |
|
// Tx is SCALE, this is anything |
|
T00 = Tx.m00; |
|
T11 = Tx.m11; |
|
if ((mystate & APPLY_SHEAR) != 0) { |
|
m01 = m01 * T00; |
|
m10 = m10 * T11; |
|
if ((mystate & APPLY_SCALE) != 0) { |
|
m00 = m00 * T00; |
|
m11 = m11 * T11; |
|
} |
|
} else { |
|
m00 = m00 * T00; |
|
m11 = m11 * T11; |
|
} |
|
if ((mystate & APPLY_TRANSLATE) != 0) { |
|
m02 = m02 * T00; |
|
m12 = m12 * T11; |
|
} |
|
type = TYPE_UNKNOWN; |
|
return; |
|
case (HI_SHEAR | APPLY_SHEAR | APPLY_TRANSLATE): |
|
case (HI_SHEAR | APPLY_SHEAR): |
|
mystate = mystate | APPLY_SCALE; |
|
/* NOBREAK */ |
|
case (HI_SHEAR | APPLY_TRANSLATE): |
|
case (HI_SHEAR | APPLY_IDENTITY): |
|
case (HI_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
case (HI_SHEAR | APPLY_SCALE): |
|
state = mystate ^ APPLY_SHEAR; |
|
/* NOBREAK */ |
|
case (HI_SHEAR | APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
case (HI_SHEAR | APPLY_SHEAR | APPLY_SCALE): |
|
// Tx is SHEAR, this is anything |
|
T01 = Tx.m01; |
|
T10 = Tx.m10; |
|
M0 = m00; |
|
m00 = m10 * T01; |
|
m10 = M0 * T10; |
|
M0 = m01; |
|
m01 = m11 * T01; |
|
m11 = M0 * T10; |
|
M0 = m02; |
|
m02 = m12 * T01; |
|
m12 = M0 * T10; |
|
type = TYPE_UNKNOWN; |
|
return; |
|
} |
|
// If Tx has more than one attribute, it is not worth optimizing |
|
// all of those cases... |
|
T00 = Tx.m00; T01 = Tx.m01; T02 = Tx.m02; |
|
T10 = Tx.m10; T11 = Tx.m11; T12 = Tx.m12; |
|
switch (mystate) { |
|
default: |
|
stateError(); |
|
/* NOTREACHED */ |
|
case (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
M0 = m02; |
|
M1 = m12; |
|
T02 += M0 * T00 + M1 * T01; |
|
T12 += M0 * T10 + M1 * T11; |
|
/* NOBREAK */ |
|
case (APPLY_SHEAR | APPLY_SCALE): |
|
m02 = T02; |
|
m12 = T12; |
|
M0 = m00; |
|
M1 = m10; |
|
m00 = M0 * T00 + M1 * T01; |
|
m10 = M0 * T10 + M1 * T11; |
|
M0 = m01; |
|
M1 = m11; |
|
m01 = M0 * T00 + M1 * T01; |
|
m11 = M0 * T10 + M1 * T11; |
|
break; |
|
case (APPLY_SHEAR | APPLY_TRANSLATE): |
|
M0 = m02; |
|
M1 = m12; |
|
T02 += M0 * T00 + M1 * T01; |
|
T12 += M0 * T10 + M1 * T11; |
|
/* NOBREAK */ |
|
case (APPLY_SHEAR): |
|
m02 = T02; |
|
m12 = T12; |
|
M0 = m10; |
|
m00 = M0 * T01; |
|
m10 = M0 * T11; |
|
M0 = m01; |
|
m01 = M0 * T00; |
|
m11 = M0 * T10; |
|
break; |
|
case (APPLY_SCALE | APPLY_TRANSLATE): |
|
M0 = m02; |
|
M1 = m12; |
|
T02 += M0 * T00 + M1 * T01; |
|
T12 += M0 * T10 + M1 * T11; |
|
/* NOBREAK */ |
|
case (APPLY_SCALE): |
|
m02 = T02; |
|
m12 = T12; |
|
M0 = m00; |
|
m00 = M0 * T00; |
|
m10 = M0 * T10; |
|
M0 = m11; |
|
m01 = M0 * T01; |
|
m11 = M0 * T11; |
|
break; |
|
case (APPLY_TRANSLATE): |
|
M0 = m02; |
|
M1 = m12; |
|
T02 += M0 * T00 + M1 * T01; |
|
T12 += M0 * T10 + M1 * T11; |
|
/* NOBREAK */ |
|
case (APPLY_IDENTITY): |
|
m02 = T02; |
|
m12 = T12; |
|
m00 = T00; |
|
m10 = T10; |
|
m01 = T01; |
|
m11 = T11; |
|
state = mystate | txstate; |
|
type = TYPE_UNKNOWN; |
|
return; |
|
} |
|
updateState(); |
|
} |
|
/** |
|
* Returns an <code>AffineTransform</code> object representing the |
|
* inverse transformation. |
|
* The inverse transform Tx' of this transform Tx |
|
* maps coordinates transformed by Tx back |
|
* to their original coordinates. |
|
* In other words, Tx'(Tx(p)) = p = Tx(Tx'(p)). |
|
* <p> |
|
* If this transform maps all coordinates onto a point or a line |
|
* then it will not have an inverse, since coordinates that do |
|
* not lie on the destination point or line will not have an inverse |
|
* mapping. |
|
* The <code>getDeterminant</code> method can be used to determine if this |
|
* transform has no inverse, in which case an exception will be |
|
* thrown if the <code>createInverse</code> method is called. |
|
* @return a new <code>AffineTransform</code> object representing the |
|
* inverse transformation. |
|
* @see #getDeterminant |
|
* @exception NoninvertibleTransformException |
|
* if the matrix cannot be inverted. |
|
* @since 1.2 |
|
*/ |
|
public AffineTransform createInverse() |
|
throws NoninvertibleTransformException |
|
{ |
|
double det; |
|
switch (state) { |
|
default: |
|
stateError(); |
|
/* NOTREACHED */ |
|
return null; |
|
case (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
det = m00 * m11 - m01 * m10; |
|
if (Math.abs(det) <= Double.MIN_VALUE) { |
|
throw new NoninvertibleTransformException("Determinant is "+ |
|
det); |
|
} |
|
return new AffineTransform( m11 / det, -m10 / det, |
|
-m01 / det, m00 / det, |
|
(m01 * m12 - m11 * m02) / det, |
|
(m10 * m02 - m00 * m12) / det, |
|
(APPLY_SHEAR | |
|
APPLY_SCALE | |
|
APPLY_TRANSLATE)); |
|
case (APPLY_SHEAR | APPLY_SCALE): |
|
det = m00 * m11 - m01 * m10; |
|
if (Math.abs(det) <= Double.MIN_VALUE) { |
|
throw new NoninvertibleTransformException("Determinant is "+ |
|
det); |
|
} |
|
return new AffineTransform( m11 / det, -m10 / det, |
|
-m01 / det, m00 / det, |
|
0.0, 0.0, |
|
(APPLY_SHEAR | APPLY_SCALE)); |
|
case (APPLY_SHEAR | APPLY_TRANSLATE): |
|
if (m01 == 0.0 || m10 == 0.0) { |
|
throw new NoninvertibleTransformException("Determinant is 0"); |
|
} |
|
return new AffineTransform( 0.0, 1.0 / m01, |
|
1.0 / m10, 0.0, |
|
-m12 / m10, -m02 / m01, |
|
(APPLY_SHEAR | APPLY_TRANSLATE)); |
|
case (APPLY_SHEAR): |
|
if (m01 == 0.0 || m10 == 0.0) { |
|
throw new NoninvertibleTransformException("Determinant is 0"); |
|
} |
|
return new AffineTransform(0.0, 1.0 / m01, |
|
1.0 / m10, 0.0, |
|
0.0, 0.0, |
|
(APPLY_SHEAR)); |
|
case (APPLY_SCALE | APPLY_TRANSLATE): |
|
if (m00 == 0.0 || m11 == 0.0) { |
|
throw new NoninvertibleTransformException("Determinant is 0"); |
|
} |
|
return new AffineTransform( 1.0 / m00, 0.0, |
|
0.0, 1.0 / m11, |
|
-m02 / m00, -m12 / m11, |
|
(APPLY_SCALE | APPLY_TRANSLATE)); |
|
case (APPLY_SCALE): |
|
if (m00 == 0.0 || m11 == 0.0) { |
|
throw new NoninvertibleTransformException("Determinant is 0"); |
|
} |
|
return new AffineTransform(1.0 / m00, 0.0, |
|
0.0, 1.0 / m11, |
|
0.0, 0.0, |
|
(APPLY_SCALE)); |
|
case (APPLY_TRANSLATE): |
|
return new AffineTransform( 1.0, 0.0, |
|
0.0, 1.0, |
|
-m02, -m12, |
|
(APPLY_TRANSLATE)); |
|
case (APPLY_IDENTITY): |
|
return new AffineTransform(); |
|
} |
|
/* NOTREACHED */ |
|
} |
|
/** |
|
* Sets this transform to the inverse of itself. |
|
* The inverse transform Tx' of this transform Tx |
|
* maps coordinates transformed by Tx back |
|
* to their original coordinates. |
|
* In other words, Tx'(Tx(p)) = p = Tx(Tx'(p)). |
|
* <p> |
|
* If this transform maps all coordinates onto a point or a line |
|
* then it will not have an inverse, since coordinates that do |
|
* not lie on the destination point or line will not have an inverse |
|
* mapping. |
|
* The <code>getDeterminant</code> method can be used to determine if this |
|
* transform has no inverse, in which case an exception will be |
|
* thrown if the <code>invert</code> method is called. |
|
* @see #getDeterminant |
|
* @exception NoninvertibleTransformException |
|
* if the matrix cannot be inverted. |
|
* @since 1.6 |
|
*/ |
|
public void invert() |
|
throws NoninvertibleTransformException |
|
{ |
|
double M00, M01, M02; |
|
double M10, M11, M12; |
|
double det; |
|
switch (state) { |
|
default: |
|
stateError(); |
|
/* NOTREACHED */ |
|
return; |
|
case (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
M00 = m00; M01 = m01; M02 = m02; |
|
M10 = m10; M11 = m11; M12 = m12; |
|
det = M00 * M11 - M01 * M10; |
|
if (Math.abs(det) <= Double.MIN_VALUE) { |
|
throw new NoninvertibleTransformException("Determinant is "+ |
|
det); |
|
} |
|
m00 = M11 / det; |
|
m10 = -M10 / det; |
|
m01 = -M01 / det; |
|
m11 = M00 / det; |
|
m02 = (M01 * M12 - M11 * M02) / det; |
|
m12 = (M10 * M02 - M00 * M12) / det; |
|
break; |
|
case (APPLY_SHEAR | APPLY_SCALE): |
|
M00 = m00; M01 = m01; |
|
M10 = m10; M11 = m11; |
|
det = M00 * M11 - M01 * M10; |
|
if (Math.abs(det) <= Double.MIN_VALUE) { |
|
throw new NoninvertibleTransformException("Determinant is "+ |
|
det); |
|
} |
|
m00 = M11 / det; |
|
m10 = -M10 / det; |
|
m01 = -M01 / det; |
|
m11 = M00 / det; |
|
// m02 = 0.0; |
|
// m12 = 0.0; |
|
break; |
|
case (APPLY_SHEAR | APPLY_TRANSLATE): |
|
M01 = m01; M02 = m02; |
|
M10 = m10; M12 = m12; |
|
if (M01 == 0.0 || M10 == 0.0) { |
|
throw new NoninvertibleTransformException("Determinant is 0"); |
|
} |
|
// m00 = 0.0; |
|
m10 = 1.0 / M01; |
|
m01 = 1.0 / M10; |
|
// m11 = 0.0; |
|
m02 = -M12 / M10; |
|
m12 = -M02 / M01; |
|
break; |
|
case (APPLY_SHEAR): |
|
M01 = m01; |
|
M10 = m10; |
|
if (M01 == 0.0 || M10 == 0.0) { |
|
throw new NoninvertibleTransformException("Determinant is 0"); |
|
} |
|
// m00 = 0.0; |
|
m10 = 1.0 / M01; |
|
m01 = 1.0 / M10; |
|
// m11 = 0.0; |
|
// m02 = 0.0; |
|
// m12 = 0.0; |
|
break; |
|
case (APPLY_SCALE | APPLY_TRANSLATE): |
|
M00 = m00; M02 = m02; |
|
M11 = m11; M12 = m12; |
|
if (M00 == 0.0 || M11 == 0.0) { |
|
throw new NoninvertibleTransformException("Determinant is 0"); |
|
} |
|
m00 = 1.0 / M00; |
|
// m10 = 0.0; |
|
// m01 = 0.0; |
|
m11 = 1.0 / M11; |
|
m02 = -M02 / M00; |
|
m12 = -M12 / M11; |
|
break; |
|
case (APPLY_SCALE): |
|
M00 = m00; |
|
M11 = m11; |
|
if (M00 == 0.0 || M11 == 0.0) { |
|
throw new NoninvertibleTransformException("Determinant is 0"); |
|
} |
|
m00 = 1.0 / M00; |
|
// m10 = 0.0; |
|
// m01 = 0.0; |
|
m11 = 1.0 / M11; |
|
// m02 = 0.0; |
|
// m12 = 0.0; |
|
break; |
|
case (APPLY_TRANSLATE): |
|
// m00 = 1.0; |
|
// m10 = 0.0; |
|
// m01 = 0.0; |
|
// m11 = 1.0; |
|
m02 = -m02; |
|
m12 = -m12; |
|
break; |
|
case (APPLY_IDENTITY): |
|
// m00 = 1.0; |
|
// m10 = 0.0; |
|
// m01 = 0.0; |
|
// m11 = 1.0; |
|
// m02 = 0.0; |
|
// m12 = 0.0; |
|
break; |
|
} |
|
} |
|
/** |
|
* Transforms the specified <code>ptSrc</code> and stores the result |
|
* in <code>ptDst</code>. |
|
* If <code>ptDst</code> is <code>null</code>, a new {@link Point2D} |
|
* object is allocated and then the result of the transformation is |
|
* stored in this object. |
|
* In either case, <code>ptDst</code>, which contains the |
|
* transformed point, is returned for convenience. |
|
* If <code>ptSrc</code> and <code>ptDst</code> are the same |
|
* object, the input point is correctly overwritten with |
|
* the transformed point. |
|
* @param ptSrc the specified <code>Point2D</code> to be transformed |
|
* @param ptDst the specified <code>Point2D</code> that stores the |
|
* result of transforming <code>ptSrc</code> |
|
* @return the <code>ptDst</code> after transforming |
|
* <code>ptSrc</code> and storing the result in <code>ptDst</code>. |
|
* @since 1.2 |
|
*/ |
|
public Point2D transform(Point2D ptSrc, Point2D ptDst) { |
|
if (ptDst == null) { |
|
if (ptSrc instanceof Point2D.Double) { |
|
ptDst = new Point2D.Double(); |
|
} else { |
|
ptDst = new Point2D.Float(); |
|
} |
|
} |
|
// Copy source coords into local variables in case src == dst |
|
double x = ptSrc.getX(); |
|
double y = ptSrc.getY(); |
|
switch (state) { |
|
default: |
|
stateError(); |
|
/* NOTREACHED */ |
|
return null; |
|
case (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
ptDst.setLocation(x * m00 + y * m01 + m02, |
|
x * m10 + y * m11 + m12); |
|
return ptDst; |
|
case (APPLY_SHEAR | APPLY_SCALE): |
|
ptDst.setLocation(x * m00 + y * m01, x * m10 + y * m11); |
|
return ptDst; |
|
case (APPLY_SHEAR | APPLY_TRANSLATE): |
|
ptDst.setLocation(y * m01 + m02, x * m10 + m12); |
|
return ptDst; |
|
case (APPLY_SHEAR): |
|
ptDst.setLocation(y * m01, x * m10); |
|
return ptDst; |
|
case (APPLY_SCALE | APPLY_TRANSLATE): |
|
ptDst.setLocation(x * m00 + m02, y * m11 + m12); |
|
return ptDst; |
|
case (APPLY_SCALE): |
|
ptDst.setLocation(x * m00, y * m11); |
|
return ptDst; |
|
case (APPLY_TRANSLATE): |
|
ptDst.setLocation(x + m02, y + m12); |
|
return ptDst; |
|
case (APPLY_IDENTITY): |
|
ptDst.setLocation(x, y); |
|
return ptDst; |
|
} |
|
/* NOTREACHED */ |
|
} |
|
/** |
|
* Transforms an array of point objects by this transform. |
|
* If any element of the <code>ptDst</code> array is |
|
* <code>null</code>, a new <code>Point2D</code> object is allocated |
|
* and stored into that element before storing the results of the |
|
* transformation. |
|
* <p> |
|
* Note that this method does not take any precautions to |
|
* avoid problems caused by storing results into <code>Point2D</code> |
|
* objects that will be used as the source for calculations |
|
* further down the source array. |
|
* This method does guarantee that if a specified <code>Point2D</code> |
|
* object is both the source and destination for the same single point |
|
* transform operation then the results will not be stored until |
|
* the calculations are complete to avoid storing the results on |
|
* top of the operands. |
|
* If, however, the destination <code>Point2D</code> object for one |
|
* operation is the same object as the source <code>Point2D</code> |
|
* object for another operation further down the source array then |
|
* the original coordinates in that point are overwritten before |
|
* they can be converted. |
|
* @param ptSrc the array containing the source point objects |
|
* @param ptDst the array into which the transform point objects are |
|
* returned |
|
* @param srcOff the offset to the first point object to be |
|
* transformed in the source array |
|
* @param dstOff the offset to the location of the first |
|
* transformed point object that is stored in the destination array |
|
* @param numPts the number of point objects to be transformed |
|
* @since 1.2 |
|
*/ |
|
public void transform(Point2D[] ptSrc, int srcOff, |
|
Point2D[] ptDst, int dstOff, |
|
int numPts) { |
|
int state = this.state; |
|
while (--numPts >= 0) { |
|
// Copy source coords into local variables in case src == dst |
|
Point2D src = ptSrc[srcOff++]; |
|
double x = src.getX(); |
|
double y = src.getY(); |
|
Point2D dst = ptDst[dstOff++]; |
|
if (dst == null) { |
|
if (src instanceof Point2D.Double) { |
|
dst = new Point2D.Double(); |
|
} else { |
|
dst = new Point2D.Float(); |
|
} |
|
ptDst[dstOff - 1] = dst; |
|
} |
|
switch (state) { |
|
default: |
|
stateError(); |
|
/* NOTREACHED */ |
|
return; |
|
case (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
dst.setLocation(x * m00 + y * m01 + m02, |
|
x * m10 + y * m11 + m12); |
|
break; |
|
case (APPLY_SHEAR | APPLY_SCALE): |
|
dst.setLocation(x * m00 + y * m01, x * m10 + y * m11); |
|
break; |
|
case (APPLY_SHEAR | APPLY_TRANSLATE): |
|
dst.setLocation(y * m01 + m02, x * m10 + m12); |
|
break; |
|
case (APPLY_SHEAR): |
|
dst.setLocation(y * m01, x * m10); |
|
break; |
|
case (APPLY_SCALE | APPLY_TRANSLATE): |
|
dst.setLocation(x * m00 + m02, y * m11 + m12); |
|
break; |
|
case (APPLY_SCALE): |
|
dst.setLocation(x * m00, y * m11); |
|
break; |
|
case (APPLY_TRANSLATE): |
|
dst.setLocation(x + m02, y + m12); |
|
break; |
|
case (APPLY_IDENTITY): |
|
dst.setLocation(x, y); |
|
break; |
|
} |
|
} |
|
/* NOTREACHED */ |
|
} |
|
/** |
|
* Transforms an array of floating point coordinates by this transform. |
|
* The two coordinate array sections can be exactly the same or |
|
* can be overlapping sections of the same array without affecting the |
|
* validity of the results. |
|
* This method ensures that no source coordinates are overwritten by a |
|
* previous operation before they can be transformed. |
|
* The coordinates are stored in the arrays starting at the specified |
|
* offset in the order <code>[x0, y0, x1, y1, ..., xn, yn]</code>. |
|
* @param srcPts the array containing the source point coordinates. |
|
* Each point is stored as a pair of x, y coordinates. |
|
* @param dstPts the array into which the transformed point coordinates |
|
* are returned. Each point is stored as a pair of x, y |
|
* coordinates. |
|
* @param srcOff the offset to the first point to be transformed |
|
* in the source array |
|
* @param dstOff the offset to the location of the first |
|
* transformed point that is stored in the destination array |
|
* @param numPts the number of points to be transformed |
|
* @since 1.2 |
|
*/ |
|
public void transform(float[] srcPts, int srcOff, |
|
float[] dstPts, int dstOff, |
|
int numPts) { |
|
double M00, M01, M02, M10, M11, M12; // For caching |
|
if (dstPts == srcPts && |
|
dstOff > srcOff && dstOff < srcOff + numPts * 2) |
|
{ |
|
// If the arrays overlap partially with the destination higher |
|
// than the source and we transform the coordinates normally |
|
// we would overwrite some of the later source coordinates |
|
// with results of previous transformations. |
|
// To get around this we use arraycopy to copy the points |
|
// to their final destination with correct overwrite |
|
// handling and then transform them in place in the new |
|
// safer location. |
|
System.arraycopy(srcPts, srcOff, dstPts, dstOff, numPts * 2); |
|
// srcPts = dstPts; // They are known to be equal. |
|
srcOff = dstOff; |
|
} |
|
switch (state) { |
|
default: |
|
stateError(); |
|
/* NOTREACHED */ |
|
return; |
|
case (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
M00 = m00; M01 = m01; M02 = m02; |
|
M10 = m10; M11 = m11; M12 = m12; |
|
while (--numPts >= 0) { |
|
double x = srcPts[srcOff++]; |
|
double y = srcPts[srcOff++]; |
|
dstPts[dstOff++] = (float) (M00 * x + M01 * y + M02); |
|
dstPts[dstOff++] = (float) (M10 * x + M11 * y + M12); |
|
} |
|
return; |
|
case (APPLY_SHEAR | APPLY_SCALE): |
|
M00 = m00; M01 = m01; |
|
M10 = m10; M11 = m11; |
|
while (--numPts >= 0) { |
|
double x = srcPts[srcOff++]; |
|
double y = srcPts[srcOff++]; |
|
dstPts[dstOff++] = (float) (M00 * x + M01 * y); |
|
dstPts[dstOff++] = (float) (M10 * x + M11 * y); |
|
} |
|
return; |
|
case (APPLY_SHEAR | APPLY_TRANSLATE): |
|
M01 = m01; M02 = m02; |
|
M10 = m10; M12 = m12; |
|
while (--numPts >= 0) { |
|
double x = srcPts[srcOff++]; |
|
dstPts[dstOff++] = (float) (M01 * srcPts[srcOff++] + M02); |
|
dstPts[dstOff++] = (float) (M10 * x + M12); |
|
} |
|
return; |
|
case (APPLY_SHEAR): |
|
M01 = m01; M10 = m10; |
|
while (--numPts >= 0) { |
|
double x = srcPts[srcOff++]; |
|
dstPts[dstOff++] = (float) (M01 * srcPts[srcOff++]); |
|
dstPts[dstOff++] = (float) (M10 * x); |
|
} |
|
return; |
|
case (APPLY_SCALE | APPLY_TRANSLATE): |
|
M00 = m00; M02 = m02; |
|
M11 = m11; M12 = m12; |
|
while (--numPts >= 0) { |
|
dstPts[dstOff++] = (float) (M00 * srcPts[srcOff++] + M02); |
|
dstPts[dstOff++] = (float) (M11 * srcPts[srcOff++] + M12); |
|
} |
|
return; |
|
case (APPLY_SCALE): |
|
M00 = m00; M11 = m11; |
|
while (--numPts >= 0) { |
|
dstPts[dstOff++] = (float) (M00 * srcPts[srcOff++]); |
|
dstPts[dstOff++] = (float) (M11 * srcPts[srcOff++]); |
|
} |
|
return; |
|
case (APPLY_TRANSLATE): |
|
M02 = m02; M12 = m12; |
|
while (--numPts >= 0) { |
|
dstPts[dstOff++] = (float) (srcPts[srcOff++] + M02); |
|
dstPts[dstOff++] = (float) (srcPts[srcOff++] + M12); |
|
} |
|
return; |
|
case (APPLY_IDENTITY): |
|
if (srcPts != dstPts || srcOff != dstOff) { |
|
System.arraycopy(srcPts, srcOff, dstPts, dstOff, |
|
numPts * 2); |
|
} |
|
return; |
|
} |
|
/* NOTREACHED */ |
|
} |
|
/** |
|
* Transforms an array of double precision coordinates by this transform. |
|
* The two coordinate array sections can be exactly the same or |
|
* can be overlapping sections of the same array without affecting the |
|
* validity of the results. |
|
* This method ensures that no source coordinates are |
|
* overwritten by a previous operation before they can be transformed. |
|
* The coordinates are stored in the arrays starting at the indicated |
|
* offset in the order <code>[x0, y0, x1, y1, ..., xn, yn]</code>. |
|
* @param srcPts the array containing the source point coordinates. |
|
* Each point is stored as a pair of x, y coordinates. |
|
* @param dstPts the array into which the transformed point |
|
* coordinates are returned. Each point is stored as a pair of |
|
* x, y coordinates. |
|
* @param srcOff the offset to the first point to be transformed |
|
* in the source array |
|
* @param dstOff the offset to the location of the first |
|
* transformed point that is stored in the destination array |
|
* @param numPts the number of point objects to be transformed |
|
* @since 1.2 |
|
*/ |
|
public void transform(double[] srcPts, int srcOff, |
|
double[] dstPts, int dstOff, |
|
int numPts) { |
|
double M00, M01, M02, M10, M11, M12; // For caching |
|
if (dstPts == srcPts && |
|
dstOff > srcOff && dstOff < srcOff + numPts * 2) |
|
{ |
|
// If the arrays overlap partially with the destination higher |
|
// than the source and we transform the coordinates normally |
|
// we would overwrite some of the later source coordinates |
|
// with results of previous transformations. |
|
// To get around this we use arraycopy to copy the points |
|
// to their final destination with correct overwrite |
|
// handling and then transform them in place in the new |
|
// safer location. |
|
System.arraycopy(srcPts, srcOff, dstPts, dstOff, numPts * 2); |
|
// srcPts = dstPts; // They are known to be equal. |
|
srcOff = dstOff; |
|
} |
|
switch (state) { |
|
default: |
|
stateError(); |
|
/* NOTREACHED */ |
|
return; |
|
case (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
M00 = m00; M01 = m01; M02 = m02; |
|
M10 = m10; M11 = m11; M12 = m12; |
|
while (--numPts >= 0) { |
|
double x = srcPts[srcOff++]; |
|
double y = srcPts[srcOff++]; |
|
dstPts[dstOff++] = M00 * x + M01 * y + M02; |
|
dstPts[dstOff++] = M10 * x + M11 * y + M12; |
|
} |
|
return; |
|
case (APPLY_SHEAR | APPLY_SCALE): |
|
M00 = m00; M01 = m01; |
|
M10 = m10; M11 = m11; |
|
while (--numPts >= 0) { |
|
double x = srcPts[srcOff++]; |
|
double y = srcPts[srcOff++]; |
|
dstPts[dstOff++] = M00 * x + M01 * y; |
|
dstPts[dstOff++] = M10 * x + M11 * y; |
|
} |
|
return; |
|
case (APPLY_SHEAR | APPLY_TRANSLATE): |
|
M01 = m01; M02 = m02; |
|
M10 = m10; M12 = m12; |
|
while (--numPts >= 0) { |
|
double x = srcPts[srcOff++]; |
|
dstPts[dstOff++] = M01 * srcPts[srcOff++] + M02; |
|
dstPts[dstOff++] = M10 * x + M12; |
|
} |
|
return; |
|
case (APPLY_SHEAR): |
|
M01 = m01; M10 = m10; |
|
while (--numPts >= 0) { |
|
double x = srcPts[srcOff++]; |
|
dstPts[dstOff++] = M01 * srcPts[srcOff++]; |
|
dstPts[dstOff++] = M10 * x; |
|
} |
|
return; |
|
case (APPLY_SCALE | APPLY_TRANSLATE): |
|
M00 = m00; M02 = m02; |
|
M11 = m11; M12 = m12; |
|
while (--numPts >= 0) { |
|
dstPts[dstOff++] = M00 * srcPts[srcOff++] + M02; |
|
dstPts[dstOff++] = M11 * srcPts[srcOff++] + M12; |
|
} |
|
return; |
|
case (APPLY_SCALE): |
|
M00 = m00; M11 = m11; |
|
while (--numPts >= 0) { |
|
dstPts[dstOff++] = M00 * srcPts[srcOff++]; |
|
dstPts[dstOff++] = M11 * srcPts[srcOff++]; |
|
} |
|
return; |
|
case (APPLY_TRANSLATE): |
|
M02 = m02; M12 = m12; |
|
while (--numPts >= 0) { |
|
dstPts[dstOff++] = srcPts[srcOff++] + M02; |
|
dstPts[dstOff++] = srcPts[srcOff++] + M12; |
|
} |
|
return; |
|
case (APPLY_IDENTITY): |
|
if (srcPts != dstPts || srcOff != dstOff) { |
|
System.arraycopy(srcPts, srcOff, dstPts, dstOff, |
|
numPts * 2); |
|
} |
|
return; |
|
} |
|
/* NOTREACHED */ |
|
} |
|
/** |
|
* Transforms an array of floating point coordinates by this transform |
|
* and stores the results into an array of doubles. |
|
* The coordinates are stored in the arrays starting at the specified |
|
* offset in the order <code>[x0, y0, x1, y1, ..., xn, yn]</code>. |
|
* @param srcPts the array containing the source point coordinates. |
|
* Each point is stored as a pair of x, y coordinates. |
|
* @param dstPts the array into which the transformed point coordinates |
|
* are returned. Each point is stored as a pair of x, y |
|
* coordinates. |
|
* @param srcOff the offset to the first point to be transformed |
|
* in the source array |
|
* @param dstOff the offset to the location of the first |
|
* transformed point that is stored in the destination array |
|
* @param numPts the number of points to be transformed |
|
* @since 1.2 |
|
*/ |
|
public void transform(float[] srcPts, int srcOff, |
|
double[] dstPts, int dstOff, |
|
int numPts) { |
|
double M00, M01, M02, M10, M11, M12; // For caching |
|
switch (state) { |
|
default: |
|
stateError(); |
|
/* NOTREACHED */ |
|
return; |
|
case (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
M00 = m00; M01 = m01; M02 = m02; |
|
M10 = m10; M11 = m11; M12 = m12; |
|
while (--numPts >= 0) { |
|
double x = srcPts[srcOff++]; |
|
double y = srcPts[srcOff++]; |
|
dstPts[dstOff++] = M00 * x + M01 * y + M02; |
|
dstPts[dstOff++] = M10 * x + M11 * y + M12; |
|
} |
|
return; |
|
case (APPLY_SHEAR | APPLY_SCALE): |
|
M00 = m00; M01 = m01; |
|
M10 = m10; M11 = m11; |
|
while (--numPts >= 0) { |
|
double x = srcPts[srcOff++]; |
|
double y = srcPts[srcOff++]; |
|
dstPts[dstOff++] = M00 * x + M01 * y; |
|
dstPts[dstOff++] = M10 * x + M11 * y; |
|
} |
|
return; |
|
case (APPLY_SHEAR | APPLY_TRANSLATE): |
|
M01 = m01; M02 = m02; |
|
M10 = m10; M12 = m12; |
|
while (--numPts >= 0) { |
|
double x = srcPts[srcOff++]; |
|
dstPts[dstOff++] = M01 * srcPts[srcOff++] + M02; |
|
dstPts[dstOff++] = M10 * x + M12; |
|
} |
|
return; |
|
case (APPLY_SHEAR): |
|
M01 = m01; M10 = m10; |
|
while (--numPts >= 0) { |
|
double x = srcPts[srcOff++]; |
|
dstPts[dstOff++] = M01 * srcPts[srcOff++]; |
|
dstPts[dstOff++] = M10 * x; |
|
} |
|
return; |
|
case (APPLY_SCALE | APPLY_TRANSLATE): |
|
M00 = m00; M02 = m02; |
|
M11 = m11; M12 = m12; |
|
while (--numPts >= 0) { |
|
dstPts[dstOff++] = M00 * srcPts[srcOff++] + M02; |
|
dstPts[dstOff++] = M11 * srcPts[srcOff++] + M12; |
|
} |
|
return; |
|
case (APPLY_SCALE): |
|
M00 = m00; M11 = m11; |
|
while (--numPts >= 0) { |
|
dstPts[dstOff++] = M00 * srcPts[srcOff++]; |
|
dstPts[dstOff++] = M11 * srcPts[srcOff++]; |
|
} |
|
return; |
|
case (APPLY_TRANSLATE): |
|
M02 = m02; M12 = m12; |
|
while (--numPts >= 0) { |
|
dstPts[dstOff++] = srcPts[srcOff++] + M02; |
|
dstPts[dstOff++] = srcPts[srcOff++] + M12; |
|
} |
|
return; |
|
case (APPLY_IDENTITY): |
|
while (--numPts >= 0) { |
|
dstPts[dstOff++] = srcPts[srcOff++]; |
|
dstPts[dstOff++] = srcPts[srcOff++]; |
|
} |
|
return; |
|
} |
|
/* NOTREACHED */ |
|
} |
|
/** |
|
* Transforms an array of double precision coordinates by this transform |
|
* and stores the results into an array of floats. |
|
* The coordinates are stored in the arrays starting at the specified |
|
* offset in the order <code>[x0, y0, x1, y1, ..., xn, yn]</code>. |
|
* @param srcPts the array containing the source point coordinates. |
|
* Each point is stored as a pair of x, y coordinates. |
|
* @param dstPts the array into which the transformed point |
|
* coordinates are returned. Each point is stored as a pair of |
|
* x, y coordinates. |
|
* @param srcOff the offset to the first point to be transformed |
|
* in the source array |
|
* @param dstOff the offset to the location of the first |
|
* transformed point that is stored in the destination array |
|
* @param numPts the number of point objects to be transformed |
|
* @since 1.2 |
|
*/ |
|
public void transform(double[] srcPts, int srcOff, |
|
float[] dstPts, int dstOff, |
|
int numPts) { |
|
double M00, M01, M02, M10, M11, M12; // For caching |
|
switch (state) { |
|
default: |
|
stateError(); |
|
/* NOTREACHED */ |
|
return; |
|
case (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
M00 = m00; M01 = m01; M02 = m02; |
|
M10 = m10; M11 = m11; M12 = m12; |
|
while (--numPts >= 0) { |
|
double x = srcPts[srcOff++]; |
|
double y = srcPts[srcOff++]; |
|
dstPts[dstOff++] = (float) (M00 * x + M01 * y + M02); |
|
dstPts[dstOff++] = (float) (M10 * x + M11 * y + M12); |
|
} |
|
return; |
|
case (APPLY_SHEAR | APPLY_SCALE): |
|
M00 = m00; M01 = m01; |
|
M10 = m10; M11 = m11; |
|
while (--numPts >= 0) { |
|
double x = srcPts[srcOff++]; |
|
double y = srcPts[srcOff++]; |
|
dstPts[dstOff++] = (float) (M00 * x + M01 * y); |
|
dstPts[dstOff++] = (float) (M10 * x + M11 * y); |
|
} |
|
return; |
|
case (APPLY_SHEAR | APPLY_TRANSLATE): |
|
M01 = m01; M02 = m02; |
|
M10 = m10; M12 = m12; |
|
while (--numPts >= 0) { |
|
double x = srcPts[srcOff++]; |
|
dstPts[dstOff++] = (float) (M01 * srcPts[srcOff++] + M02); |
|
dstPts[dstOff++] = (float) (M10 * x + M12); |
|
} |
|
return; |
|
case (APPLY_SHEAR): |
|
M01 = m01; M10 = m10; |
|
while (--numPts >= 0) { |
|
double x = srcPts[srcOff++]; |
|
dstPts[dstOff++] = (float) (M01 * srcPts[srcOff++]); |
|
dstPts[dstOff++] = (float) (M10 * x); |
|
} |
|
return; |
|
case (APPLY_SCALE | APPLY_TRANSLATE): |
|
M00 = m00; M02 = m02; |
|
M11 = m11; M12 = m12; |
|
while (--numPts >= 0) { |
|
dstPts[dstOff++] = (float) (M00 * srcPts[srcOff++] + M02); |
|
dstPts[dstOff++] = (float) (M11 * srcPts[srcOff++] + M12); |
|
} |
|
return; |
|
case (APPLY_SCALE): |
|
M00 = m00; M11 = m11; |
|
while (--numPts >= 0) { |
|
dstPts[dstOff++] = (float) (M00 * srcPts[srcOff++]); |
|
dstPts[dstOff++] = (float) (M11 * srcPts[srcOff++]); |
|
} |
|
return; |
|
case (APPLY_TRANSLATE): |
|
M02 = m02; M12 = m12; |
|
while (--numPts >= 0) { |
|
dstPts[dstOff++] = (float) (srcPts[srcOff++] + M02); |
|
dstPts[dstOff++] = (float) (srcPts[srcOff++] + M12); |
|
} |
|
return; |
|
case (APPLY_IDENTITY): |
|
while (--numPts >= 0) { |
|
dstPts[dstOff++] = (float) (srcPts[srcOff++]); |
|
dstPts[dstOff++] = (float) (srcPts[srcOff++]); |
|
} |
|
return; |
|
} |
|
/* NOTREACHED */ |
|
} |
|
/** |
|
* Inverse transforms the specified <code>ptSrc</code> and stores the |
|
* result in <code>ptDst</code>. |
|
* If <code>ptDst</code> is <code>null</code>, a new |
|
* <code>Point2D</code> object is allocated and then the result of the |
|
* transform is stored in this object. |
|
* In either case, <code>ptDst</code>, which contains the transformed |
|
* point, is returned for convenience. |
|
* If <code>ptSrc</code> and <code>ptDst</code> are the same |
|
* object, the input point is correctly overwritten with the |
|
* transformed point. |
|
* @param ptSrc the point to be inverse transformed |
|
* @param ptDst the resulting transformed point |
|
* @return <code>ptDst</code>, which contains the result of the |
|
* inverse transform. |
|
* @exception NoninvertibleTransformException if the matrix cannot be |
|
* inverted. |
|
* @since 1.2 |
|
*/ |
|
@SuppressWarnings("fallthrough") |
|
public Point2D inverseTransform(Point2D ptSrc, Point2D ptDst) |
|
throws NoninvertibleTransformException |
|
{ |
|
if (ptDst == null) { |
|
if (ptSrc instanceof Point2D.Double) { |
|
ptDst = new Point2D.Double(); |
|
} else { |
|
ptDst = new Point2D.Float(); |
|
} |
|
} |
|
// Copy source coords into local variables in case src == dst |
|
double x = ptSrc.getX(); |
|
double y = ptSrc.getY(); |
|
switch (state) { |
|
default: |
|
stateError(); |
|
/* NOTREACHED */ |
|
case (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
x -= m02; |
|
y -= m12; |
|
/* NOBREAK */ |
|
case (APPLY_SHEAR | APPLY_SCALE): |
|
double det = m00 * m11 - m01 * m10; |
|
if (Math.abs(det) <= Double.MIN_VALUE) { |
|
throw new NoninvertibleTransformException("Determinant is "+ |
|
det); |
|
} |
|
ptDst.setLocation((x * m11 - y * m01) / det, |
|
(y * m00 - x * m10) / det); |
|
return ptDst; |
|
case (APPLY_SHEAR | APPLY_TRANSLATE): |
|
x -= m02; |
|
y -= m12; |
|
/* NOBREAK */ |
|
case (APPLY_SHEAR): |
|
if (m01 == 0.0 || m10 == 0.0) { |
|
throw new NoninvertibleTransformException("Determinant is 0"); |
|
} |
|
ptDst.setLocation(y / m10, x / m01); |
|
return ptDst; |
|
case (APPLY_SCALE | APPLY_TRANSLATE): |
|
x -= m02; |
|
y -= m12; |
|
/* NOBREAK */ |
|
case (APPLY_SCALE): |
|
if (m00 == 0.0 || m11 == 0.0) { |
|
throw new NoninvertibleTransformException("Determinant is 0"); |
|
} |
|
ptDst.setLocation(x / m00, y / m11); |
|
return ptDst; |
|
case (APPLY_TRANSLATE): |
|
ptDst.setLocation(x - m02, y - m12); |
|
return ptDst; |
|
case (APPLY_IDENTITY): |
|
ptDst.setLocation(x, y); |
|
return ptDst; |
|
} |
|
/* NOTREACHED */ |
|
} |
|
/** |
|
* Inverse transforms an array of double precision coordinates by |
|
* this transform. |
|
* The two coordinate array sections can be exactly the same or |
|
* can be overlapping sections of the same array without affecting the |
|
* validity of the results. |
|
* This method ensures that no source coordinates are |
|
* overwritten by a previous operation before they can be transformed. |
|
* The coordinates are stored in the arrays starting at the specified |
|
* offset in the order <code>[x0, y0, x1, y1, ..., xn, yn]</code>. |
|
* @param srcPts the array containing the source point coordinates. |
|
* Each point is stored as a pair of x, y coordinates. |
|
* @param dstPts the array into which the transformed point |
|
* coordinates are returned. Each point is stored as a pair of |
|
* x, y coordinates. |
|
* @param srcOff the offset to the first point to be transformed |
|
* in the source array |
|
* @param dstOff the offset to the location of the first |
|
* transformed point that is stored in the destination array |
|
* @param numPts the number of point objects to be transformed |
|
* @exception NoninvertibleTransformException if the matrix cannot be |
|
* inverted. |
|
* @since 1.2 |
|
*/ |
|
public void inverseTransform(double[] srcPts, int srcOff, |
|
double[] dstPts, int dstOff, |
|
int numPts) |
|
throws NoninvertibleTransformException |
|
{ |
|
double M00, M01, M02, M10, M11, M12; // For caching |
|
double det; |
|
if (dstPts == srcPts && |
|
dstOff > srcOff && dstOff < srcOff + numPts * 2) |
|
{ |
|
// If the arrays overlap partially with the destination higher |
|
// than the source and we transform the coordinates normally |
|
// we would overwrite some of the later source coordinates |
|
// with results of previous transformations. |
|
// To get around this we use arraycopy to copy the points |
|
// to their final destination with correct overwrite |
|
// handling and then transform them in place in the new |
|
// safer location. |
|
System.arraycopy(srcPts, srcOff, dstPts, dstOff, numPts * 2); |
|
// srcPts = dstPts; // They are known to be equal. |
|
srcOff = dstOff; |
|
} |
|
switch (state) { |
|
default: |
|
stateError(); |
|
/* NOTREACHED */ |
|
return; |
|
case (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
M00 = m00; M01 = m01; M02 = m02; |
|
M10 = m10; M11 = m11; M12 = m12; |
|
det = M00 * M11 - M01 * M10; |
|
if (Math.abs(det) <= Double.MIN_VALUE) { |
|
throw new NoninvertibleTransformException("Determinant is "+ |
|
det); |
|
} |
|
while (--numPts >= 0) { |
|
double x = srcPts[srcOff++] - M02; |
|
double y = srcPts[srcOff++] - M12; |
|
dstPts[dstOff++] = (x * M11 - y * M01) / det; |
|
dstPts[dstOff++] = (y * M00 - x * M10) / det; |
|
} |
|
return; |
|
case (APPLY_SHEAR | APPLY_SCALE): |
|
M00 = m00; M01 = m01; |
|
M10 = m10; M11 = m11; |
|
det = M00 * M11 - M01 * M10; |
|
if (Math.abs(det) <= Double.MIN_VALUE) { |
|
throw new NoninvertibleTransformException("Determinant is "+ |
|
det); |
|
} |
|
while (--numPts >= 0) { |
|
double x = srcPts[srcOff++]; |
|
double y = srcPts[srcOff++]; |
|
dstPts[dstOff++] = (x * M11 - y * M01) / det; |
|
dstPts[dstOff++] = (y * M00 - x * M10) / det; |
|
} |
|
return; |
|
case (APPLY_SHEAR | APPLY_TRANSLATE): |
|
M01 = m01; M02 = m02; |
|
M10 = m10; M12 = m12; |
|
if (M01 == 0.0 || M10 == 0.0) { |
|
throw new NoninvertibleTransformException("Determinant is 0"); |
|
} |
|
while (--numPts >= 0) { |
|
double x = srcPts[srcOff++] - M02; |
|
dstPts[dstOff++] = (srcPts[srcOff++] - M12) / M10; |
|
dstPts[dstOff++] = x / M01; |
|
} |
|
return; |
|
case (APPLY_SHEAR): |
|
M01 = m01; M10 = m10; |
|
if (M01 == 0.0 || M10 == 0.0) { |
|
throw new NoninvertibleTransformException("Determinant is 0"); |
|
} |
|
while (--numPts >= 0) { |
|
double x = srcPts[srcOff++]; |
|
dstPts[dstOff++] = srcPts[srcOff++] / M10; |
|
dstPts[dstOff++] = x / M01; |
|
} |
|
return; |
|
case (APPLY_SCALE | APPLY_TRANSLATE): |
|
M00 = m00; M02 = m02; |
|
M11 = m11; M12 = m12; |
|
if (M00 == 0.0 || M11 == 0.0) { |
|
throw new NoninvertibleTransformException("Determinant is 0"); |
|
} |
|
while (--numPts >= 0) { |
|
dstPts[dstOff++] = (srcPts[srcOff++] - M02) / M00; |
|
dstPts[dstOff++] = (srcPts[srcOff++] - M12) / M11; |
|
} |
|
return; |
|
case (APPLY_SCALE): |
|
M00 = m00; M11 = m11; |
|
if (M00 == 0.0 || M11 == 0.0) { |
|
throw new NoninvertibleTransformException("Determinant is 0"); |
|
} |
|
while (--numPts >= 0) { |
|
dstPts[dstOff++] = srcPts[srcOff++] / M00; |
|
dstPts[dstOff++] = srcPts[srcOff++] / M11; |
|
} |
|
return; |
|
case (APPLY_TRANSLATE): |
|
M02 = m02; M12 = m12; |
|
while (--numPts >= 0) { |
|
dstPts[dstOff++] = srcPts[srcOff++] - M02; |
|
dstPts[dstOff++] = srcPts[srcOff++] - M12; |
|
} |
|
return; |
|
case (APPLY_IDENTITY): |
|
if (srcPts != dstPts || srcOff != dstOff) { |
|
System.arraycopy(srcPts, srcOff, dstPts, dstOff, |
|
numPts * 2); |
|
} |
|
return; |
|
} |
|
/* NOTREACHED */ |
|
} |
|
/** |
|
* Transforms the relative distance vector specified by |
|
* <code>ptSrc</code> and stores the result in <code>ptDst</code>. |
|
* A relative distance vector is transformed without applying the |
|
* translation components of the affine transformation matrix |
|
* using the following equations: |
|
* <pre> |
|
* [ x' ] [ m00 m01 (m02) ] [ x ] [ m00x + m01y ] |
|
* [ y' ] = [ m10 m11 (m12) ] [ y ] = [ m10x + m11y ] |
|
* [ (1) ] [ (0) (0) ( 1 ) ] [ (1) ] [ (1) ] |
|
* </pre> |
|
* If <code>ptDst</code> is <code>null</code>, a new |
|
* <code>Point2D</code> object is allocated and then the result of the |
|
* transform is stored in this object. |
|
* In either case, <code>ptDst</code>, which contains the |
|
* transformed point, is returned for convenience. |
|
* If <code>ptSrc</code> and <code>ptDst</code> are the same object, |
|
* the input point is correctly overwritten with the transformed |
|
* point. |
|
* @param ptSrc the distance vector to be delta transformed |
|
* @param ptDst the resulting transformed distance vector |
|
* @return <code>ptDst</code>, which contains the result of the |
|
* transformation. |
|
* @since 1.2 |
|
*/ |
|
public Point2D deltaTransform(Point2D ptSrc, Point2D ptDst) { |
|
if (ptDst == null) { |
|
if (ptSrc instanceof Point2D.Double) { |
|
ptDst = new Point2D.Double(); |
|
} else { |
|
ptDst = new Point2D.Float(); |
|
} |
|
} |
|
// Copy source coords into local variables in case src == dst |
|
double x = ptSrc.getX(); |
|
double y = ptSrc.getY(); |
|
switch (state) { |
|
default: |
|
stateError(); |
|
/* NOTREACHED */ |
|
return null; |
|
case (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
case (APPLY_SHEAR | APPLY_SCALE): |
|
ptDst.setLocation(x * m00 + y * m01, x * m10 + y * m11); |
|
return ptDst; |
|
case (APPLY_SHEAR | APPLY_TRANSLATE): |
|
case (APPLY_SHEAR): |
|
ptDst.setLocation(y * m01, x * m10); |
|
return ptDst; |
|
case (APPLY_SCALE | APPLY_TRANSLATE): |
|
case (APPLY_SCALE): |
|
ptDst.setLocation(x * m00, y * m11); |
|
return ptDst; |
|
case (APPLY_TRANSLATE): |
|
case (APPLY_IDENTITY): |
|
ptDst.setLocation(x, y); |
|
return ptDst; |
|
} |
|
/* NOTREACHED */ |
|
} |
|
/** |
|
* Transforms an array of relative distance vectors by this |
|
* transform. |
|
* A relative distance vector is transformed without applying the |
|
* translation components of the affine transformation matrix |
|
* using the following equations: |
|
* <pre> |
|
* [ x' ] [ m00 m01 (m02) ] [ x ] [ m00x + m01y ] |
|
* [ y' ] = [ m10 m11 (m12) ] [ y ] = [ m10x + m11y ] |
|
* [ (1) ] [ (0) (0) ( 1 ) ] [ (1) ] [ (1) ] |
|
* </pre> |
|
* The two coordinate array sections can be exactly the same or |
|
* can be overlapping sections of the same array without affecting the |
|
* validity of the results. |
|
* This method ensures that no source coordinates are |
|
* overwritten by a previous operation before they can be transformed. |
|
* The coordinates are stored in the arrays starting at the indicated |
|
* offset in the order <code>[x0, y0, x1, y1, ..., xn, yn]</code>. |
|
* @param srcPts the array containing the source distance vectors. |
|
* Each vector is stored as a pair of relative x, y coordinates. |
|
* @param dstPts the array into which the transformed distance vectors |
|
* are returned. Each vector is stored as a pair of relative |
|
* x, y coordinates. |
|
* @param srcOff the offset to the first vector to be transformed |
|
* in the source array |
|
* @param dstOff the offset to the location of the first |
|
* transformed vector that is stored in the destination array |
|
* @param numPts the number of vector coordinate pairs to be |
|
* transformed |
|
* @since 1.2 |
|
*/ |
|
public void deltaTransform(double[] srcPts, int srcOff, |
|
double[] dstPts, int dstOff, |
|
int numPts) { |
|
double M00, M01, M10, M11; // For caching |
|
if (dstPts == srcPts && |
|
dstOff > srcOff && dstOff < srcOff + numPts * 2) |
|
{ |
|
// If the arrays overlap partially with the destination higher |
|
// than the source and we transform the coordinates normally |
|
// we would overwrite some of the later source coordinates |
|
// with results of previous transformations. |
|
// To get around this we use arraycopy to copy the points |
|
// to their final destination with correct overwrite |
|
// handling and then transform them in place in the new |
|
// safer location. |
|
System.arraycopy(srcPts, srcOff, dstPts, dstOff, numPts * 2); |
|
// srcPts = dstPts; // They are known to be equal. |
|
srcOff = dstOff; |
|
} |
|
switch (state) { |
|
default: |
|
stateError(); |
|
/* NOTREACHED */ |
|
return; |
|
case (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): |
|
case (APPLY_SHEAR | APPLY_SCALE): |
|
M00 = m00; M01 = m01; |
|
M10 = m10; M11 = m11; |
|
while (--numPts >= 0) { |
|
double x = srcPts[srcOff++]; |
|
double y = srcPts[srcOff++]; |
|
dstPts[dstOff++] = x * M00 + y * M01; |
|
dstPts[dstOff++] = x * M10 + y * M11; |
|
} |
|
return; |
|
case (APPLY_SHEAR | APPLY_TRANSLATE): |
|
case (APPLY_SHEAR): |
|
M01 = m01; M10 = m10; |
|
while (--numPts >= 0) { |
|
double x = srcPts[srcOff++]; |
|
dstPts[dstOff++] = srcPts[srcOff++] * M01; |
|
dstPts[dstOff++] = x * M10; |
|
} |
|
return; |
|
case (APPLY_SCALE | APPLY_TRANSLATE): |
|
case (APPLY_SCALE): |
|
M00 = m00; M11 = m11; |
|
while (--numPts >= 0) { |
|
dstPts[dstOff++] = srcPts[srcOff++] * M00; |
|
dstPts[dstOff++] = srcPts[srcOff++] * M11; |
|
} |
|
return; |
|
case (APPLY_TRANSLATE): |
|
case (APPLY_IDENTITY): |
|
if (srcPts != dstPts || srcOff != dstOff) { |
|
System.arraycopy(srcPts, srcOff, dstPts, dstOff, |
|
numPts * 2); |
|
} |
|
return; |
|
} |
|
/* NOTREACHED */ |
|
} |
|
/** |
|
* Returns a new {@link Shape} object defined by the geometry of the |
|
* specified <code>Shape</code> after it has been transformed by |
|
* this transform. |
|
* @param pSrc the specified <code>Shape</code> object to be |
|
* transformed by this transform. |
|
* @return a new <code>Shape</code> object that defines the geometry |
|
* of the transformed <code>Shape</code>, or null if {@code pSrc} is null. |
|
* @since 1.2 |
|
*/ |
|
public Shape createTransformedShape(Shape pSrc) { |
|
if (pSrc == null) { |
|
return null; |
|
} |
|
return new Path2D.Double(pSrc, this); |
|
} |
|
// Round values to sane precision for printing |
|
// Note that Math.sin(Math.PI) has an error of about 10^-16 |
|
private static double _matround(double matval) { |
|
return Math.rint(matval * 1E15) / 1E15; |
|
} |
|
/** |
|
* Returns a <code>String</code> that represents the value of this |
|
* {@link Object}. |
|
* @return a <code>String</code> representing the value of this |
|
* <code>Object</code>. |
|
* @since 1.2 |
|
*/ |
|
public String toString() { |
|
return ("AffineTransform[[" |
|
+ _matround(m00) + ", " |
|
+ _matround(m01) + ", " |
|
+ _matround(m02) + "], [" |
|
+ _matround(m10) + ", " |
|
+ _matround(m11) + ", " |
|
+ _matround(m12) + "]]"); |
|
} |
|
/** |
|
* Returns <code>true</code> if this <code>AffineTransform</code> is |
|
* an identity transform. |
|
* @return <code>true</code> if this <code>AffineTransform</code> is |
|
* an identity transform; <code>false</code> otherwise. |
|
* @since 1.2 |
|
*/ |
|
public boolean isIdentity() { |
|
return (state == APPLY_IDENTITY || (getType() == TYPE_IDENTITY)); |
|
} |
|
/** |
|
* Returns a copy of this <code>AffineTransform</code> object. |
|
* @return an <code>Object</code> that is a copy of this |
|
* <code>AffineTransform</code> object. |
|
* @since 1.2 |
|
*/ |
|
public Object clone() { |
|
try { |
|
return super.clone(); |
|
} catch (CloneNotSupportedException e) { |
|
// this shouldn't happen, since we are Cloneable |
|
throw new InternalError(e); |
|
} |
|
} |
|
/** |
|
* Returns the hashcode for this transform. |
|
* @return a hash code for this transform. |
|
* @since 1.2 |
|
*/ |
|
public int hashCode() { |
|
long bits = Double.doubleToLongBits(m00); |
|
bits = bits * 31 + Double.doubleToLongBits(m01); |
|
bits = bits * 31 + Double.doubleToLongBits(m02); |
|
bits = bits * 31 + Double.doubleToLongBits(m10); |
|
bits = bits * 31 + Double.doubleToLongBits(m11); |
|
bits = bits * 31 + Double.doubleToLongBits(m12); |
|
return (((int) bits) ^ ((int) (bits >> 32))); |
|
} |
|
/** |
|
* Returns <code>true</code> if this <code>AffineTransform</code> |
|
* represents the same affine coordinate transform as the specified |
|
* argument. |
|
* @param obj the <code>Object</code> to test for equality with this |
|
* <code>AffineTransform</code> |
|
* @return <code>true</code> if <code>obj</code> equals this |
|
* <code>AffineTransform</code> object; <code>false</code> otherwise. |
|
* @since 1.2 |
|
*/ |
|
public boolean equals(Object obj) { |
|
if (!(obj instanceof AffineTransform)) { |
|
return false; |
|
} |
|
AffineTransform a = (AffineTransform)obj; |
|
return ((m00 == a.m00) && (m01 == a.m01) && (m02 == a.m02) && |
|
(m10 == a.m10) && (m11 == a.m11) && (m12 == a.m12)); |
|
} |
|
/* Serialization support. A readObject method is neccessary because |
|
* the state field is part of the implementation of this particular |
|
* AffineTransform and not part of the public specification. The |
|
* state variable's value needs to be recalculated on the fly by the |
|
* readObject method as it is in the 6-argument matrix constructor. |
|
*/ |
|
/* |
|
* JDK 1.2 serialVersionUID |
|
*/ |
|
private static final long serialVersionUID = 1330973210523860834L; |
|
private void writeObject(java.io.ObjectOutputStream s) |
|
throws java.lang.ClassNotFoundException, java.io.IOException |
|
{ |
|
s.defaultWriteObject(); |
|
} |
|
private void readObject(java.io.ObjectInputStream s) |
|
throws java.lang.ClassNotFoundException, java.io.IOException |
|
{ |
|
s.defaultReadObject(); |
|
updateState(); |
|
} |
|
} |