package nodebox.graphics; import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.List; public class Transform implements Cloneable { public enum Mode { CORNER, CENTER } private AffineTransform affineTransform; public static Transform translated(double tx, double ty) { Transform t = new Transform(); t.translate(tx, ty); return t; } public static Transform translated(Point t) { return translated(t.x, t.y); } public static Transform rotated(double degrees) { Transform t = new Transform(); t.rotate(degrees); return t; } public static Transform rotatedRadians(double radians) { Transform t = new Transform(); t.rotateRadians(radians); return t; } public static Transform scaled(double scale) { Transform t = new Transform(); t.scale(scale); return t; } public static Transform scaled(double sx, double sy) { Transform t = new Transform(); t.scale(sx, sy); return t; } public static Transform scaled(Point s) { return scaled(s.x, s.y); } public static Transform skewed(double skew) { Transform t = new Transform(); t.skew(skew); return t; } public static Transform skewed(double kx, double ky) { Transform t = new Transform(); t.skew(kx, ky); return t; } public Transform() { affineTransform = new AffineTransform(); } public Transform(double m00, double m10, double m01, double m11, double m02, double m12) { affineTransform = new AffineTransform(m00, m10, m01, m11, m02, m12); } public Transform(AffineTransform affineTransform) { this.affineTransform = affineTransform; } public Transform(Transform other) { this.affineTransform = (AffineTransform) other.affineTransform.clone(); } //// Transform changes //// public void translate(Point point) { affineTransform.translate(point.x, point.y); } public void translate(double tx, double ty) { affineTransform.translate(tx, ty); } public void rotate(double degrees) { double radians = degrees * Math.PI / 180; affineTransform.rotate(radians); } public void rotateRadians(double radians) { affineTransform.rotate(radians); } public void scale(double scale) { affineTransform.scale(scale, scale); } public void scale(double sx, double sy) { affineTransform.scale(sx, sy); } public void skew(double skew) { skew(skew, skew); } public void skew(double kx, double ky) { kx = Math.PI * kx / 180.0; ky = Math.PI * ky / 180.0; affineTransform.concatenate(new AffineTransform(1, Math.tan(ky), -Math.tan(kx), 1, 0, 0)); } public boolean invert() { try { affineTransform = affineTransform.createInverse(); return true; } catch (NoninvertibleTransformException e) { return false; } } public void append(Transform t) { affineTransform.concatenate(t.affineTransform); } public void prepend(Transform t) { affineTransform.preConcatenate(t.affineTransform); } //// Operations //// public Point map(Point p) { Point2D.Double p2 = new Point2D.Double(); affineTransform.transform(p.toPoint2D(), p2); return new Point(p2); } public Rect map(Rect r) { // TODO: The size conversion might be incorrect. (using deltaTransform) In that case, make topLeft and bottomRight points. Point2D origin = new Point2D.Double(r.getX(), r.getY()); Point2D size = new Point2D.Double(r.getWidth(), r.getHeight()); Point2D transformedOrigin = new Point2D.Double(); Point2D transformedSize = new Point2D.Double(); affineTransform.transform(origin, transformedOrigin); affineTransform.deltaTransform(size, transformedSize); return new Rect(transformedOrigin.getX(), transformedOrigin.getY(), transformedSize.getX(), transformedSize.getY()); } public IGeometry map(IGeometry shape) { if (shape instanceof Path) { return map((Path) shape); } else if (shape instanceof Geometry) { return map((Geometry) shape); } else { throw new RuntimeException("Unsupported geometry type " + shape); } } public Path map(Path p) { Path newPath = new Path(p, false); for (Contour c : p.getContours()) { Contour newContour = new Contour(map(c.getPoints()), c.isClosed()); newPath.add(newContour); } return newPath; } public Geometry map(Geometry g) { Geometry newGeometry = new Geometry(); for (Path p : g.getPaths()) { Path newPath = map(p); newGeometry.add(newPath); } return newGeometry; } /** * Transform all the given points and return a list of transformed points. * Points are immutable, so they can not be transformed in-place. * * @param points The points to transform. * @return The list of transformed points. */ public List<Point> map(List<Point> points) { // Prepare the points for the AffineTransform transformation. double[] coords = new double[points.size() * 2]; int i = 0; for (Point pt : points) { coords[i++] = pt.x; coords[i++] = pt.y; } affineTransform.transform(coords, 0, coords, 0, points.size()); // Convert the transformed points into a new List. List<Point> transformed = new ArrayList<Point>(points.size()); int pointIndex = 0; for (i = 0; i < coords.length; i += 2) { transformed.add(new Point(coords[i], coords[i + 1], points.get(pointIndex).type)); pointIndex++; } return transformed; } public Rect convertBoundsToFrame(Rect bounds) { AffineTransform t = fullTransform(bounds); Point2D transformedOrigin = new Point2D.Double(); Point2D transformedSize = new Point2D.Double(); t.transform(new Point2D.Double(bounds.getX(), bounds.getY()), transformedOrigin); t.deltaTransform(new Point2D.Double(bounds.getWidth(), bounds.getHeight()), transformedSize); return new Rect(transformedOrigin.getX(), transformedOrigin.getY(), transformedSize.getX(), transformedSize.getY()); } private AffineTransform fullTransform(Rect bounds) { double cx = bounds.getX() + bounds.getWidth() / 2; double cy = bounds.getY() + bounds.getHeight() / 2; AffineTransform t = new AffineTransform(); t.translate(cx, cy); t.preConcatenate(affineTransform); t.translate(-cx, -cy); return t; } @Override protected Transform clone() { return new Transform(this); } @Override public boolean equals(Object obj) { if (!(obj instanceof Transform)) return false; return getAffineTransform().equals(((Transform) obj).getAffineTransform()); } public void apply(Graphics2D g, Rect bounds) { AffineTransform t = fullTransform(bounds); g.transform(t); } public AffineTransform getAffineTransform() { return affineTransform; } }