com.itextpdf.kernel.geom.AffineTransform Maven / Gradle / Ivy
/*
This file is part of the iText (R) project.
Copyright (c) 1998-2023 Apryse Group NV
Authors: Apryse Software.
This program is offered under a commercial and under the AGPL license.
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
AGPL licensing:
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
package com.itextpdf.kernel.geom;
import java.util.Objects;
public class AffineTransform implements Cloneable {
/**
* The type of affine transformation. See {@link AffineTransform#getType()}.
*/
public static final int TYPE_IDENTITY = 0;
/**
* The type of affine transformation. See {@link AffineTransform#getType()}.
*/
public static final int TYPE_TRANSLATION = 1;
/**
* The type of affine transformation. See {@link AffineTransform#getType()}.
*/
public static final int TYPE_UNIFORM_SCALE = 2;
/**
* The type of affine transformation. See {@link AffineTransform#getType()}.
*/
public static final int TYPE_GENERAL_SCALE = 4;
/**
* The type of affine transformation. See {@link AffineTransform#getType()}.
*/
public static final int TYPE_QUADRANT_ROTATION = 8;
/**
* The type of affine transformation. See {@link AffineTransform#getType()}.
*/
public static final int TYPE_GENERAL_ROTATION = 16;
/**
* The type of affine transformation. See {@link AffineTransform#getType()}.
*/
public static final int TYPE_GENERAL_TRANSFORM = 32;
/**
* The type of affine transformation. See {@link AffineTransform#getType()}.
*/
public static final int TYPE_FLIP = 64;
/**
* The type of affine transformation. See {@link AffineTransform#getType()}.
*/
public static final int TYPE_MASK_SCALE = TYPE_UNIFORM_SCALE | TYPE_GENERAL_SCALE;
/**
* The type of affine transformation. See {@link AffineTransform#getType()}.
*/
public static final int TYPE_MASK_ROTATION = TYPE_QUADRANT_ROTATION | TYPE_GENERAL_ROTATION;
/**
* The TYPE_UNKNOWN
is an initial type value.
*/
static final int TYPE_UNKNOWN = -1;
/**
* The min value equivalent to zero. If absolute value less then ZERO it considered as zero.
*/
static final double ZERO = 1E-10;
/**
* The values of transformation matrix
*/
double m00;
double m10;
double m01;
double m11;
double m02;
double m12;
/**
* The transformation type
*/
int type;
public AffineTransform() {
type = TYPE_IDENTITY;
m00 = m11 = 1;
m10 = m01 = m02 = m12 = 0;
}
public AffineTransform(AffineTransform t) {
this.type = t.type;
this.m00 = t.m00;
this.m10 = t.m10;
this.m01 = t.m01;
this.m11 = t.m11;
this.m02 = t.m02;
this.m12 = t.m12;
}
public AffineTransform(double m00, double m10, double m01, double m11, double m02, double m12) {
this.type = TYPE_UNKNOWN;
this.m00 = m00;
this.m10 = m10;
this.m01 = m01;
this.m11 = m11;
this.m02 = m02;
this.m12 = m12;
}
public AffineTransform(float[] matrix) {
this.type = TYPE_UNKNOWN;
m00 = matrix[0];
m10 = matrix[1];
m01 = matrix[2];
m11 = matrix[3];
if (matrix.length > 4) {
m02 = matrix[4];
m12 = matrix[5];
}
}
public AffineTransform(double[] matrix) {
this.type = TYPE_UNKNOWN;
m00 = matrix[0];
m10 = matrix[1];
m01 = matrix[2];
m11 = matrix[3];
if (matrix.length > 4) {
m02 = matrix[4];
m12 = matrix[5];
}
}
/**
* Method returns type of affine transformation.
*
* Transform matrix is
* m00 m01 m02
* m10 m11 m12
*
* According analytic geometry new basis vectors are (m00, m01) and (m10, m11),
* translation vector is (m02, m12). Original basis vectors are (1, 0) and (0, 1).
* Type transformations classification:
*
* - {@link AffineTransform#TYPE_IDENTITY} - new basis equals original one and zero translation
*
- {@link AffineTransform#TYPE_TRANSLATION} - translation vector isn't zero
*
- {@link AffineTransform#TYPE_UNIFORM_SCALE} - vectors length of new basis equals
*
- {@link AffineTransform#TYPE_GENERAL_SCALE} - vectors length of new basis doesn't equal
*
- {@link AffineTransform#TYPE_FLIP} - new basis vector orientation differ from original one
*
- {@link AffineTransform#TYPE_QUADRANT_ROTATION} - new basis is rotated by 90, 180, 270, or 360 degrees
*
- {@link AffineTransform#TYPE_GENERAL_ROTATION} - new basis is rotated by arbitrary angle
*
- {@link AffineTransform#TYPE_GENERAL_TRANSFORM} - transformation can't be inversed
*
*
* @return the type of this AffineTransform
*/
public int getType() {
if (this.type != TYPE_UNKNOWN) {
return this.type;
}
int type = 0;
if (m00 * m01 + m10 * m11 != 0.0) {
type |= TYPE_GENERAL_TRANSFORM;
return type;
}
if (m02 != 0.0 || m12 != 0.0) {
type |= TYPE_TRANSLATION;
} else if (m00 == 1.0 && m11 == 1.0 && m01 == 0.0 && m10 == 0.0) {
type = TYPE_IDENTITY;
return type;
}
if (m00 * m11 - m01 * m10 < 0.0) {
type |= TYPE_FLIP;
}
double dx = m00 * m00 + m10 * m10;
double dy = m01 * m01 + m11 * m11;
if (dx != dy) {
type |= TYPE_GENERAL_SCALE;
} else if (dx != 1.0) {
type |= TYPE_UNIFORM_SCALE;
}
if ((m00 == 0.0 && m11 == 0.0) ||
(m10 == 0.0 && m01 == 0.0 && (m00 < 0.0 || m11 < 0.0))) {
type |= TYPE_QUADRANT_ROTATION;
} else if (m01 != 0.0 || m10 != 0.0) {
type |= TYPE_GENERAL_ROTATION;
}
return type;
}
public double getScaleX() {
return m00;
}
public double getScaleY() {
return m11;
}
public double getShearX() {
return m01;
}
public double getShearY() {
return m10;
}
public double getTranslateX() {
return m02;
}
public double getTranslateY() {
return m12;
}
public boolean isIdentity() {
return getType() == TYPE_IDENTITY;
}
public void getMatrix(float[] matrix) {
matrix[0] = (float) m00;
matrix[1] = (float) m10;
matrix[2] = (float) m01;
matrix[3] = (float) m11;
if (matrix.length > 4) {
matrix[4] = (float) m02;
matrix[5] = (float) m12;
}
}
public void getMatrix(double[] matrix) {
matrix[0] = m00;
matrix[1] = m10;
matrix[2] = m01;
matrix[3] = m11;
if (matrix.length > 4) {
matrix[4] = m02;
matrix[5] = m12;
}
}
public double getDeterminant() {
return m00 * m11 - m01 * m10;
}
public void setTransform(float m00, float m10, float m01, float m11, float m02, float m12) {
this.type = TYPE_UNKNOWN;
this.m00 = m00;
this.m10 = m10;
this.m01 = m01;
this.m11 = m11;
this.m02 = m02;
this.m12 = m12;
}
public void setTransform(double m00, double m10, double m01, double m11, double m02, double m12) {
this.type = TYPE_UNKNOWN;
this.m00 = m00;
this.m10 = m10;
this.m01 = m01;
this.m11 = m11;
this.m02 = m02;
this.m12 = m12;
}
public void setTransform(AffineTransform t) {
type = t.type;
setTransform(t.m00, t.m10, t.m01, t.m11, t.m02, t.m12);
}
public void setToIdentity() {
type = TYPE_IDENTITY;
m00 = m11 = 1;
m10 = m01 = m02 = m12 = 0;
}
public void setToTranslation(double mx, double my) {
m00 = m11 = 1;
m01 = m10 = 0;
m02 = mx;
m12 = my;
if (mx == 0 && my == 0) {
type = TYPE_IDENTITY;
} else {
type = TYPE_TRANSLATION;
}
}
public void setToScale(double scx, double scy) {
m00 = scx;
m11 = scy;
m10 = m01 = m02 = m12 = 0;
if (scx != 1.0 || scy != 1) {
type = TYPE_UNKNOWN;
} else {
type = TYPE_IDENTITY;
}
}
public void setToShear(double shx, double shy) {
m00 = m11 = 1;
m02 = m12 = 0;
m01 = shx;
m10 = shy;
if (shx != 0.0 || shy != 0.0) {
type = TYPE_UNKNOWN;
} else {
type = TYPE_IDENTITY;
}
}
/**
* Set this affine transformation to represent a rotation over the passed angle
*
* @param angle angle to rotate over in radians
*/
public void setToRotation(double angle) {
double sin = Math.sin(angle);
double cos = Math.cos(angle);
if (Math.abs(cos) < ZERO) {
cos = 0.0;
sin = sin > 0.0 ? 1.0 : -1.0;
} else if (Math.abs(sin) < ZERO) {
sin = 0.0;
cos = cos > 0.0 ? 1.0 : -1.0;
}
m00 = m11 = (float) cos;
m01 = (float) -sin;
m10 = (float) sin;
m02 = m12 = 0;
type = TYPE_UNKNOWN;
}
/**
* Set this affine transformation to represent a rotation over the passed angle,
* using the passed point as the center of rotation
*
* @param angle angle to rotate over in radians
* @param px x-coordinate of center of rotation
* @param py y-coordinate of center of rotation
*/
public void setToRotation(double angle, double px, double py) {
setToRotation(angle);
m02 = px * (1 - m00) + py * m10;
m12 = py * (1 - m00) - px * m10;
type = TYPE_UNKNOWN;
}
public static AffineTransform getTranslateInstance(double mx, double my) {
AffineTransform t = new AffineTransform();
t.setToTranslation(mx, my);
return t;
}
public static AffineTransform getScaleInstance(double scx, double scY) {
AffineTransform t = new AffineTransform();
t.setToScale(scx, scY);
return t;
}
public static AffineTransform getShearInstance(double shx, double shy) {
AffineTransform m = new AffineTransform();
m.setToShear(shx, shy);
return m;
}
/**
* Get an affine transformation representing a counter-clockwise rotation over the passed angle
*
* @param angle angle in radians to rotate over
* @return {@link AffineTransform} representing the rotation
*/
public static AffineTransform getRotateInstance(double angle) {
AffineTransform t = new AffineTransform();
t.setToRotation(angle);
return t;
}
/**
* Get an affine transformation representing a counter-clockwise rotation over the passed angle,
* using the passed point as the center of rotation
*
* @param angle angle in radians to rotate over
* @param x x-coordinate of center of rotation
* @param y y-coordinate of center of rotation
* @return {@link AffineTransform} representing the rotation
*/
public static AffineTransform getRotateInstance(double angle, double x, double y) {
AffineTransform t = new AffineTransform();
t.setToRotation(angle, x, y);
return t;
}
public void translate(double mx, double my) {
concatenate(AffineTransform.getTranslateInstance(mx, my));
}
public void scale(double scx, double scy) {
concatenate(AffineTransform.getScaleInstance(scx, scy));
}
public void shear(double shx, double shy) {
concatenate(AffineTransform.getShearInstance(shx, shy));
}
/**
* Add a counter-clockwise rotation to this transformation
*
* @param angle angle in radians to rotate over
*/
public void rotate(double angle) {
concatenate(AffineTransform.getRotateInstance(angle));
}
/**
* Add a counter-clockwise rotation to this transformation,
* using the passed point as the center of rotation
* @param angle angle in radians to rotate over
* @param px x-coordinate of center of rotation
* @param py y-coordinate of center of rotation
*/
public void rotate(double angle, double px, double py) {
concatenate(AffineTransform.getRotateInstance(angle, px, py));
}
/**
* Multiply matrix of two AffineTransform objects
*
* @param t1 - the AffineTransform object is a multiplicand
* @param t2 - the AffineTransform object is a multiplier
* @return an AffineTransform object that is a result of t1 multiplied by matrix t2.
*/
AffineTransform multiply(AffineTransform t1, AffineTransform t2) {
return new AffineTransform(
t1.m00 * t2.m00 + t1.m10 * t2.m01,
t1.m00 * t2.m10 + t1.m10 * t2.m11,
t1.m01 * t2.m00 + t1.m11 * t2.m01,
t1.m01 * t2.m10 + t1.m11 * t2.m11,
t1.m02 * t2.m00 + t1.m12 * t2.m01 + t2.m02,
t1.m02 * t2.m10 + t1.m12 * t2.m11 + t2.m12);
}
public void concatenate(AffineTransform t) {
setTransform(multiply(t, this));
}
public void preConcatenate(AffineTransform t) {
setTransform(multiply(this, t));
}
public AffineTransform createInverse() throws NoninvertibleTransformException {
double det = getDeterminant();
if (Math.abs(det) < ZERO) {
// awt.204=Determinant is zero
//$NON-NLS-1$
throw new NoninvertibleTransformException(NoninvertibleTransformException.DETERMINANT_IS_ZERO_CANNOT_INVERT_TRANSFORMATION);
}
return new AffineTransform(
m11 / det,
-m10 / det,
-m01 / det,
m00 / det,
(m01 * m12 - m11 * m02) / det,
(m10 * m02 - m00 * m12) / det
);
}
public Point transform(Point src, Point dst) {
if (dst == null) {
dst = new Point();
}
double x = src.getX();
double y = src.getY();
dst.setLocation(x * m00 + y * m01 + m02, x * m10 + y * m11 + m12);
return dst;
}
public void transform(Point[] src, int srcOff, Point[] dst, int dstOff, int length) {
while (--length >= 0) {
Point srcPoint = src[srcOff++];
double x = srcPoint.getX();
double y = srcPoint.getY();
Point dstPoint = dst[dstOff];
if (dstPoint == null) {
dstPoint = new Point();
}
dstPoint.setLocation(x * m00 + y * m01 + m02, x * m10 + y * m11 + m12);
dst[dstOff++] = dstPoint;
}
}
public void transform(double[] src, int srcOff, double[] dst, int dstOff, int length) {
int step = 2;
if (src == dst && srcOff < dstOff && dstOff < srcOff + length * 2) {
srcOff = srcOff + length * 2 - 2;
dstOff = dstOff + length * 2 - 2;
step = -2;
}
while (--length >= 0) {
double x = src[srcOff + 0];
double y = src[srcOff + 1];
dst[dstOff + 0] = x * m00 + y * m01 + m02;
dst[dstOff + 1] = x * m10 + y * m11 + m12;
srcOff += step;
dstOff += step;
}
}
public void transform(float[] src, int srcOff, float[] dst, int dstOff, int length) {
int step = 2;
if (src == dst && srcOff < dstOff && dstOff < srcOff + length * 2) {
srcOff = srcOff + length * 2 - 2;
dstOff = dstOff + length * 2 - 2;
step = -2;
}
while (--length >= 0) {
float x = src[srcOff + 0];
float y = src[srcOff + 1];
dst[dstOff + 0] = (float) (x * m00 + y * m01 + m02);
dst[dstOff + 1] = (float) (x * m10 + y * m11 + m12);
srcOff += step;
dstOff += step;
}
}
public void transform(float[] src, int srcOff, double[] dst, int dstOff, int length) {
while (--length >= 0) {
float x = src[srcOff++];
float y = src[srcOff++];
dst[dstOff++] = x * m00 + y * m01 + m02;
dst[dstOff++] = x * m10 + y * m11 + m12;
}
}
public void transform(double[] src, int srcOff, float[] dst, int dstOff, int length) {
while (--length >= 0) {
double x = src[srcOff++];
double y = src[srcOff++];
dst[dstOff++] = (float) (x * m00 + y * m01 + m02);
dst[dstOff++] = (float) (x * m10 + y * m11 + m12);
}
}
public Point deltaTransform(Point src, Point dst) {
if (dst == null) {
dst = new Point();
}
double x = src.getX();
double y = src.getY();
dst.setLocation(x * m00 + y * m01, x * m10 + y * m11);
return dst;
}
public void deltaTransform(double[] src, int srcOff, double[] dst, int dstOff, int length) {
while (--length >= 0) {
double x = src[srcOff++];
double y = src[srcOff++];
dst[dstOff++] = x * m00 + y * m01;
dst[dstOff++] = x * m10 + y * m11;
}
}
public Point inverseTransform(Point src, Point dst) throws NoninvertibleTransformException {
double det = getDeterminant();
if (Math.abs(det) < ZERO) {
// awt.204=Determinant is zero
//$NON-NLS-1$
throw new NoninvertibleTransformException(NoninvertibleTransformException.DETERMINANT_IS_ZERO_CANNOT_INVERT_TRANSFORMATION);
}
if (dst == null) {
dst = new Point();
}
double x = src.getX() - m02;
double y = src.getY() - m12;
dst.setLocation((x * m11 - y * m01) / det, (y * m00 - x * m10) / det);
return dst;
}
public void inverseTransform(double[] src, int srcOff, double[] dst, int dstOff, int length)
throws NoninvertibleTransformException {
double det = getDeterminant();
if (Math.abs(det) < ZERO) {
// awt.204=Determinant is zero
//$NON-NLS-1$
throw new NoninvertibleTransformException(NoninvertibleTransformException.DETERMINANT_IS_ZERO_CANNOT_INVERT_TRANSFORMATION);
}
while (--length >= 0) {
double x = src[srcOff++] - m02;
double y = src[srcOff++] - m12;
dst[dstOff++] = (x * m11 - y * m01) / det;
dst[dstOff++] = (y * m00 - x * m10) / det;
}
}
public void inverseTransform(float[] src, int srcOff, float[] dst, int dstOff, int length)
throws NoninvertibleTransformException {
float det = (float) getDeterminant();
if (Math.abs(det) < ZERO) {
// awt.204=Determinant is zero
//$NON-NLS-1$
throw new NoninvertibleTransformException(NoninvertibleTransformException.DETERMINANT_IS_ZERO_CANNOT_INVERT_TRANSFORMATION);
}
while (--length >= 0) {
float x = (float) (src[srcOff++] - m02);
float y = (float) (src[srcOff++] - m12);
dst[dstOff++] = (float) ((x * m11 - y * m01) / det);
dst[dstOff++] = (float) ((y * m00 - x * m10) / det);
}
}
/**
* Creates a "deep copy" of this AffineTransform, meaning the object returned by this method will be independent
* of the object being cloned.
*
* @return the copied AffineTransform.
*/
@Override
public AffineTransform clone() throws CloneNotSupportedException {
// super.clone is safe to return since all of the AffineTransform's fields are primitive.
return (AffineTransform) super.clone();
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
AffineTransform that = (AffineTransform) o;
return Double.compare(that.m00, m00) == 0 &&
Double.compare(that.m10, m10) == 0 &&
Double.compare(that.m01, m01) == 0 &&
Double.compare(that.m11, m11) == 0 &&
Double.compare(that.m02, m02) == 0 &&
Double.compare(that.m12, m12) == 0;
}
@Override
public int hashCode() {
return Objects.hash(m00, m10, m01, m11, m02, m12);
}
}