1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.g2d.utils;
14 import java.awt.geom.AffineTransform;
15 import java.awt.geom.NoninvertibleTransformException;
16 import java.awt.geom.Point2D;
17 import java.awt.geom.Rectangle2D;
20 * @author Tuukka Lehtonen
22 public final class ViewBoxUtils {
25 * A default viewbox generated with Stetson-Harrison method for a last
26 * fallback value if nothing else is available.
28 private static final Rectangle2D defaultViewBox = new Rectangle2D.Double(-10, -10, 20, 20);
30 public static Rectangle2D getDefaultViewBox() {
31 Rectangle2D r = new Rectangle2D.Double();
32 r.setFrame(defaultViewBox);
39 * @param distanceFromPlane (0, +inf)
40 * @param fieldOfView (0, 180) deg
41 * @param aspectRatio (0, +inf), view-width / view-height
44 // public static Rectangle2D toViewBox(double x, double y, double distanceFromPlane, double fieldOfView, double aspectRatio) {
45 // // w/2 = d * tan(fov)
46 // // h = w / aspectRatio
47 // double w = distanceFromPlane * Math.tan(fieldOfView * 0.5);
48 // double h = w / aspectRatio;
50 // Rectangle2D r = new Rectangle2D.Double(x - w, y - h, w * 2, h * 2);
55 * Get the view transformation of the specified canvas as an
59 public static AffineTransform getViewTransform(Rectangle2D viewBox, Point2D canvasSize) {
60 double sx = canvasSize.getX() / viewBox.getWidth();
61 double sy = canvasSize.getY() / viewBox.getHeight();
63 AffineTransform tr = new AffineTransform();
65 // System.out.println("getViewTransform:");
66 // System.out.println(" scale: (" + sx + ", " + sy + ")");
67 // System.out.println(" after scale: " + tr);
68 tr.translate(-viewBox.getMinX(), -viewBox.getMinY());
69 // System.out.println(" after translation: " + tr);
75 * Get the view transformation of the specified canvas as an
79 public static AffineTransform getViewTransform(Rectangle2D viewBox, Point2D controlSize, Point2D usableControlSize) {
80 double sx = usableControlSize.getX() / viewBox.getWidth();
81 double sy = usableControlSize.getY() / viewBox.getHeight();
83 AffineTransform tr = new AffineTransform();
85 (-usableControlSize.getX()+controlSize.getX())/2,
86 (-usableControlSize.getX()+controlSize.getX())/2);
88 tr.translate(-viewBox.getMinX(), -viewBox.getMinY());
93 * Interpolates a 2D position with respect to a rectangle and two normalized
94 * interpolation factors.
96 * @param r the rectangle inside which to interpolate a coordinate
97 * @param nx x interpolation value in [0,1]
98 * @param ny y interpolation value in [0,1]
101 public static Point2D interpolate(Rectangle2D r, double nx, double ny) {
102 return new Point2D.Double(
103 r.getMinX() + r.getWidth() * nx,
104 r.getMinY() + r.getHeight() * ny);
108 * Converts the specified AffineTransform into a view box rectangle with
109 * respect to the size of the specified canvas instance. The client can
110 * specify whether the scaling should be uniform or non-uniform. In the
111 * uniform case the X scale factor is used for both dimensions.
118 public static Rectangle2D transformToViewBox(Point2D canvasSize, AffineTransform tr, boolean uniform) {
123 // if (tr.getScaleX() != tr.getScaleY())
124 // System.out.println("WARNING: scale not uniform: " + tr.getScaleX() + " vs. " + tr.getScaleY());
125 sx = sy = 1.0 / tr.getScaleX();
127 sx = 1.0 / tr.getScaleX();
128 sy = 1.0 / tr.getScaleY();
131 double startX = -tr.getTranslateX() * sx;
132 double startY = -tr.getTranslateY() * sy;
133 double endX = startX + sx * canvasSize.getX();
134 double endY = startY + sy * canvasSize.getY();
136 return new Rectangle2D.Double(startX, startY, endX - startX, endY - startY);
140 * Uniformly inflates the specified rectangle by a scaling factor around its
141 * the center of the box. Modifies the specified rectangle instance itself.
143 * @param r the rectangle to inflate
144 * @param scale the inflation scale factor
146 public static void inflate(Rectangle2D r, double scale) {
147 double cx = r.getCenterX();
148 double cy = r.getCenterY();
149 double nw = r.getWidth() * scale;
150 double nh = r.getHeight() * scale;
151 double nw2 = nw * 0.5;
152 double nh2 = nh * 0.5;
153 r.setFrame(cx - nw2, cy - nh2, nw, nh);
157 * Uniformly inflates the specified rectangle by a scaling factor around its
158 * the center of the box. Returns the inflated rectangle as a new instance.
159 * The original rectangle is not modified.
161 * @param r the rectangle to inflate
162 * @param scale the inflation scale factor
163 * @return an inflated rectangle
165 public static Rectangle2D inflated(Rectangle2D r, double scale) {
166 Rectangle2D r2 = new Rectangle2D.Double();
173 * Uniformly fits the specified view box to size of the specified canvas.
175 * This means that the viewbox if the viewbox coordinates are not a perfect
176 * multiple of the canvas size (i.e. viewBox-size * s = canvas-size), the
177 * view box is always scaled in either the horizontal or the vertical
178 * dimension. Here the scaled dimension is chosen to always inflate the view
179 * box, never deflate it.
181 * @param canvasSize the size of the canvas to fit the view box to
182 * @param viewBox the view box to fit
184 public static void uniformFitToCanvas(Point2D canvasSize, Rectangle2D viewBox) {
185 double cx = viewBox.getCenterX();
186 double cy = viewBox.getCenterY();
188 double cw = (double) canvasSize.getX();
189 double ch = (double) canvasSize.getY();
190 double w = viewBox.getWidth();
191 double h = viewBox.getHeight();
196 // The specified viewbox fits the canvas in width but in height there
197 // is extra space which the viewbox needs to fill!
199 // height of requested viewbox in device coordinates = h' = h * sw
200 // height of viewbox that fits the canvas height = h * ch / h'
203 double fh = h * ch / dh;
205 viewBox.setFrameFromCenter(cx, cy, cx + w * 0.5, cy + fh * 0.5);
206 // System.out.println("sw < sh: " + dh + ", " + fh + ": " + viewBox);
207 } else if (sw > sh) {
208 // The specified viewbox fits the canvas in height but in width there
209 // is extra space which the viewbox needs to fill!
211 // width of requested viewbox in device coordinates = dw = w * sh
212 // width of viewbox that fits the canvas height = fw = w * cw / w'
215 double fw = w * cw / dw;
217 viewBox.setFrameFromCenter(cx, cy, cx + fw * 0.5, cy + h * 0.5);
218 // System.out.println("sw > sh: " + dw + ", " + fw + ": " + viewBox);
222 public static void move(Rectangle2D r, double tx, double ty) {
223 r.setFrame(r.getMinX() + tx, r.getMinY() + ty, r.getWidth(), r.getHeight());
226 public static Rectangle2D moved(Rectangle2D r, double tx, double ty) {
227 Rectangle2D r2 = new Rectangle2D.Double();
228 r2.setFrame(r.getMinX() + tx, r.getMinY() + ty, r.getWidth(), r.getHeight());
241 public static Rectangle2D uniformZoomedViewBox(Rectangle2D viewBox, Point2D canvasSize, int px, int py, double sx, double sy) {
242 return zoomedViewBox(viewBox, canvasSize, px, py, sx, sy, true);
254 public static Rectangle2D zoomedViewBox(Rectangle2D viewBox, Point2D canvasSize, int px, int py, double sx, double sy, boolean uniform) {
255 if (sx <= 0 || sy <= 0) {
256 throw new IllegalArgumentException("invalid scaling: " + sx + ", " + sy);
259 AffineTransform view = getViewTransform(viewBox, canvasSize);
261 AffineTransform at = AffineTransform.getTranslateInstance(px, py);
263 at.translate(-px, -py);
264 at.concatenate(view);
266 Rectangle2D box = transformToViewBox(canvasSize, at, uniform);
276 public enum Align {Begin, Center, End, Fill};
279 * Creates tranform matrix, that converts coordinates from
280 * uniform viewport to a 100x100 square. Aspect ratio remains the same.
283 * @param horiz Horizontal alignment of 100x100 square
284 * @param vert Vertical alignment of 100x100 square
285 * @return transform (that can be concatenated)
287 public static AffineTransform viewportToSquare(
288 Rectangle2D viewport,
291 boolean wide = viewport.getWidth() > viewport.getHeight();
292 double shorterEdge = wide ? viewport.getHeight() : viewport.getWidth();
293 double longerEdge = !wide ? viewport.getHeight() : viewport.getWidth();
294 double tx = -viewport.getX();
295 double ty = -viewport.getY();
296 double s = 100 / shorterEdge;
298 if (horiz == Align.Center)
299 tx += (longerEdge - shorterEdge) / 2;
300 else if (horiz == Align.End)
301 tx += longerEdge - shorterEdge;
302 else if (horiz == Align.Fill)
303 s = 100 / viewport.getWidth();
305 if (vert == Align.Center)
306 ty += (longerEdge - shorterEdge) / 2;
307 else if (vert == Align.End)
308 ty += longerEdge - shorterEdge;
309 else if (vert == Align.Fill)
310 s = 100 / viewport.getHeight();
313 AffineTransform at = new AffineTransform();
315 at.translate(tx, ty);
320 * Creates tranform matrix, that converts coordinates from
321 * 100x100 square to a uniform viewport. Aspect ratio remains the same.
324 * @param horiz Horizontal alignment of 100x100 square
325 * @param vert Vertical alignment of 100x100 square
326 * @return transform (that can be concatenated)
328 public static AffineTransform squareToViewport(
329 Rectangle2D viewport,
332 AffineTransform at = viewportToSquare(viewport, horiz, vert);
334 return at.createInverse();
335 } catch (NoninvertibleTransformException e) {
336 throw new RuntimeException(e);