/*
* Scriptographer
*
* This file is part of Scriptographer, a Scripting Plugin for Adobe Illustrator
* http://scriptographer.org/
*
* Copyright (c) 2002-2010, Juerg Lehni
* http://scratchdisk.com/
*
* All rights reserved. See LICENSE file for details.
*
* File created on 20.12.2004.
*/
package com.scriptographer.ai;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.text.NumberFormat;
import com.scriptographer.ScriptographerEngine;
/**
* Matrix represents an affine transformation between two coordinate spaces in 2
* dimensions. Such a transform preserves the "straightness" and "parallelness"
* of lines. The transform is built from a sequence of translations, scales,
* flips, rotations, and shears.
*
* The transformation can be represented using matrix math on a 3x3 array. Given
* {@code (x, y),} the transformation {@code (x', y')} can be
* found by:
*
* <pre>
* [ x'] [ scaleX shearX translateX ] [ x ]
* [ y'] = [ shearY scaleY translateY ] [ y ]
* [ 1 ] [ 0 0 1 ] [ 1 ]
*
* [ scaleX * x + shearX * y + translateX ]
* = [ shearY * x + scaleY * y + translateY ]
* [ 1 ]
* </pre>
*
* The bottom row of the matrix is constant, so a transform can be uniquely
* represented by
* {@code "[[scaleX, shearX, translateX], [shearY, scaleY, translateY]]"}.
*
* @author lehni
*/
public class Matrix {
private AffineTransform transform;
public Matrix() {
transform = new AffineTransform();
}
/**
* Create a new matrix which copies the given one.
*
* @param matrix the matrix to copy
* @throws NullPointerException if m is null
*
* @jshide
*/
public Matrix(Matrix matrix) {
transform = matrix != null ? matrix.toAffineTransform()
: new AffineTransform();
}
/**
* @jshide
*/
public AffineTransform toAffineTransform() {
return new AffineTransform(transform);
}
/**
* Create a new matrix from the given AWT AffineTransform.
*
* @param transform the affine transformation to copy
* @throws NullPointerException if at is null
*
* @jshide
*/
public Matrix(AffineTransform transform) {
this.transform = new AffineTransform(transform);
}
/**
* Construct a transform with the given matrix entries:
* <pre>
* [ scaleX shearX translateX ]
* [ shearY scaleY translateY ]
* [ 0 0 1 ]
* </pre>
*
* @param scaleX the x scaling component
* @param shearY the y shearing component
* @param shearX the x shearing component
* @param scaleY the y scaling component
* @param translateX the x translation component
* @param translateY the y translation component
*/
public Matrix(double scaleX, double shearY, double shearX, double scaleY,
double translateX, double translateY) {
transform = new AffineTransform(scaleX, shearY, shearX, scaleY,
translateX, translateY);
}
/**
* Construct a matrix from a sequence of numbers. The array must
* have at least 4 entries, which has a translation factor of 0; or 6
* entries, for specifying all parameters:
* <pre>
* [ values[0] values[2] (values[4]) ]
* [ values[1] values[3] (values[5]) ]
* [ 0 0 1 ]
* </pre>
*
* @param values the matrix to copy from, with at least 4 (6) entries
* @throws NullPointerException if values is null
* @throws ArrayIndexOutOfBoundsException if values is too small
*/
public Matrix(double[] values) {
transform = new AffineTransform(values);
}
/**
* Construct a matrix from a two dimensional array:
* <pre>
* [ values[0][0] values[0][1] values[0][2] ]
* [ values[1][0] values[1][1] values[1][2] ]
* [ 0 0 1 ]
* </pre>
*
* @param values the matrix to copy from
* @throws NullPointerException if values is null
* @throws ArrayIndexOutOfBoundsException if values is too small
*/
public Matrix(double[][] values) {
transform = new AffineTransform(
values[0][0], values[0][1], values[0][2],
values[1][0], values[1][1], values[1][2]);
}
/**
* Returns a copy of the {@code Matrix} object.
*
* @return a copy of the {@code Matrix} object.
*/
public Object clone() {
return new Matrix(this);
}
/**
* Creates the inverse transformation of the matrix. If the matrix is not
* invertible (in which case {@link #isSingular()} returns true), invert()
* returns null, otherwise the matrix itself is modified and a reference to
* it is returned.
*
* @return the inverted matrix, or null, if the matrix is singular
*/
public Matrix invert() {
try {
transform = transform.createInverse();
return this;
} catch (NoninvertibleTransformException e) {
return null;
}
}
public boolean equals(Object obj) {
return transform.equals(((Matrix) obj).transform);
}
public double getScaleX() {
return transform.getScaleX();
}
public void setScaleX(double scaleX) {
transform.setTransform(scaleX, transform.getShearY(),
transform.getShearX(), transform.getScaleY(),
transform.getTranslateX(), transform.getTranslateY());
}
public double getScaleY() {
return transform.getScaleY();
}
public void setScaleY(double scaleY) {
transform.setTransform(transform.getScaleX(), transform.getShearY(),
transform.getShearX(), scaleY, transform.getTranslateX(),
transform.getTranslateY());
}
public double getShearX() {
return transform.getShearX();
}
public void setShearX(double shearX) {
transform.setTransform(transform.getScaleX(), transform.getShearY(),
shearX, transform.getScaleY(), transform.getTranslateX(),
transform.getTranslateY());
}
public double getShearY() {
return transform.getShearY();
}
public void setShearY(double shearY) {
transform.setTransform(transform.getScaleX(), transform.getShearY(),
transform.getShearX(), shearY, transform.getTranslateX(),
transform.getTranslateY());
}
public double getTranslateX() {
return transform.getTranslateX();
}
public void setTranslateX(double translateX) {
transform.setTransform(transform.getScaleX(), transform.getShearY(),
transform.getShearX(), transform.getScaleY(), translateX,
transform.getTranslateY());
}
public double getTranslateY() {
return transform.getTranslateY();
}
public void setTranslateY(double translateY) {
transform.setTransform(transform.getScaleX(), transform.getShearY(),
transform.getShearX(), transform.getScaleY(),
transform.getTranslateX(), translateY);
}
/**
* @jshide
*/
public Point transform(double x, double y) {
// A bit of converting from Point2D <-> Point
return new Point(transform.transform(new Point2D.Double(x, y),
new Point2D.Double()));
}
public Point transform(Point point) {
return transform(point.x, point.y);
}
/**
* {@grouptitle Matrix Concatenation}
*
* Concatenates the matrix with a translation matrix that translates by
* {@code (x, y)}. The object itself is modified and a reference to it is
* returned.
*
* @param x the x coordinate of the translation
* @param y the y coordinate of the translation
* @return the translated matrix
*/
public Matrix translate(double x, double y) {
transform.translate(x, y);
return this;
}
/**
* Concatenates the matrix with a translation matrix that translates by the
* specified point. The object itself is modified and a reference to it is
* returned.
*
* @param pt the coordinates of the translation
* @return the translated matrix
*/
public Matrix translate(Point pt) {
return translate(
pt != null ? pt.getX() : 0,
pt != null ? pt.getY() : 0);
}
/**
* Concatenates the matrix with a scaling matrix that scales by the
* specified {@code (scaleX, scaleY)} factors. The object itself is modified
* and a reference to it is returned.
*
* @param scaleX
* @param scaleY
* @param center
* @return a reference to the matrix
*/
public Matrix scale(double scaleX, double scaleY, Point center) {
translate(center);
scale(scaleX, scaleY);
return translate(center.negate());
}
public Matrix scale(double scaleX, double scaleY) {
transform.scale(scaleX, scaleY);
return this;
}
/**
* Concatenates the matrix with a scaling matrix that scales by the
* specified {@code scale} factor. The object itself is modified and a
* reference to it is returned.
*
* @param scale
* @param center
* @return a reference to the matrix
*/
public Matrix scale(double scale, Point center) {
return scale(scale, scale, center);
}
public Matrix scale(double scale) {
return scale(scale, scale);
}
/**
* Concatenates the matrix with a matrix that rotates coordinates by a
* specified angle (and around a center point, if specified). The matrix
* itself is modified and a reference to it is returned.
*
* Angles are oriented clockwise and measured in degrees by default. Read
* more about angle units and orientation in the description of the
* {@link com.scriptographer.ai.Point#getAngle()} property.
*
* @param angle the angle to rotate by
* @param center the center point around which to rotate
* @return a reference to the matrix
*/
public Matrix rotate(double angle, Point center) {
transform.rotate(ScriptographerEngine.anglesInDegrees
? angle * Math.PI / 180.0
: angle,
center != null ? center.getX() : 0,
center != null ? center.getY() : 0);
return this;
}
public Matrix rotate(double angle) {
transform.rotate(ScriptographerEngine.anglesInDegrees
? angle * Math.PI / 180.0
: angle);
return this;
}
/**
* Concatenates the matrix with a shearing matrix. The object itself is
* modified and a reference to it is returned.
*
* @param shearX the horizontal shearing
* @param shearY the vertical shearing
* @return a reference to the matrix
*/
public Matrix shear(double shearX, double shearY) {
transform.shear(shearX, shearY);
return this;
}
/**
* Concatenates the specified matrix to the matrix in the most commonly
* useful way to provide a new user space that is mapped to the former user
* space by the specified matrix. The matrix itself is modified and a
* reference to it is returned.
*
* @param matrix
* @return a reference to the matrix
*/
public Matrix concatenate(Matrix matrix) {
transform.concatenate(matrix.toAffineTransform());
return this;
}
/**
* Concatenates the specified matrix to the matrix in a less commonly used
* way such that the specified matrix modifies the coordinate transformation
* relative to the absolute pixel space rather than relative to the existing
* user space. The object itself is modified and a reference to it is
* returned.
*
* @param matrix
* @return a reference to the matrix
*/
public Matrix preConcatenate(Matrix matrix) {
transform.preConcatenate(matrix.toAffineTransform());
return this;
}
public String toString() {
NumberFormat format = ScriptographerEngine.numberFormat;
return "[[" + format.format(transform.getScaleX()) + ", "
+ format.format(transform.getShearX()) + ", "
+ format.format(transform.getTranslateX()) + "], ["
+ format.format(transform.getShearY()) + ", "
+ format.format(transform.getScaleY()) + ", "
+ format.format(transform.getTranslateY()) + "]]";
}
/**
* {@grouptitle Tests}
*
* Checks whether the matrix is an identity. Identity matrices are equal to
* their inversion.
*
* @return true if the matrix is an identity, false otherwise
*/
public boolean isIdentity() {
return transform.isIdentity();
}
/**
* Checks whether the matrix is singular or not. Singular matrices cannot be
* inverted.
*
* @return true if the matrix is singular, false otherwise
*/
public boolean isSingular() {
// There seems to be no other way to find out if we can
// invert than actually trying:
return invert() == null;
}
}