]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.g2d/src/org/simantics/g2d/utils/ViewBoxUtils.java
Two rendering glitch fixes for time series charts
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / utils / ViewBoxUtils.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
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
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.g2d.utils;
13
14 import java.awt.geom.AffineTransform;
15 import java.awt.geom.NoninvertibleTransformException;
16 import java.awt.geom.Point2D;
17 import java.awt.geom.Rectangle2D;
18
19 /**
20  * @author Tuukka Lehtonen
21  */
22 public final class ViewBoxUtils {
23
24     /**
25      * A default viewbox generated with Stetson-Harrison method for a last
26      * fallback value if nothing else is available.
27      */
28     private static final Rectangle2D defaultViewBox = new Rectangle2D.Double(-10, -10, 20, 20);
29     
30     public static Rectangle2D getDefaultViewBox() {
31         Rectangle2D r = new Rectangle2D.Double();
32         r.setFrame(defaultViewBox);
33         return r;
34     }
35     
36     /**
37      * @param x
38      * @param y
39      * @param distanceFromPlane (0, +inf)
40      * @param fieldOfView (0, 180) deg
41      * @param aspectRatio (0, +inf), view-width / view-height
42      * @return
43      */
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;
49 //        
50 //        Rectangle2D r = new Rectangle2D.Double(x - w, y - h, w * 2, h * 2);
51 //        return r;
52 //    }
53
54     /**
55      * Get the view transformation of the specified canvas as an
56      * AffineTransform.
57      * 
58      */
59     public static AffineTransform getViewTransform(Rectangle2D viewBox, Point2D canvasSize) {
60         double sx = canvasSize.getX() / viewBox.getWidth();
61         double sy = canvasSize.getY() / viewBox.getHeight();
62
63         AffineTransform tr = new AffineTransform();
64         tr.scale(sx, sy);
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); 
70         
71         return tr;
72     }
73     
74     /**
75      * Get the view transformation of the specified canvas as an
76      * AffineTransform.
77      * 
78      */
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();
82
83         AffineTransform tr = new AffineTransform();
84         tr.translate(
85                 (-usableControlSize.getX()+controlSize.getX())/2, 
86                 (-usableControlSize.getX()+controlSize.getX())/2);                
87         tr.scale(sx, sy);
88         tr.translate(-viewBox.getMinX(), -viewBox.getMinY());
89         
90         return tr;
91     }    
92     /**
93      * Interpolates a 2D position with respect to a rectangle and two normalized
94      * interpolation factors.
95      * 
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]
99      * @return
100      */
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);
105     }
106     
107     /**
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.
112      * 
113      * @param canvasSize
114      * @param tr
115      * @param uniform
116      * @return
117      */
118     public static Rectangle2D transformToViewBox(Point2D canvasSize, AffineTransform tr, boolean uniform) {
119         double sx;
120         double sy;
121         
122         if (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();
126         } else {
127             sx = 1.0 / tr.getScaleX();
128             sy = 1.0 / tr.getScaleY();
129         }
130         
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();
135
136         return new Rectangle2D.Double(startX, startY, endX - startX, endY - startY);
137     }
138
139     /**
140      * Uniformly inflates the specified rectangle by a scaling factor around its
141      * the center of the box. Modifies the specified rectangle instance itself.
142      * 
143      * @param r the rectangle to inflate
144      * @param scale the inflation scale factor
145      */
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);
154     }
155     
156     /**
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.
160      * 
161      * @param r the rectangle to inflate
162      * @param scale the inflation scale factor
163      * @return an inflated rectangle
164      */
165     public static Rectangle2D inflated(Rectangle2D r, double scale) {
166         Rectangle2D r2 = new Rectangle2D.Double();
167         r2.setFrame(r);
168         inflate(r2, scale);
169         return r2;
170     }
171
172     /**
173      * Uniformly fits the specified view box to size of the specified canvas.
174      * 
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.
180      * 
181      * @param canvasSize the size of the canvas to fit the view box to
182      * @param viewBox the view box to fit
183      */
184     public static void uniformFitToCanvas(Point2D canvasSize, Rectangle2D viewBox) {
185         double cx = viewBox.getCenterX();
186         double cy = viewBox.getCenterY();
187         
188         double cw = (double) canvasSize.getX();
189         double ch = (double) canvasSize.getY();
190         double w = viewBox.getWidth();
191         double h = viewBox.getHeight();
192         double sw = cw / w;
193         double sh = ch / h;
194         
195         if (sw < sh) {
196             // The specified viewbox fits the canvas in width but in height there
197             // is extra space which the viewbox needs to fill!
198             
199             // height of requested viewbox in device coordinates = h' = h * sw
200             // height of viewbox that fits the canvas height = h * ch / h'
201             
202             double dh = h * sw;
203             double fh = h * ch / dh;
204             
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!
210             
211             // width of requested viewbox in device coordinates = dw = w * sh
212             // width of viewbox that fits the canvas height = fw = w * cw / w'
213             
214             double dw = w * sh;
215             double fw = w * cw / dw;
216             
217             viewBox.setFrameFromCenter(cx, cy, cx + fw * 0.5, cy + h * 0.5);
218 //            System.out.println("sw > sh: " + dw + ", " + fw + ": " + viewBox);
219         }
220     }
221
222     public static void move(Rectangle2D r, double tx, double ty) {
223         r.setFrame(r.getMinX() + tx, r.getMinY() + ty, r.getWidth(), r.getHeight());
224     }
225
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());
229         return r2;
230     }
231
232     
233     /**
234      * @param canvasSize
235      * @param px
236      * @param py
237      * @param sx
238      * @param sy
239      * @return
240      */
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);
243     }
244  
245     /**
246      * @param c
247      * @param px
248      * @param py
249      * @param sx
250      * @param sy
251      * @param uniform
252      * @return
253      */
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);
257         }
258         
259         AffineTransform view = getViewTransform(viewBox, canvasSize);
260         
261         AffineTransform at = AffineTransform.getTranslateInstance(px, py);
262         at.scale(sx, sy);
263         at.translate(-px, -py);
264         at.concatenate(view);
265         
266         Rectangle2D box = transformToViewBox(canvasSize, at, uniform);
267         return box;
268     }
269     
270     
271     
272     
273     
274     
275
276     public enum Align {Begin, Center, End, Fill};
277     
278     /**
279      * Creates tranform matrix, that converts coordinates from 
280      * uniform viewport to a 100x100 square. Aspect ratio remains the same.
281      *  
282      * @param viewport
283      * @param horiz Horizontal alignment of 100x100 square
284      * @param vert Vertical alignment of 100x100 square
285      * @return transform (that can be concatenated)
286      */
287     public static AffineTransform viewportToSquare(
288             Rectangle2D viewport, 
289             Align horiz, 
290             Align vert) {
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;
297         if (wide) {
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();            
304         } else {
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();            
311         }
312         
313         AffineTransform at = new AffineTransform();
314         at.scale(s, s);
315         at.translate(tx, ty);
316         return at;
317     }
318
319     /**
320      * Creates tranform matrix, that converts coordinates from 
321      * 100x100 square to a uniform viewport. Aspect ratio remains the same.
322      *  
323      * @param viewport
324      * @param horiz Horizontal alignment of 100x100 square
325      * @param vert Vertical alignment of 100x100 square
326      * @return transform (that can be concatenated)
327      */
328     public static AffineTransform squareToViewport(
329             Rectangle2D viewport, 
330             Align horiz, 
331             Align vert) {
332         AffineTransform at = viewportToSquare(viewport, horiz, vert);
333         try {
334             return at.createInverse();
335         } catch (NoninvertibleTransformException e) {
336             throw new RuntimeException(e);
337         }         
338     }
339     
340     
341 }