com.sun.javafx.geom.transform.Affine2D Maven / Gradle / Ivy
/*
* Copyright (c) 1996, 2022, 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 com.sun.javafx.geom.transform;
import com.sun.javafx.geom.Point2D;
/**
* The Affine2D
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.
*
* 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:
*
* [ x'] [ m00 m01 m02 ] [ x ] [ m00x + m01y + m02 ]
* [ y'] = [ m10 m11 m12 ] [ y ] = [ m10x + m11y + m12 ]
* [ 1 ] [ 0 0 1 ] [ 1 ] [ 1 ]
*
*
* Handling 90-Degree Rotations
*
* In some variations of the rotate
methods in the
* Affine2D
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.
*
* Since 90 degrees is represented as PI/2
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 PI/2
but never close enough
* for the sine or cosine to be exactly 0.0, 1.0 or -1.0.
* The implementations of Math.sin()
and
* Math.cos()
correspondingly never return 0.0
* for any case other than Math.sin(0.0)
.
* 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.
*
* The net result of these issues is that if the
* Math.sin()
and Math.cos()
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 rotate(Math.PI/2.0)
,
* 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.
*
* 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 theta as a quadrant
* rotation if either Math.sin(theta)
or
* Math.cos(theta)
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 Math.PI/2.0
.
*
* @version 1.83, 05/05/07
*/
public class Affine2D extends AffineBase {
private Affine2D(double mxx, double myx,
double mxy, double myy,
double mxt, double myt,
int state)
{
this.mxx = mxx;
this.myx = myx;
this.mxy = mxy;
this.myy = myy;
this.mxt = mxt;
this.myt = myt;
this.state = state;
this.type = TYPE_UNKNOWN;
}
/**
* Constructs a new Affine2D
representing the
* Identity transformation.
*/
public Affine2D() {
mxx = myy = 1.0;
// m01 = m10 = m02 = m12 = 0.0; /* Not needed. */
// state = APPLY_IDENTITY; /* Not needed. */
// type = TYPE_IDENTITY; /* Not needed. */
}
/**
* Constructs a new Affine2D
that uses the same transform
* as the specified BaseTransform
object.
* @param Tx the BaseTransform
object to copy
*/
public Affine2D(BaseTransform Tx) {
setTransform(Tx);
}
/**
* Constructs a new Affine2D
from 6 floating point
* values representing the 6 specifiable entries of the 3x3
* transformation matrix.
*
* @param mxx the X coordinate scaling element of the 3x3 matrix
* @param myx the Y coordinate shearing element of the 3x3 matrix
* @param mxy the X coordinate shearing element of the 3x3 matrix
* @param myy the Y coordinate scaling element of the 3x3 matrix
* @param mxt the X coordinate translation element of the 3x3 matrix
* @param myt the Y coordinate translation element of the 3x3 matrix
*/
public Affine2D(float mxx, float myx,
float mxy, float myy,
float mxt, float myt)
{
this.mxx = mxx;
this.myx = myx;
this.mxy = mxy;
this.myy = myy;
this.mxt = mxt;
this.myt = myt;
updateState2D();
}
/**
* Constructs a new Affine2D
from 6 double
* precision values representing the 6 specifiable entries of the 3x3
* transformation matrix.
*
* @param mxx the X coordinate scaling element of the 3x3 matrix
* @param myx the Y coordinate shearing element of the 3x3 matrix
* @param mxy the X coordinate shearing element of the 3x3 matrix
* @param myy the Y coordinate scaling element of the 3x3 matrix
* @param mxt the X coordinate translation element of the 3x3 matrix
* @param myt the Y coordinate translation element of the 3x3 matrix
*/
public Affine2D(double mxx, double myx,
double mxy, double myy,
double mxt, double myt)
{
this.mxx = mxx;
this.myx = myx;
this.mxy = mxy;
this.myy = myy;
this.mxt = mxt;
this.myt = myt;
updateState2D();
}
@Override
public Degree getDegree() {
return Degree.AFFINE_2D;
}
@Override
protected void reset3Delements() { /* NOP for Affine2D */ }
/**
* 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).
*
* This operation is equivalent to the following sequence of calls:
*
* translate(anchorx, anchory); // S3: final translation
* rotate(theta); // S2: rotate around anchor
* translate(-anchorx, -anchory); // S1: translate anchor to origin
*
* Rotating by a positive angle theta rotates points on the positive
* X axis toward the positive Y axis.
* Note also the discussion of
* Handling 90-Degree Rotations
* 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
*/
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 vecx
and vecy
are 0.0,
* no additional rotation is added to this transform.
* This operation is equivalent to calling:
*
* rotate(Math.atan2(vecy, vecx));
*
*
* @param vecx the X coordinate of the rotation vector
* @param vecy the Y coordinate of the rotation vector
*/
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 = mxx;
M1 = mxy;
mxx = cos * M0 + sin * M1;
mxy = -sin * M0 + cos * M1;
M0 = myx;
M1 = myy;
myx = cos * M0 + sin * M1;
myy = -sin * M0 + cos * M1;
updateState2D();
}
}
/**
* 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 vecx
and vecy
are 0.0,
* the transform is not modified in any way.
* This method is equivalent to calling:
*
* rotate(Math.atan2(vecy, vecx), anchorx, anchory);
*
*
* @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
*/
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:
*
* rotate(numquadrants * Math.PI / 2.0);
*
* 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
*/
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:
*
* rotate(numquadrants * Math.PI / 2.0, anchorx, anchory);
*
* 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
*/
public void quadrantRotate(int numquadrants,
double anchorx, double anchory)
{
switch (numquadrants & 3) {
case 0:
return;
case 1:
mxt += anchorx * (mxx - mxy) + anchory * (mxy + mxx);
myt += anchorx * (myx - myy) + anchory * (myy + myx);
rotate90();
break;
case 2:
mxt += anchorx * (mxx + mxx) + anchory * (mxy + mxy);
myt += anchorx * (myx + myx) + anchory * (myy + myy);
rotate180();
break;
case 3:
mxt += anchorx * (mxx + mxy) + anchory * (mxy - mxx);
myt += anchorx * (myx + myy) + anchory * (myy - myx);
rotate270();
break;
}
if (mxt == 0.0 && myt == 0.0) {
state &= ~APPLY_TRANSLATE;
if (type != TYPE_UNKNOWN) {
type &= ~TYPE_TRANSLATION;
}
} else {
state |= APPLY_TRANSLATE;
type |= TYPE_TRANSLATION;
}
}
/**
* Sets this transform to a translation transformation.
* The matrix representing this transform becomes:
*
* [ 1 0 tx ]
* [ 0 1 ty ]
* [ 0 0 1 ]
*
* @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
*/
public void setToTranslation(double tx, double ty) {
mxx = 1.0;
myx = 0.0;
mxy = 0.0;
myy = 1.0;
mxt = tx;
myt = 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:
*
* [ cos(theta) -sin(theta) 0 ]
* [ sin(theta) cos(theta) 0 ]
* [ 0 0 1 ]
*
* Rotating by a positive angle theta rotates points on the positive
* X axis toward the positive Y axis.
* Note also the discussion of
* Handling 90-Degree Rotations
* above.
* @param theta the angle of rotation measured in radians
*/
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;
}
}
mxx = cos;
myx = sin;
mxy = -sin;
myy = cos;
mxt = 0.0;
myt = 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).
*
* This operation is equivalent to the following sequence of calls:
*
* setToTranslation(anchorx, anchory); // S3: final translation
* rotate(theta); // S2: rotate around anchor
* translate(-anchorx, -anchory); // S1: translate anchor to origin
*
* The matrix representing this transform becomes:
*
* [ cos(theta) -sin(theta) x-x*cos+y*sin ]
* [ sin(theta) cos(theta) y-x*sin-y*cos ]
* [ 0 0 1 ]
*
* Rotating by a positive angle theta rotates points on the positive
* X axis toward the positive Y axis.
* Note also the discussion of
* Handling 90-Degree Rotations
* 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
*/
public void setToRotation(double theta, double anchorx, double anchory) {
setToRotation(theta);
double sin = myx;
double oneMinusCos = 1.0 - mxx;
mxt = anchorx * oneMinusCos + anchory * sin;
myt = anchory * oneMinusCos - anchorx * sin;
if (mxt != 0.0 || myt != 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 vecx
and vecy
are 0.0,
* the transform is set to an identity transform.
* This operation is equivalent to calling:
*
* setToRotation(Math.atan2(vecy, vecx));
*
*
* @param vecx the X coordinate of the rotation vector
* @param vecy the Y coordinate of the rotation vector
*/
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;
}
mxx = cos;
myx = sin;
mxy = -sin;
myy = cos;
mxt = 0.0;
myt = 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 vecx
and vecy
are 0.0,
* the transform is set to an identity transform.
* This operation is equivalent to calling:
*
* setToTranslation(Math.atan2(vecy, vecx), anchorx, anchory);
*
*
* @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
*/
public void setToRotation(double vecx, double vecy,
double anchorx, double anchory)
{
setToRotation(vecx, vecy);
double sin = myx;
double oneMinusCos = 1.0 - mxx;
mxt = anchorx * oneMinusCos + anchory * sin;
myt = anchory * oneMinusCos - anchorx * sin;
if (mxt != 0.0 || myt != 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:
*
* setToRotation(numquadrants * Math.PI / 2.0);
*
* 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
*/
public void setToQuadrantRotation(int numquadrants) {
switch (numquadrants & 3) {
case 0:
mxx = 1.0;
myx = 0.0;
mxy = 0.0;
myy = 1.0;
mxt = 0.0;
myt = 0.0;
state = APPLY_IDENTITY;
type = TYPE_IDENTITY;
break;
case 1:
mxx = 0.0;
myx = 1.0;
mxy = -1.0;
myy = 0.0;
mxt = 0.0;
myt = 0.0;
state = APPLY_SHEAR;
type = TYPE_QUADRANT_ROTATION;
break;
case 2:
mxx = -1.0;
myx = 0.0;
mxy = 0.0;
myy = -1.0;
mxt = 0.0;
myt = 0.0;
state = APPLY_SCALE;
type = TYPE_QUADRANT_ROTATION;
break;
case 3:
mxx = 0.0;
myx = -1.0;
mxy = 1.0;
myy = 0.0;
mxt = 0.0;
myt = 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:
*
* setToRotation(numquadrants * Math.PI / 2.0, anchorx, anchory);
*
* 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
*/
public void setToQuadrantRotation(int numquadrants,
double anchorx, double anchory)
{
switch (numquadrants & 3) {
case 0:
mxx = 1.0;
myx = 0.0;
mxy = 0.0;
myy = 1.0;
mxt = 0.0;
myt = 0.0;
state = APPLY_IDENTITY;
type = TYPE_IDENTITY;
break;
case 1:
mxx = 0.0;
myx = 1.0;
mxy = -1.0;
myy = 0.0;
mxt = anchorx + anchory;
myt = anchory - anchorx;
if (mxt == 0.0 && myt == 0.0) {
state = APPLY_SHEAR;
type = TYPE_QUADRANT_ROTATION;
} else {
state = APPLY_SHEAR | APPLY_TRANSLATE;
type = TYPE_QUADRANT_ROTATION | TYPE_TRANSLATION;
}
break;
case 2:
mxx = -1.0;
myx = 0.0;
mxy = 0.0;
myy = -1.0;
mxt = anchorx + anchorx;
myt = anchory + anchory;
if (mxt == 0.0 && myt == 0.0) {
state = APPLY_SCALE;
type = TYPE_QUADRANT_ROTATION;
} else {
state = APPLY_SCALE | APPLY_TRANSLATE;
type = TYPE_QUADRANT_ROTATION | TYPE_TRANSLATION;
}
break;
case 3:
mxx = 0.0;
myx = -1.0;
mxy = 1.0;
myy = 0.0;
mxt = anchorx - anchory;
myt = anchory + anchorx;
if (mxt == 0.0 && myt == 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:
*
* [ sx 0 0 ]
* [ 0 sy 0 ]
* [ 0 0 1 ]
*
* @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
*/
public void setToScale(double sx, double sy) {
mxx = sx;
myx = 0.0;
mxy = 0.0;
myy = sy;
mxt = 0.0;
myt = 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 copy of the transform in the specified
* BaseTransform
object.
* @param Tx the BaseTransform
object from which to
* copy the transform
*/
@Override
public void setTransform(BaseTransform Tx) {
switch (Tx.getDegree()) {
case IDENTITY:
setToIdentity();
break;
case TRANSLATE_2D:
setToTranslation(Tx.getMxt(), Tx.getMyt());
break;
default:
if (Tx.getType() > TYPE_AFFINE2D_MASK) {
System.out.println(Tx+" is "+Tx.getType());
System.out.print(" "+Tx.getMxx());
System.out.print(", "+Tx.getMxy());
System.out.print(", "+Tx.getMxz());
System.out.print(", "+Tx.getMxt());
System.out.println();
System.out.print(" "+Tx.getMyx());
System.out.print(", "+Tx.getMyy());
System.out.print(", "+Tx.getMyz());
System.out.print(", "+Tx.getMyt());
System.out.println();
System.out.print(" "+Tx.getMzx());
System.out.print(", "+Tx.getMzy());
System.out.print(", "+Tx.getMzz());
System.out.print(", "+Tx.getMzt());
System.out.println();
// TODO: Should this be thrown before we modify anything?
// (RT-26801)
degreeError(Degree.AFFINE_2D);
}
/* No Break */
case AFFINE_2D:
this.mxx = Tx.getMxx();
this.myx = Tx.getMyx();
this.mxy = Tx.getMxy();
this.myy = Tx.getMyy();
this.mxt = Tx.getMxt();
this.myt = Tx.getMyt();
if (Tx instanceof AffineBase) {
this.state = ((AffineBase) Tx).state;
this.type = ((AffineBase) Tx).type;
} else {
updateState2D();
}
break;
}
}
/**
* Concatenates a BaseTransform
Tx
to
* this Affine2D
Cx
* in a less commonly used way such that Tx
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
* Tx
like this:
* Cx'(p) = Tx(Cx(p))
* In matrix notation, if this transform Cx
* is represented by the matrix [this] and Tx
is
* represented by the matrix [Tx] then this method does the
* following:
*
* [this] = [Tx] x [this]
*
* @param Tx the BaseTransform
object to be
* concatenated with this Affine2D
object.
* @see #concatenate
*/
public void preConcatenate(BaseTransform Tx) {
switch (Tx.getDegree()) {
case IDENTITY:
return;
case TRANSLATE_2D:
translate(Tx.getMxt(), Tx.getMyt());
return;
case AFFINE_2D:
break;
default:
degreeError(Degree.AFFINE_2D);
}
double M0, M1;
double Txx, Txy, Tyx, Tyy;
double Txt, Tyt;
int mystate = state;
Affine2D at = (Affine2D) Tx;
int txstate = at.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
mxt = at.mxt;
myt = at.myt;
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
mxt = mxt + at.mxt;
myt = myt + at.myt;
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
Txx = at.mxx;
Tyy = at.myy;
if ((mystate & APPLY_SHEAR) != 0) {
mxy = mxy * Txx;
myx = myx * Tyy;
if ((mystate & APPLY_SCALE) != 0) {
mxx = mxx * Txx;
myy = myy * Tyy;
}
} else {
mxx = mxx * Txx;
myy = myy * Tyy;
}
if ((mystate & APPLY_TRANSLATE) != 0) {
mxt = mxt * Txx;
myt = myt * Tyy;
}
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
Txy = at.mxy;
Tyx = at.myx;
M0 = mxx;
mxx = myx * Txy;
myx = M0 * Tyx;
M0 = mxy;
mxy = myy * Txy;
myy = M0 * Tyx;
M0 = mxt;
mxt = myt * Txy;
myt = M0 * Tyx;
type = TYPE_UNKNOWN;
return;
}
// If Tx has more than one attribute, it is not worth optimizing
// all of those cases...
Txx = at.mxx; Txy = at.mxy; Txt = at.mxt;
Tyx = at.myx; Tyy = at.myy; Tyt = at.myt;
switch (mystate) {
default:
stateError();
/* NOTREACHED */
case (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE):
M0 = mxt;
M1 = myt;
Txt += M0 * Txx + M1 * Txy;
Tyt += M0 * Tyx + M1 * Tyy;
/* NOBREAK */
case (APPLY_SHEAR | APPLY_SCALE):
mxt = Txt;
myt = Tyt;
M0 = mxx;
M1 = myx;
mxx = M0 * Txx + M1 * Txy;
myx = M0 * Tyx + M1 * Tyy;
M0 = mxy;
M1 = myy;
mxy = M0 * Txx + M1 * Txy;
myy = M0 * Tyx + M1 * Tyy;
break;
case (APPLY_SHEAR | APPLY_TRANSLATE):
M0 = mxt;
M1 = myt;
Txt += M0 * Txx + M1 * Txy;
Tyt += M0 * Tyx + M1 * Tyy;
/* NOBREAK */
case (APPLY_SHEAR):
mxt = Txt;
myt = Tyt;
M0 = myx;
mxx = M0 * Txy;
myx = M0 * Tyy;
M0 = mxy;
mxy = M0 * Txx;
myy = M0 * Tyx;
break;
case (APPLY_SCALE | APPLY_TRANSLATE):
M0 = mxt;
M1 = myt;
Txt += M0 * Txx + M1 * Txy;
Tyt += M0 * Tyx + M1 * Tyy;
/* NOBREAK */
case (APPLY_SCALE):
mxt = Txt;
myt = Tyt;
M0 = mxx;
mxx = M0 * Txx;
myx = M0 * Tyx;
M0 = myy;
mxy = M0 * Txy;
myy = M0 * Tyy;
break;
case (APPLY_TRANSLATE):
M0 = mxt;
M1 = myt;
Txt += M0 * Txx + M1 * Txy;
Tyt += M0 * Tyx + M1 * Tyy;
/* NOBREAK */
case (APPLY_IDENTITY):
mxt = Txt;
myt = Tyt;
mxx = Txx;
myx = Tyx;
mxy = Txy;
myy = Tyy;
state = mystate | txstate;
type = TYPE_UNKNOWN;
return;
}
updateState2D();
}
/**
* Returns an Affine2D
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)).
*
* 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 getDeterminant
method can be used to determine if this
* transform has no inverse, in which case an exception will be
* thrown if the createInverse
method is called.
* @return a new Affine2D
object representing the
* inverse transformation.
* @see #getDeterminant
* @exception NoninvertibleTransformException
* if the matrix cannot be inverted.
*/
@Override
public Affine2D createInverse()
throws NoninvertibleTransformException
{
double det;
switch (state) {
default:
stateError();
/* NOTREACHED */
case (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE):
det = mxx * myy - mxy * myx;
if (det == 0 || Math.abs(det) <= Double.MIN_VALUE) {
throw new NoninvertibleTransformException("Determinant is "+
det);
}
return new Affine2D( myy / det, -myx / det,
-mxy / det, mxx / det,
(mxy * myt - myy * mxt) / det,
(myx * mxt - mxx * myt) / det,
(APPLY_SHEAR |
APPLY_SCALE |
APPLY_TRANSLATE));
case (APPLY_SHEAR | APPLY_SCALE):
det = mxx * myy - mxy * myx;
if (det == 0 || Math.abs(det) <= Double.MIN_VALUE) {
throw new NoninvertibleTransformException("Determinant is "+
det);
}
return new Affine2D( myy / det, -myx / det,
-mxy / det, mxx / det,
0.0, 0.0,
(APPLY_SHEAR | APPLY_SCALE));
case (APPLY_SHEAR | APPLY_TRANSLATE):
if (mxy == 0.0 || myx == 0.0) {
throw new NoninvertibleTransformException("Determinant is 0");
}
return new Affine2D( 0.0, 1.0 / mxy,
1.0 / myx, 0.0,
-myt / myx, -mxt / mxy,
(APPLY_SHEAR | APPLY_TRANSLATE));
case (APPLY_SHEAR):
if (mxy == 0.0 || myx == 0.0) {
throw new NoninvertibleTransformException("Determinant is 0");
}
return new Affine2D(0.0, 1.0 / mxy,
1.0 / myx, 0.0,
0.0, 0.0,
(APPLY_SHEAR));
case (APPLY_SCALE | APPLY_TRANSLATE):
if (mxx == 0.0 || myy == 0.0) {
throw new NoninvertibleTransformException("Determinant is 0");
}
return new Affine2D( 1.0 / mxx, 0.0,
0.0, 1.0 / myy,
-mxt / mxx, -myt / myy,
(APPLY_SCALE | APPLY_TRANSLATE));
case (APPLY_SCALE):
if (mxx == 0.0 || myy == 0.0) {
throw new NoninvertibleTransformException("Determinant is 0");
}
return new Affine2D(1.0 / mxx, 0.0,
0.0, 1.0 / myy,
0.0, 0.0,
(APPLY_SCALE));
case (APPLY_TRANSLATE):
return new Affine2D( 1.0, 0.0,
0.0, 1.0,
-mxt, -myt,
(APPLY_TRANSLATE));
case (APPLY_IDENTITY):
return new Affine2D();
}
/* NOTREACHED */
}
/**
* Transforms an array of point objects by this transform.
* If any element of the ptDst
array is
* null
, a new Point2D
object is allocated
* and stored into that element before storing the results of the
* transformation.
*
* Note that this method does not take any precautions to
* avoid problems caused by storing results into Point2D
* objects that will be used as the source for calculations
* further down the source array.
* This method does guarantee that if a specified Point2D
* 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 Point2D
object for one
* operation is the same object as the source Point2D
* 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
*/
public void transform(Point2D[] ptSrc, int srcOff,
Point2D[] ptDst, int dstOff,
int numPts) {
int mystate = this.state;
while (--numPts >= 0) {
// Copy source coords into local variables in case src == dst
Point2D src = ptSrc[srcOff++];
double x = src.x;
double y = src.y;
Point2D dst = ptDst[dstOff++];
if (dst == null) {
dst = new Point2D();
ptDst[dstOff - 1] = dst;
}
switch (mystate) {
default:
stateError();
/* NOTREACHED */
case (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE):
dst.setLocation((float)(x * mxx + y * mxy + mxt),
(float)(x * myx + y * myy + myt));
break;
case (APPLY_SHEAR | APPLY_SCALE):
dst.setLocation((float)(x * mxx + y * mxy),
(float)(x * myx + y * myy));
break;
case (APPLY_SHEAR | APPLY_TRANSLATE):
dst.setLocation((float)(y * mxy + mxt),
(float)(x * myx + myt));
break;
case (APPLY_SHEAR):
dst.setLocation((float)(y * mxy), (float)(x * myx));
break;
case (APPLY_SCALE | APPLY_TRANSLATE):
dst.setLocation((float)(x * mxx + mxt), (float)(y * myy + myt));
break;
case (APPLY_SCALE):
dst.setLocation((float)(x * mxx), (float)(y * myy));
break;
case (APPLY_TRANSLATE):
dst.setLocation((float)(x + mxt), (float)(y + myt));
break;
case (APPLY_IDENTITY):
dst.setLocation((float) x, (float) y);
break;
}
}
/* NOTREACHED */
}
/**
* Transforms the relative distance vector specified by
* ptSrc
and stores the result in ptDst
.
* A relative distance vector is transformed without applying the
* translation components of the affine transformation matrix
* using the following equations:
*
* [ x' ] [ m00 m01 (m02) ] [ x ] [ m00x + m01y ]
* [ y' ] = [ m10 m11 (m12) ] [ y ] = [ m10x + m11y ]
* [ (1) ] [ (0) (0) ( 1 ) ] [ (1) ] [ (1) ]
*
* If ptDst
is null
, a new
* Point2D
object is allocated and then the result of the
* transform is stored in this object.
* In either case, ptDst
, which contains the
* transformed point, is returned for convenience.
* If ptSrc
and ptDst
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 ptDst
, which contains the result of the
* transformation.
*/
public Point2D deltaTransform(Point2D ptSrc, Point2D ptDst) {
if (ptDst == null) {
ptDst = new Point2D();
}
// Copy source coords into local variables in case src == dst
double x = ptSrc.x;
double y = ptSrc.y;
switch (state) {
default:
stateError();
/* NOTREACHED */
case (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE):
case (APPLY_SHEAR | APPLY_SCALE):
ptDst.setLocation((float)(x * mxx + y * mxy), (float)(x * myx + y * myy));
return ptDst;
case (APPLY_SHEAR | APPLY_TRANSLATE):
case (APPLY_SHEAR):
ptDst.setLocation((float)(y * mxy), (float)(x * myx));
return ptDst;
case (APPLY_SCALE | APPLY_TRANSLATE):
case (APPLY_SCALE):
ptDst.setLocation((float)(x * mxx), (float)(y * myy));
return ptDst;
case (APPLY_TRANSLATE):
case (APPLY_IDENTITY):
ptDst.setLocation((float) x, (float) y);
return ptDst;
}
/* NOTREACHED */
}
// 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 String
that represents the value of this
* {@link Object}.
* @return a String
representing the value of this
* Object
.
*/
@Override
public String toString() {
return ("Affine2D[["
+ _matround(mxx) + ", "
+ _matround(mxy) + ", "
+ _matround(mxt) + "], ["
+ _matround(myx) + ", "
+ _matround(myy) + ", "
+ _matround(myt) + "]]");
}
@Override
public boolean is2D() {
return true;
}
@Override
public void restoreTransform(double mxx, double myx,
double mxy, double myy,
double mxt, double myt)
{
setTransform(mxx, myx, mxy, myy, mxt, myt);
}
@Override
public void restoreTransform(double mxx, double mxy, double mxz, double mxt,
double myx, double myy, double myz, double myt,
double mzx, double mzy, double mzz, double mzt)
{
if ( mxz != 0 ||
myz != 0 ||
mzx != 0 || mzy != 0 || mzz != 1 || mzt != 0.0)
{
degreeError(Degree.AFFINE_2D);
}
setTransform(mxx, myx, mxy, myy, mxt, myt);
}
@Override
public BaseTransform deriveWithTranslation(double mxt, double myt) {
translate(mxt, myt);
return this;
}
@Override
public BaseTransform deriveWithTranslation(double mxt, double myt, double mzt) {
if (mzt == 0.0) {
translate(mxt, myt);
return this;
}
Affine3D a = new Affine3D(this);
a.translate(mxt, myt, mzt);
return a;
}
@Override
public BaseTransform deriveWithScale(double mxx, double myy, double mzz) {
if (mzz == 1.0) {
scale(mxx, myy);
return this;
}
Affine3D a = new Affine3D(this);
a.scale(mxx, myy, mzz);
return a;
}
@Override
public BaseTransform deriveWithRotation(double theta,
double axisX, double axisY, double axisZ) {
if (theta == 0.0) {
return this;
}
if (almostZero(axisX) && almostZero(axisY)) {
if (axisZ > 0) {
rotate(theta);
} else if (axisZ < 0) {
rotate(-theta);
} // else rotating about zero vector - NOP
return this;
}
Affine3D a = new Affine3D(this);
a.rotate(theta, axisX, axisY, axisZ);
return a;
}
@Override
public BaseTransform deriveWithPreTranslation(double mxt, double myt) {
this.mxt += mxt;
this.myt += myt;
if (this.mxt != 0.0 || this.myt != 0.0) {
state |= APPLY_TRANSLATE;
// if (type != TYPE_UNKNOWN) {
type |= TYPE_TRANSLATION;
// }
} else {
state &= ~APPLY_TRANSLATE;
if (type != TYPE_UNKNOWN) {
type &= ~TYPE_TRANSLATION;
}
}
return this;
}
@Override
public BaseTransform deriveWithConcatenation(double mxx, double myx,
double mxy, double myy,
double mxt, double myt)
{
// TODO: Simplify this (RT-26801)
BaseTransform tmpTx = getInstance(mxx, myx,
mxy, myy,
mxt, myt);
concatenate(tmpTx);
return this;
}
@Override
public BaseTransform deriveWithConcatenation(
double mxx, double mxy, double mxz, double mxt,
double myx, double myy, double myz, double myt,
double mzx, double mzy, double mzz, double mzt) {
if ( mxz == 0.0
&& myz == 0.0
&& mzx == 0.0 && mzy == 0.0 && mzz == 1.0 && mzt == 0.0) {
concatenate(mxx, mxy,
mxt, myx,
myy, myt);
return this;
}
Affine3D t3d = new Affine3D(this);
t3d.concatenate(mxx, mxy, mxz, mxt,
myx, myy, myz, myt,
mzx, mzy, mzz, mzt);
return t3d;
}
@Override
public BaseTransform deriveWithConcatenation(BaseTransform tx) {
if (tx.is2D()) {
concatenate(tx);
return this;
}
Affine3D t3d = new Affine3D(this);
t3d.concatenate(tx);
return t3d;
}
@Override
public BaseTransform deriveWithPreConcatenation(BaseTransform tx) {
if (tx.is2D()) {
preConcatenate(tx);
return this;
}
Affine3D t3d = new Affine3D(this);
t3d.preConcatenate(tx);
return t3d;
}
@Override
public BaseTransform deriveWithNewTransform(BaseTransform tx) {
if (tx.is2D()) {
setTransform(tx);
return this;
}
return getInstance(tx);
}
@Override
public BaseTransform copy() {
return new Affine2D(this);
}
private static final long BASE_HASH;
static {
long bits = 0;
bits = bits * 31 + Double.doubleToLongBits(IDENTITY_TRANSFORM.getMzz());
bits = bits * 31 + Double.doubleToLongBits(IDENTITY_TRANSFORM.getMzy());
bits = bits * 31 + Double.doubleToLongBits(IDENTITY_TRANSFORM.getMzx());
bits = bits * 31 + Double.doubleToLongBits(IDENTITY_TRANSFORM.getMyz());
bits = bits * 31 + Double.doubleToLongBits(IDENTITY_TRANSFORM.getMxz());
BASE_HASH = bits;
}
/**
* Returns the hashcode for this transform. The base algorithm for
* computing the hashcode is defined by the implementation in
* the {@code BaseTransform} class. This implementation is just a
* faster way of computing the same value knowing which elements of
* the transform matrix are populated.
* @return a hash code for this transform.
*/
@Override
public int hashCode() {
if (isIdentity()) return 0;
long bits = BASE_HASH;
bits = bits * 31 + Double.doubleToLongBits(getMyy());
bits = bits * 31 + Double.doubleToLongBits(getMyx());
bits = bits * 31 + Double.doubleToLongBits(getMxy());
bits = bits * 31 + Double.doubleToLongBits(getMxx());
bits = bits * 31 + Double.doubleToLongBits(0.0); // mzt
bits = bits * 31 + Double.doubleToLongBits(getMyt());
bits = bits * 31 + Double.doubleToLongBits(getMxt());
return (((int) bits) ^ ((int) (bits >> 32)));
}
/**
* Returns true
if this Affine2D
* represents the same coordinate transform as the specified
* argument.
* @param obj the Object
to test for equality with this
* Affine2D
* @return true
if obj
equals this
* Affine2D
object; false
otherwise.
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof BaseTransform) {
BaseTransform a = (BaseTransform) obj;
return (a.getType() <= TYPE_AFFINE2D_MASK &&
a.getMxx() == this.mxx &&
a.getMxy() == this.mxy &&
a.getMxt() == this.mxt &&
a.getMyx() == this.myx &&
a.getMyy() == this.myy &&
a.getMyt() == this.myt);
}
return false;
}
}