--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.g2d.utils;\r
+\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.NoninvertibleTransformException;\r
+import java.awt.geom.Point2D;\r
+import java.awt.geom.Rectangle2D;\r
+\r
+/**\r
+ * @author Tuukka Lehtonen\r
+ */\r
+public final class ViewBoxUtils {\r
+\r
+ /**\r
+ * A default viewbox generated with Stetson-Harrison method for a last\r
+ * fallback value if nothing else is available.\r
+ */\r
+ private static final Rectangle2D defaultViewBox = new Rectangle2D.Double(-10, -10, 20, 20);\r
+ \r
+ public static Rectangle2D getDefaultViewBox() {\r
+ Rectangle2D r = new Rectangle2D.Double();\r
+ r.setFrame(defaultViewBox);\r
+ return r;\r
+ }\r
+ \r
+ /**\r
+ * @param x\r
+ * @param y\r
+ * @param distanceFromPlane (0, +inf)\r
+ * @param fieldOfView (0, 180) deg\r
+ * @param aspectRatio (0, +inf), view-width / view-height\r
+ * @return\r
+ */\r
+// public static Rectangle2D toViewBox(double x, double y, double distanceFromPlane, double fieldOfView, double aspectRatio) {\r
+// // w/2 = d * tan(fov)\r
+// // h = w / aspectRatio\r
+// double w = distanceFromPlane * Math.tan(fieldOfView * 0.5);\r
+// double h = w / aspectRatio;\r
+// \r
+// Rectangle2D r = new Rectangle2D.Double(x - w, y - h, w * 2, h * 2);\r
+// return r;\r
+// }\r
+\r
+ /**\r
+ * Get the view transformation of the specified canvas as an\r
+ * AffineTransform.\r
+ * \r
+ */\r
+ public static AffineTransform getViewTransform(Rectangle2D viewBox, Point2D canvasSize) {\r
+ double sx = canvasSize.getX() / viewBox.getWidth();\r
+ double sy = canvasSize.getY() / viewBox.getHeight();\r
+\r
+ AffineTransform tr = new AffineTransform();\r
+ tr.scale(sx, sy);\r
+// System.out.println("getViewTransform:");\r
+// System.out.println(" scale: (" + sx + ", " + sy + ")");\r
+// System.out.println(" after scale: " + tr);\r
+ tr.translate(-viewBox.getMinX(), -viewBox.getMinY());\r
+// System.out.println(" after translation: " + tr); \r
+ \r
+ return tr;\r
+ }\r
+ \r
+ /**\r
+ * Get the view transformation of the specified canvas as an\r
+ * AffineTransform.\r
+ * \r
+ */\r
+ public static AffineTransform getViewTransform(Rectangle2D viewBox, Point2D controlSize, Point2D usableControlSize) {\r
+ double sx = usableControlSize.getX() / viewBox.getWidth();\r
+ double sy = usableControlSize.getY() / viewBox.getHeight();\r
+\r
+ AffineTransform tr = new AffineTransform();\r
+ tr.translate(\r
+ (-usableControlSize.getX()+controlSize.getX())/2, \r
+ (-usableControlSize.getX()+controlSize.getX())/2); \r
+ tr.scale(sx, sy);\r
+ tr.translate(-viewBox.getMinX(), -viewBox.getMinY());\r
+ \r
+ return tr;\r
+ } \r
+ /**\r
+ * Interpolates a 2D position with respect to a rectangle and two normalized\r
+ * interpolation factors.\r
+ * \r
+ * @param r the rectangle inside which to interpolate a coordinate\r
+ * @param nx x interpolation value in [0,1]\r
+ * @param ny y interpolation value in [0,1]\r
+ * @return\r
+ */\r
+ public static Point2D interpolate(Rectangle2D r, double nx, double ny) {\r
+ return new Point2D.Double(\r
+ r.getMinX() + r.getWidth() * nx,\r
+ r.getMinY() + r.getHeight() * ny);\r
+ }\r
+ \r
+ /**\r
+ * Converts the specified AffineTransform into a view box rectangle with\r
+ * respect to the size of the specified canvas instance. The client can\r
+ * specify whether the scaling should be uniform or non-uniform. In the\r
+ * uniform case the X scale factor is used for both dimensions.\r
+ * \r
+ * @param canvasSize\r
+ * @param tr\r
+ * @param uniform\r
+ * @return\r
+ */\r
+ public static Rectangle2D transformToViewBox(Point2D canvasSize, AffineTransform tr, boolean uniform) {\r
+ double sx;\r
+ double sy;\r
+ \r
+ if (uniform) {\r
+// if (tr.getScaleX() != tr.getScaleY())\r
+// System.out.println("WARNING: scale not uniform: " + tr.getScaleX() + " vs. " + tr.getScaleY());\r
+ sx = sy = 1.0 / tr.getScaleX();\r
+ } else {\r
+ sx = 1.0 / tr.getScaleX();\r
+ sy = 1.0 / tr.getScaleY();\r
+ }\r
+ \r
+ double startX = -tr.getTranslateX() * sx;\r
+ double startY = -tr.getTranslateY() * sy;\r
+ double endX = startX + sx * canvasSize.getX();\r
+ double endY = startY + sy * canvasSize.getY();\r
+\r
+ return new Rectangle2D.Double(startX, startY, endX - startX, endY - startY);\r
+ }\r
+\r
+ /**\r
+ * Uniformly inflates the specified rectangle by a scaling factor around its\r
+ * the center of the box. Modifies the specified rectangle instance itself.\r
+ * \r
+ * @param r the rectangle to inflate\r
+ * @param scale the inflation scale factor\r
+ */\r
+ public static void inflate(Rectangle2D r, double scale) {\r
+ double cx = r.getCenterX();\r
+ double cy = r.getCenterY();\r
+ double nw = r.getWidth() * scale;\r
+ double nh = r.getHeight() * scale;\r
+ double nw2 = nw * 0.5;\r
+ double nh2 = nh * 0.5;\r
+ r.setFrame(cx - nw2, cy - nh2, nw, nh);\r
+ }\r
+ \r
+ /**\r
+ * Uniformly inflates the specified rectangle by a scaling factor around its\r
+ * the center of the box. Returns the inflated rectangle as a new instance.\r
+ * The original rectangle is not modified.\r
+ * \r
+ * @param r the rectangle to inflate\r
+ * @param scale the inflation scale factor\r
+ * @return an inflated rectangle\r
+ */\r
+ public static Rectangle2D inflated(Rectangle2D r, double scale) {\r
+ Rectangle2D r2 = new Rectangle2D.Double();\r
+ r2.setFrame(r);\r
+ inflate(r2, scale);\r
+ return r2;\r
+ }\r
+\r
+ /**\r
+ * Uniformly fits the specified view box to size of the specified canvas.\r
+ * \r
+ * This means that the viewbox if the viewbox coordinates are not a perfect\r
+ * multiple of the canvas size (i.e. viewBox-size * s = canvas-size), the\r
+ * view box is always scaled in either the horizontal or the vertical\r
+ * dimension. Here the scaled dimension is chosen to always inflate the view\r
+ * box, never deflate it.\r
+ * \r
+ * @param canvasSize the size of the canvas to fit the view box to\r
+ * @param viewBox the view box to fit\r
+ */\r
+ public static void uniformFitToCanvas(Point2D canvasSize, Rectangle2D viewBox) {\r
+ double cx = viewBox.getCenterX();\r
+ double cy = viewBox.getCenterY();\r
+ \r
+ double cw = (double) canvasSize.getX();\r
+ double ch = (double) canvasSize.getY();\r
+ double w = viewBox.getWidth();\r
+ double h = viewBox.getHeight();\r
+ double sw = cw / w;\r
+ double sh = ch / h;\r
+ \r
+ if (sw < sh) {\r
+ // The specified viewbox fits the canvas in width but in height there\r
+ // is extra space which the viewbox needs to fill!\r
+ \r
+ // height of requested viewbox in device coordinates = h' = h * sw\r
+ // height of viewbox that fits the canvas height = h * ch / h'\r
+ \r
+ double dh = h * sw;\r
+ double fh = h * ch / dh;\r
+ \r
+ viewBox.setFrameFromCenter(cx, cy, cx + w * 0.5, cy + fh * 0.5);\r
+// System.out.println("sw < sh: " + dh + ", " + fh + ": " + viewBox);\r
+ } else if (sw > sh) {\r
+ // The specified viewbox fits the canvas in height but in width there\r
+ // is extra space which the viewbox needs to fill!\r
+ \r
+ // width of requested viewbox in device coordinates = dw = w * sh\r
+ // width of viewbox that fits the canvas height = fw = w * cw / w'\r
+ \r
+ double dw = w * sh;\r
+ double fw = w * cw / dw;\r
+ \r
+ viewBox.setFrameFromCenter(cx, cy, cx + fw * 0.5, cy + h * 0.5);\r
+// System.out.println("sw > sh: " + dw + ", " + fw + ": " + viewBox);\r
+ }\r
+ }\r
+\r
+ public static void move(Rectangle2D r, double tx, double ty) {\r
+ r.setFrame(r.getMinX() + tx, r.getMinY() + ty, r.getWidth(), r.getHeight());\r
+ }\r
+\r
+ public static Rectangle2D moved(Rectangle2D r, double tx, double ty) {\r
+ Rectangle2D r2 = new Rectangle2D.Double();\r
+ r2.setFrame(r.getMinX() + tx, r.getMinY() + ty, r.getWidth(), r.getHeight());\r
+ return r2;\r
+ }\r
+\r
+ \r
+ /**\r
+ * @param canvasSize\r
+ * @param px\r
+ * @param py\r
+ * @param sx\r
+ * @param sy\r
+ * @return\r
+ */\r
+ public static Rectangle2D uniformZoomedViewBox(Rectangle2D viewBox, Point2D canvasSize, int px, int py, double sx, double sy) {\r
+ return zoomedViewBox(viewBox, canvasSize, px, py, sx, sy, true);\r
+ }\r
+ \r
+ /**\r
+ * @param c\r
+ * @param px\r
+ * @param py\r
+ * @param sx\r
+ * @param sy\r
+ * @param uniform\r
+ * @return\r
+ */\r
+ public static Rectangle2D zoomedViewBox(Rectangle2D viewBox, Point2D canvasSize, int px, int py, double sx, double sy, boolean uniform) {\r
+ if (sx <= 0 || sy <= 0) {\r
+ throw new IllegalArgumentException("invalid scaling: " + sx + ", " + sy);\r
+ }\r
+ \r
+ AffineTransform view = getViewTransform(viewBox, canvasSize);\r
+ \r
+ AffineTransform at = AffineTransform.getTranslateInstance(px, py);\r
+ at.scale(sx, sy);\r
+ at.translate(-px, -py);\r
+ at.concatenate(view);\r
+ \r
+ Rectangle2D box = transformToViewBox(canvasSize, at, uniform);\r
+ return box;\r
+ }\r
+ \r
+ \r
+ \r
+ \r
+ \r
+ \r
+\r
+ public enum Align {Begin, Center, End, Fill};\r
+ \r
+ /**\r
+ * Creates tranform matrix, that converts coordinates from \r
+ * uniform viewport to a 100x100 square. Aspect ratio remains the same.\r
+ * \r
+ * @param viewport\r
+ * @param horiz Horizontal alignment of 100x100 square\r
+ * @param vert Vertical alignment of 100x100 square\r
+ * @return transform (that can be concatenated)\r
+ */\r
+ public static AffineTransform viewportToSquare(\r
+ Rectangle2D viewport, \r
+ Align horiz, \r
+ Align vert) {\r
+ boolean wide = viewport.getWidth() > viewport.getHeight();\r
+ double shorterEdge = wide ? viewport.getHeight() : viewport.getWidth();\r
+ double longerEdge = !wide ? viewport.getHeight() : viewport.getWidth();\r
+ double tx = -viewport.getX(); \r
+ double ty = -viewport.getY();\r
+ double s = 100 / shorterEdge;\r
+ if (wide) {\r
+ if (horiz == Align.Center)\r
+ tx += (longerEdge - shorterEdge) / 2;\r
+ else if (horiz == Align.End)\r
+ tx += longerEdge - shorterEdge; \r
+ else if (horiz == Align.Fill) \r
+ s = 100 / viewport.getWidth(); \r
+ } else {\r
+ if (vert == Align.Center)\r
+ ty += (longerEdge - shorterEdge) / 2;\r
+ else if (vert == Align.End)\r
+ ty += longerEdge - shorterEdge; \r
+ else if (vert == Align.Fill) \r
+ s = 100 / viewport.getHeight(); \r
+ }\r
+ \r
+ AffineTransform at = new AffineTransform();\r
+ at.scale(s, s);\r
+ at.translate(tx, ty);\r
+ return at;\r
+ }\r
+\r
+ /**\r
+ * Creates tranform matrix, that converts coordinates from \r
+ * 100x100 square to a uniform viewport. Aspect ratio remains the same.\r
+ * \r
+ * @param viewport\r
+ * @param horiz Horizontal alignment of 100x100 square\r
+ * @param vert Vertical alignment of 100x100 square\r
+ * @return transform (that can be concatenated)\r
+ */\r
+ public static AffineTransform squareToViewport(\r
+ Rectangle2D viewport, \r
+ Align horiz, \r
+ Align vert) {\r
+ AffineTransform at = viewportToSquare(viewport, horiz, vert);\r
+ try {\r
+ return at.createInverse();\r
+ } catch (NoninvertibleTransformException e) {\r
+ throw new RuntimeException(e);\r
+ } \r
+ }\r
+ \r
+ \r
+}\r